CProfileLogRoute.php 5.73 KB
Newer Older
JULIO JARAMILLO's avatar
JULIO JARAMILLO committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
<?php
/**
 * CProfileLogRoute class file.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @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:
 * <ul>
 * <li>summary: list the execution time of every marked code block</li>
 * <li>callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.</li>
 * </ul>
 *
 * @property string $report The type of the profiling report to display. Defaults to 'summary'.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @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);
	}
}