* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CProfileLogRoute displays the profiling results in Web page. * * The profiling is done by calling {@link YiiBase::beginProfile()} and {@link YiiBase::endProfile()}, * which marks the begin and end of a code block. * * CProfileLogRoute supports two types of report by setting the {@link setReport report} property: * * * @property string $report The type of the profiling report to display. Defaults to 'summary'. * * @author Qiang Xue * @package system.logging * @since 1.0 */ class CProfileLogRoute extends CWebLogRoute { /** * @var boolean whether to aggregate results according to profiling tokens. * If false, the results will be aggregated by categories. * Defaults to true. Note that this property only affects the summary report * that is enabled when {@link report} is 'summary'. */ public $groupByToken=true; /** * @var string type of profiling report to display */ private $_report='summary'; /** * Initializes the route. * This method is invoked after the route is created by the route manager. */ public function init() { $this->levels=CLogger::LEVEL_PROFILE; } /** * @return string the type of the profiling report to display. Defaults to 'summary'. */ public function getReport() { return $this->_report; } /** * @param string $value the type of the profiling report to display. Valid values include 'summary' and 'callstack'. * @throws CException if given value is not "summary" or "callstack" */ public function setReport($value) { if($value==='summary' || $value==='callstack') $this->_report=$value; else throw new CException(Yii::t('yii','CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".', array('{report}'=>$value))); } /** * Displays the log messages. * @param array $logs list of log messages */ public function processLogs($logs) { $app=Yii::app(); if(!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest()) return; if($this->getReport()==='summary') $this->displaySummary($logs); else $this->displayCallstack($logs); } /** * Displays the callstack of the profiling procedures for display. * @param array $logs list of logs * @throws CException if Yii::beginProfile() and Yii::endProfile() are not matching */ protected function displayCallstack($logs) { $stack=array(); $results=array(); $n=0; foreach($logs as $log) { if($log[1]!==CLogger::LEVEL_PROFILE) continue; $message=$log[0]; if(!strncasecmp($message,'begin:',6)) { $log[0]=substr($message,6); $log[4]=$n; $stack[]=$log; $n++; } elseif(!strncasecmp($message,'end:',4)) { $token=substr($message,4); if(($last=array_pop($stack))!==null && $last[0]===$token) { $delta=$log[3]-$last[3]; $results[$last[4]]=array($token,$delta,count($stack)); } else throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', array('{token}'=>$token))); } } // remaining entries should be closed here $now=microtime(true); while(($last=array_pop($stack))!==null) $results[$last[4]]=array($last[0],$now-$last[3],count($stack)); ksort($results); $this->render('profile-callstack',$results); } /** * Displays the summary report of the profiling result. * @param array $logs list of logs * @throws CException if Yii::beginProfile() and Yii::endProfile() are not matching */ protected function displaySummary($logs) { $stack=array(); $results=array(); foreach($logs as $log) { if($log[1]!==CLogger::LEVEL_PROFILE) continue; $message=$log[0]; if(!strncasecmp($message,'begin:',6)) { $log[0]=substr($message,6); $stack[]=$log; } elseif(!strncasecmp($message,'end:',4)) { $token=substr($message,4); if(($last=array_pop($stack))!==null && $last[0]===$token) { $delta=$log[3]-$last[3]; if(!$this->groupByToken) $token=$log[2]; if(isset($results[$token])) $results[$token]=$this->aggregateResult($results[$token],$delta); else $results[$token]=array($token,1,$delta,$delta,$delta); } else throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', array('{token}'=>$token))); } } $now=microtime(true); while(($last=array_pop($stack))!==null) { $delta=$now-$last[3]; $token=$this->groupByToken ? $last[0] : $last[2]; if(isset($results[$token])) $results[$token]=$this->aggregateResult($results[$token],$delta); else $results[$token]=array($token,1,$delta,$delta,$delta); } $entries=array_values($results); $func=create_function('$a,$b','return $a[4]<$b[4]?1:0;'); usort($entries,$func); $this->render('profile-summary',$entries); } /** * Aggregates the report result. * @param array $result log result for this code block * @param float $delta time spent for this code block * @return array */ protected function aggregateResult($result,$delta) { list($token,$calls,$min,$max,$total)=$result; if($delta<$min) $min=$delta; elseif($delta>$max) $max=$delta; $calls++; $total+=$delta; return array($token,$calls,$min,$max,$total); } }