CProfileLogRoute.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. /**
  3. * CProfileLogRoute class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CProfileLogRoute displays the profiling results in Web page.
  12. *
  13. * The profiling is done by calling {@link YiiBase::beginProfile()} and {@link YiiBase::endProfile()},
  14. * which marks the begin and end of a code block.
  15. *
  16. * CProfileLogRoute supports two types of report by setting the {@link setReport report} property:
  17. * <ul>
  18. * <li>summary: list the execution time of every marked code block</li>
  19. * <li>callstack: list the mark code blocks in a hierarchical view reflecting their calling sequence.</li>
  20. * </ul>
  21. *
  22. * @property string $report The type of the profiling report to display. Defaults to 'summary'.
  23. *
  24. * @author Qiang Xue <qiang.xue@gmail.com>
  25. * @package system.logging
  26. * @since 1.0
  27. */
  28. class CProfileLogRoute extends CWebLogRoute
  29. {
  30. /**
  31. * @var boolean whether to aggregate results according to profiling tokens.
  32. * If false, the results will be aggregated by categories.
  33. * Defaults to true. Note that this property only affects the summary report
  34. * that is enabled when {@link report} is 'summary'.
  35. */
  36. public $groupByToken=true;
  37. /**
  38. * @var string type of profiling report to display
  39. */
  40. private $_report='summary';
  41. /**
  42. * Initializes the route.
  43. * This method is invoked after the route is created by the route manager.
  44. */
  45. public function init()
  46. {
  47. $this->levels=CLogger::LEVEL_PROFILE;
  48. }
  49. /**
  50. * @return string the type of the profiling report to display. Defaults to 'summary'.
  51. */
  52. public function getReport()
  53. {
  54. return $this->_report;
  55. }
  56. /**
  57. * @param string $value the type of the profiling report to display. Valid values include 'summary' and 'callstack'.
  58. * @throws CException if given value is not "summary" or "callstack"
  59. */
  60. public function setReport($value)
  61. {
  62. if($value==='summary' || $value==='callstack')
  63. $this->_report=$value;
  64. else
  65. throw new CException(Yii::t('yii','CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".',
  66. array('{report}'=>$value)));
  67. }
  68. /**
  69. * Displays the log messages.
  70. * @param array $logs list of log messages
  71. */
  72. public function processLogs($logs)
  73. {
  74. $app=Yii::app();
  75. if(!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest())
  76. return;
  77. if($this->getReport()==='summary')
  78. $this->displaySummary($logs);
  79. else
  80. $this->displayCallstack($logs);
  81. }
  82. /**
  83. * Displays the callstack of the profiling procedures for display.
  84. * @param array $logs list of logs
  85. * @throws CException if Yii::beginProfile() and Yii::endProfile() are not matching
  86. */
  87. protected function displayCallstack($logs)
  88. {
  89. $stack=array();
  90. $results=array();
  91. $n=0;
  92. foreach($logs as $log)
  93. {
  94. if($log[1]!==CLogger::LEVEL_PROFILE)
  95. continue;
  96. $message=$log[0];
  97. if(!strncasecmp($message,'begin:',6))
  98. {
  99. $log[0]=substr($message,6);
  100. $log[4]=$n;
  101. $stack[]=$log;
  102. $n++;
  103. }
  104. elseif(!strncasecmp($message,'end:',4))
  105. {
  106. $token=substr($message,4);
  107. if(($last=array_pop($stack))!==null && $last[0]===$token)
  108. {
  109. $delta=$log[3]-$last[3];
  110. $results[$last[4]]=array($token,$delta,count($stack));
  111. }
  112. else
  113. 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.',
  114. array('{token}'=>$token)));
  115. }
  116. }
  117. // remaining entries should be closed here
  118. $now=microtime(true);
  119. while(($last=array_pop($stack))!==null)
  120. $results[$last[4]]=array($last[0],$now-$last[3],count($stack));
  121. ksort($results);
  122. $this->render('profile-callstack',$results);
  123. }
  124. /**
  125. * Displays the summary report of the profiling result.
  126. * @param array $logs list of logs
  127. * @throws CException if Yii::beginProfile() and Yii::endProfile() are not matching
  128. */
  129. protected function displaySummary($logs)
  130. {
  131. $stack=array();
  132. $results=array();
  133. foreach($logs as $log)
  134. {
  135. if($log[1]!==CLogger::LEVEL_PROFILE)
  136. continue;
  137. $message=$log[0];
  138. if(!strncasecmp($message,'begin:',6))
  139. {
  140. $log[0]=substr($message,6);
  141. $stack[]=$log;
  142. }
  143. elseif(!strncasecmp($message,'end:',4))
  144. {
  145. $token=substr($message,4);
  146. if(($last=array_pop($stack))!==null && $last[0]===$token)
  147. {
  148. $delta=$log[3]-$last[3];
  149. if(!$this->groupByToken)
  150. $token=$log[2];
  151. if(isset($results[$token]))
  152. $results[$token]=$this->aggregateResult($results[$token],$delta);
  153. else
  154. $results[$token]=array($token,1,$delta,$delta,$delta);
  155. }
  156. else
  157. 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.',
  158. array('{token}'=>$token)));
  159. }
  160. }
  161. $now=microtime(true);
  162. while(($last=array_pop($stack))!==null)
  163. {
  164. $delta=$now-$last[3];
  165. $token=$this->groupByToken ? $last[0] : $last[2];
  166. if(isset($results[$token]))
  167. $results[$token]=$this->aggregateResult($results[$token],$delta);
  168. else
  169. $results[$token]=array($token,1,$delta,$delta,$delta);
  170. }
  171. $entries=array_values($results);
  172. $func=create_function('$a,$b','return $a[4]<$b[4]?1:0;');
  173. usort($entries,$func);
  174. $this->render('profile-summary',$entries);
  175. }
  176. /**
  177. * Aggregates the report result.
  178. * @param array $result log result for this code block
  179. * @param float $delta time spent for this code block
  180. * @return array
  181. */
  182. protected function aggregateResult($result,$delta)
  183. {
  184. list($token,$calls,$min,$max,$total)=$result;
  185. if($delta<$min)
  186. $min=$delta;
  187. elseif($delta>$max)
  188. $max=$delta;
  189. $calls++;
  190. $total+=$delta;
  191. return array($token,$calls,$min,$max,$total);
  192. }
  193. }