ProfileTarget.php 5.68 KB
Newer Older
w  
Qiang Xue 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
<?php
/**
 * CProfileLogRoute class file.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008-2011 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>
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @version $Id: CProfileLogRoute.php 3204 2011-05-05 21:36:32Z alexander.makarow $
 * @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'.
	 * @since 1.0.6
	 */
	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'.
	 */
	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
	 */
	protected function displayCallstack($logs)
	{
		$stack = array();
		$results = array();
		$n = 0;
		foreach ($logs as $log)
		{
Qiang Xue committed
99
			if ($log[1] !== CLogger::LEVEL_PROFILE) {
w  
Qiang Xue committed
100
				continue;
Qiang Xue committed
101
			}
w  
Qiang Xue committed
102
			$message = $log[0];
Qiang Xue committed
103
			if (!strncasecmp($message, 'begin:', 6)) {
w  
Qiang Xue committed
104 105 106 107
				$log[0] = substr($message, 6);
				$log[4] = $n;
				$stack[] = $log;
				$n++;
Qiang Xue committed
108
			} elseif (!strncasecmp($message, 'end:', 4)) {
w  
Qiang Xue committed
109
				$token = substr($message, 4);
Qiang Xue committed
110
				if (($last = array_pop($stack)) !== null && $last[0] === $token) {
w  
Qiang Xue committed
111 112 113 114
					$delta = $log[3] - $last[3];
					$results[$last[4]] = array($token, $delta, count($stack));
				}
				else
Qiang Xue committed
115
				{
w  
Qiang Xue committed
116 117
					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)));
Qiang Xue committed
118
				}
w  
Qiang Xue committed
119 120 121 122
			}
		}
		// remaining entries should be closed here
		$now = microtime(true);
Qiang Xue committed
123
		while (($last = array_pop($stack)) !== null) {
w  
Qiang Xue committed
124
			$results[$last[4]] = array($last[0], $now - $last[3], count($stack));
Qiang Xue committed
125
		}
w  
Qiang Xue committed
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
		ksort($results);
		$this->render('profile-callstack', $results);
	}

	/**
	 * Displays the summary report of the profiling result.
	 * @param array $logs list of logs
	 */
	protected function displaySummary($logs)
	{
		$stack = 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);
	}
}