DebugPanel.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php
  2. /**
  3. * xunsearch DebugPanel class file
  4. *
  5. * @author hightman
  6. * @link http://www.xunsearch.com/
  7. * @copyright Copyright &copy; 2014 HangZhou YunSheng Network Technology Co., Ltd.
  8. * @license http://www.xunsearch.com/license/
  9. * @version $Id$
  10. */
  11. namespace hightman\xunsearch;
  12. use yii\debug\Panel;
  13. use yii\helpers\ArrayHelper;
  14. use yii\helpers\Url;
  15. use yii\log\Logger;
  16. use yii\helpers\Html;
  17. use yii\web\View;
  18. use Yii;
  19. /**
  20. * Debugger panel that collects and displays xunsearch queries performed to server-side.
  21. *
  22. * @author hightman <hightman@twomice.net>
  23. * @since 1.4.9
  24. */
  25. class DebugPanel extends Panel
  26. {
  27. public $com = 'xunsearch';
  28. private $_timings;
  29. public function init()
  30. {
  31. $this->actions['xunsearch-query'] = [
  32. 'class' => 'hightman\xunsearch\DebugAction',
  33. 'panel' => $this,
  34. 'com' => $this->com,
  35. ];
  36. }
  37. /**
  38. * @inheritdoc
  39. */
  40. public function getName()
  41. {
  42. return 'Xunsearch';
  43. }
  44. /**
  45. * @inheritdoc
  46. */
  47. public function getSummary()
  48. {
  49. $timings = $this->calculateTimings();
  50. $queryCount = count($timings);
  51. $queryTime = 0;
  52. foreach ($timings as $timing) {
  53. $queryTime += $timing[3];
  54. }
  55. $queryTime = number_format($queryTime * 1000) . ' ms';
  56. $url = $this->getUrl();
  57. $output = <<<EOD
  58. <div class="yii-debug-toolbar__block">
  59. <a href="$url" title="Executed $queryCount xunsearch queries which took $queryTime.">
  60. XS <span class="yii-debug-toolbar__label yii-debug-toolbar__label_info">$queryCount</span> <span class="yii-debug-toolbar__label">$queryTime</span>
  61. </a>
  62. </div>
  63. EOD;
  64. return $queryCount > 0 ? $output : '';
  65. }
  66. /**
  67. * @inheritdoc
  68. */
  69. public function getDetail()
  70. {
  71. $timings = $this->calculateTimings();
  72. ArrayHelper::multisort($timings, 3, SORT_DESC);
  73. $i = 0;
  74. foreach ($timings as $logId => $timing) {
  75. $duration = sprintf('%.1f ms', $timing[3] * 1000);
  76. $message = $timing[1];
  77. $traces = $timing[4];
  78. if (($pos = mb_strpos($message, "#")) !== false) {
  79. $url = mb_substr($message, 0, $pos);
  80. $body = mb_substr($message, $pos + 1);
  81. } else {
  82. $url = $message;
  83. $body = null;
  84. }
  85. $traceString = '';
  86. if (!empty($traces)) {
  87. $traceString .= Html::ul($traces, [
  88. 'class' => 'trace',
  89. 'item' => function ($trace) {
  90. return "<li>{$trace['file']}({$trace['line']})</li>";
  91. },
  92. ]);
  93. }
  94. $runLink = '';
  95. if (($pos = strrpos($url, '.')) !== false) {
  96. $action = substr($url, $pos + 1);
  97. if (!strncmp($action, 'find', 4) || !strcmp($action, 'count')) {
  98. $runLink = Html::a('run query', '#', ['id' => "xun-link-$i"]);
  99. $ajaxUrl = Url::to(['xunsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
  100. Yii::$app->view->registerJs(<<<JS
  101. $('#xun-link-$i').on('click', function () {
  102. var result = $('#xun-result-$i');
  103. result.html('Sending request...');
  104. result.parent('tr').show();
  105. $.ajax({
  106. type: "POST",
  107. url: "$ajaxUrl",
  108. success: function (data) {
  109. $('#xun-time-$i').html(data.time);
  110. $('#xun-result-$i').html(data.result);
  111. },
  112. error: function (jqXHR, textStatus, errorThrown) {
  113. $('#xun-time-$i').html('');
  114. $('#xun-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
  115. },
  116. dataType: "json"
  117. });
  118. return false;
  119. });
  120. JS
  121. , View::POS_READY);
  122. }
  123. }
  124. $rows[] = <<<HTML
  125. <tr>
  126. <td style="width: 10%;">$duration</td>
  127. <td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
  128. <td style="width: 15%;">$runLink</td>
  129. </tr>
  130. <tr style="display: none;"><td id="xun-time-$i"></td><td colspan="3" id="xun-result-$i"></td></tr>
  131. HTML;
  132. $i++;
  133. }
  134. $rows = implode("\n", $rows);
  135. return <<<HTML
  136. <h1>Xunsearch Queries</h1>
  137. <table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
  138. <thead>
  139. <tr>
  140. <th style="width: 10%;">Time</th>
  141. <th style="width: 75%;">Query</th>
  142. <th style="width: 15%;">Run Query</th>
  143. </tr>
  144. </thead>
  145. <tbody>
  146. $rows
  147. </tbody>
  148. </table>
  149. HTML;
  150. }
  151. public function calculateTimings()
  152. {
  153. if ($this->_timings !== null) {
  154. return $this->_timings;
  155. }
  156. $messages = $this->data['messages'];
  157. $timings = [];
  158. $stack = [];
  159. foreach ($messages as $i => $log) {
  160. list($token, $level, $category, $timestamp) = $log;
  161. $log[5] = $i;
  162. if ($level == Logger::LEVEL_PROFILE_BEGIN) {
  163. $stack[] = $log;
  164. } elseif ($level == Logger::LEVEL_PROFILE_END) {
  165. if (($last = array_pop($stack)) !== null && $last[0] === $token) {
  166. $timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]];
  167. }
  168. }
  169. }
  170. $now = microtime(true);
  171. while (($last = array_pop($stack)) !== null) {
  172. $delta = $now - $last[3];
  173. $timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]];
  174. }
  175. ksort($timings);
  176. return $this->_timings = $timings;
  177. }
  178. /**
  179. * @inheritdoc
  180. */
  181. public function save()
  182. {
  183. $target = $this->module->logTarget;
  184. $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, [ __NAMESPACE__ . '\*']);
  185. return ['messages' => $messages];
  186. }
  187. }