ProfileTarget.php 5.51 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
<?php
/**
 * CProfileLogRoute class file.
 *
 * @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>
Alexander Makarov committed
23
 * @since 2.0
w  
Qiang Xue committed
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
 */
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'.
	 */
	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)
	{
Qiang Xue committed
74
		$app = \Yii::$application;
w  
Qiang Xue committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
		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
95
			if ($log[1] !== CLogger::LEVEL_PROFILE) {
w  
Qiang Xue committed
96
				continue;
Qiang Xue committed
97
			}
w  
Qiang Xue committed
98
			$message = $log[0];
Qiang Xue committed
99
			if (!strncasecmp($message, 'begin:', 6)) {
w  
Qiang Xue committed
100 101 102 103
				$log[0] = substr($message, 6);
				$log[4] = $n;
				$stack[] = $log;
				$n++;
Qiang Xue committed
104
			} elseif (!strncasecmp($message, 'end:', 4)) {
w  
Qiang Xue committed
105
				$token = substr($message, 4);
Qiang Xue committed
106
				if (($last = array_pop($stack)) !== null && $last[0] === $token) {
w  
Qiang Xue committed
107 108
					$delta = $log[3] - $last[3];
					$results[$last[4]] = array($token, $delta, count($stack));
Qiang Xue committed
109
				} else
Qiang Xue committed
110
				{
w  
Qiang Xue committed
111 112
					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
113
				}
w  
Qiang Xue committed
114 115 116 117
			}
		}
		// remaining entries should be closed here
		$now = microtime(true);
Qiang Xue committed
118
		while (($last = array_pop($stack)) !== null) {
w  
Qiang Xue committed
119
			$results[$last[4]] = array($last[0], $now - $last[3], count($stack));
Qiang Xue committed
120
		}
w  
Qiang Xue committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		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;
Qiang Xue committed
141
			} elseif (!strncasecmp($message, 'end:', 4))
w  
Qiang Xue committed
142 143 144 145 146 147 148 149 150 151 152
			{
				$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);
Qiang Xue committed
153
				} else
w  
Qiang Xue committed
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
					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);
	}
}