DebugPanel.php 5.8 KB

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