Commit 62ef3721 by Qiang Xue

Merge pull request #1382 from cebe/elastic-debug-toolbar

Elasticsearch debug toolbar
parents ecc1688d b68c9f1d
...@@ -61,7 +61,8 @@ class Module extends \yii\base\Module ...@@ -61,7 +61,8 @@ class Module extends \yii\base\Module
Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); Yii::$app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']);
}); });
foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { $this->panels = array_merge($this->corePanels(), $this->panels);
foreach ($this->panels as $id => $config) {
$config['module'] = $this; $config['module'] = $this;
$config['id'] = $id; $config['id'] = $id;
$this->panels[$id] = Yii::createObject($config); $this->panels[$id] = Yii::createObject($config);
......
...@@ -31,6 +31,12 @@ class Panel extends Component ...@@ -31,6 +31,12 @@ class Panel extends Component
*/ */
public $module; public $module;
public $data; public $data;
/**
* @var array array of actions to add to the debug modules default controller.
* This array will be merged with all other panels actions property.
* See [[yii\base\Controller::actions()]] for the format.
*/
public $actions = [];
/** /**
* @return string name of the panel * @return string name of the panel
......
...@@ -27,6 +27,15 @@ class DefaultController extends Controller ...@@ -27,6 +27,15 @@ class DefaultController extends Controller
*/ */
public $summary; public $summary;
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function actionIndex() public function actionIndex()
{ {
return $this->render('index', ['manifest' => $this->getManifest()]); return $this->render('index', ['manifest' => $this->getManifest()]);
...@@ -82,7 +91,7 @@ class DefaultController extends Controller ...@@ -82,7 +91,7 @@ class DefaultController extends Controller
return $this->_manifest; return $this->_manifest;
} }
protected function loadData($tag) public function loadData($tag)
{ {
$manifest = $this->getManifest(); $manifest = $this->getManifest();
if (isset($manifest[$tag])) { if (isset($manifest[$tag])) {
......
...@@ -94,7 +94,7 @@ class Command extends Component ...@@ -94,7 +94,7 @@ class Command extends Component
*/ */
public function get($index, $type, $id, $options = []) public function get($index, $type, $id, $options = [])
{ {
return $this->db->get([$index, $type, $id], $options, null, [200, 404]); return $this->db->get([$index, $type, $id], $options, null);
} }
/** /**
......
...@@ -177,10 +177,10 @@ class Connection extends Component ...@@ -177,10 +177,10 @@ class Connection extends Component
return new QueryBuilder($this); return new QueryBuilder($this);
} }
public function get($url, $options = [], $body = null) public function get($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('GET', $this->createUrl($url, $options), $body); return $this->httpRequest('GET', $this->createUrl($url, $options), $body, $raw);
} }
public function head($url, $options = [], $body = null) public function head($url, $options = [], $body = null)
...@@ -189,37 +189,43 @@ class Connection extends Component ...@@ -189,37 +189,43 @@ class Connection extends Component
return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body); return $this->httpRequest('HEAD', $this->createUrl($url, $options), $body);
} }
public function post($url, $options = [], $body = null) public function post($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('POST', $this->createUrl($url, $options), $body); return $this->httpRequest('POST', $this->createUrl($url, $options), $body, $raw);
} }
public function put($url, $options = [], $body = null) public function put($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('PUT', $this->createUrl($url, $options), $body); return $this->httpRequest('PUT', $this->createUrl($url, $options), $body, $raw);
} }
public function delete($url, $options = [], $body = null) public function delete($url, $options = [], $body = null, $raw = false)
{ {
$this->open(); $this->open();
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body); return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body, $raw);
} }
private function createUrl($path, $options = []) private function createUrl($path, $options = [])
{ {
if (!is_string($path)) {
$url = implode('/', array_map(function($a) { $url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a); return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path)); }, $path));
if (!empty($options)) { if (!empty($options)) {
$url .= '?' . http_build_query($options); $url .= '?' . http_build_query($options);
} }
} else {
$url = $path;
if (!empty($options)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options);
}
}
return [$this->nodes[$this->activeNode]['http_address'], $url]; return [$this->nodes[$this->activeNode]['http_address'], $url];
} }
protected function httpRequest($method, $url, $requestBody = null) protected function httpRequest($method, $url, $requestBody = null, $raw = false)
{ {
$method = strtoupper($method); $method = strtoupper($method);
...@@ -228,7 +234,7 @@ class Connection extends Component ...@@ -228,7 +234,7 @@ class Connection extends Component
$body = ''; $body = '';
$options = [ $options = [
CURLOPT_USERAGENT => 'Yii2 Framework ' . __CLASS__, CURLOPT_USERAGENT => 'Yii Framework 2 ' . __CLASS__,
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false, CURLOPT_HEADER => false,
// http://www.php.net/manual/en/function.curl-setopt.php#82418 // http://www.php.net/manual/en/function.curl-setopt.php#82418
...@@ -264,8 +270,11 @@ class Connection extends Component ...@@ -264,8 +270,11 @@ class Connection extends Component
if (is_array($url)) { if (is_array($url)) {
list($host, $q) = $url; list($host, $q) = $url;
if (strncmp($host, 'inet[/', 6) == 0) { if (strncmp($host, 'inet[', 5) == 0) {
$host = substr($host, 6, -1); $host = substr($host, 5, -1);
if (($pos = strpos($host, '/')) !== false) {
$host = substr($host, $pos + 1);
}
} }
$profile = $method . ' ' . $q . '#' . $requestBody; $profile = $method . ' ' . $q . '#' . $requestBody;
$url = 'http://' . $host . '/' . $q; $url = 'http://' . $host . '/' . $q;
...@@ -312,7 +321,7 @@ class Connection extends Component ...@@ -312,7 +321,7 @@ class Connection extends Component
]); ]);
} }
if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) { if (isset($headers['content-type']) && !strncmp($headers['content-type'], 'application/json', 16)) {
return Json::decode($body); return $raw ? $body : Json::decode($body);
} }
throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [ throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [
'requestMethod' => $method, 'requestMethod' => $method,
......
<?php
/**
* @author Carsten Brandt <mail@cebe.cc>
*/
namespace yii\elasticsearch;
use yii\base\Action;
use yii\base\NotSupportedException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use Yii;
use yii\web\Response;
class DebugAction extends Action
{
/**
* @var string the connection id to use
*/
public $db;
/**
* @var Panel
*/
public $panel;
public function run($logId, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
if (!isset($timings[$logId])) {
throw new HttpException(404, 'Log message not found.');
}
$message = $timings[$logId][1];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$method = mb_substr($url, 0, $pos = mb_strpos($url, ' '));
$url = mb_substr($url, $pos + 1);
$options = ['pretty' => true];
/** @var Connection $db */
$db = \Yii::$app->getComponent($this->db);
$time = microtime(true);
switch($method) {
case 'GET': $result = $db->get($url, $options, $body, true); break;
case 'POST': $result = $db->post($url, $options, $body, true); break;
case 'PUT': $result = $db->put($url, $options, $body, true); break;
case 'DELETE': $result = $db->delete($url, $options, $body, true); break;
case 'HEAD': $result = $db->head($url, $options, $body); break;
default:
throw new NotSupportedException("Request method '$method' is not supported by elasticsearch.");
}
$time = microtime(true) - $time;
if ($result === true) {
$result = '<span class="label label-success">success</span>';
} elseif ($result === false) {
$result = '<span class="label label-danger">no success</span>';
}
Yii::$app->response->format = Response::FORMAT_JSON;
return [
'time' => sprintf('%.1f ms', $time * 1000),
'result' => $result,
];
}
}
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace yii\elasticsearch; namespace yii\elasticsearch;
use yii\debug\Panel; use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger; use yii\log\Logger;
use yii\helpers\Html; use yii\helpers\Html;
use yii\web\View; use yii\web\View;
...@@ -20,6 +21,17 @@ use yii\web\View; ...@@ -20,6 +21,17 @@ use yii\web\View;
*/ */
class DebugPanel extends Panel class DebugPanel extends Panel
{ {
public $db = 'elasticsearch';
public function init()
{
$this->actions['elasticsearch-query'] = [
'class' => 'yii\\elasticsearch\\DebugAction',
'panel' => $this,
'db' => $this->db,
];
}
public function getName() public function getName()
{ {
return 'Elasticsearch'; return 'Elasticsearch';
...@@ -47,13 +59,14 @@ EOD; ...@@ -47,13 +59,14 @@ EOD;
public function getDetail() public function getDetail()
{ {
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = []; $rows = [];
$i = 0; $i = 0;
foreach ($this->data['messages'] as $log) { foreach ($timings as $logId => $timing) {
list ($message, $level, $category, $time, $traces) = $log; $duration = sprintf('%.1f ms', $timing[3] * 1000);
if ($level == Logger::LEVEL_PROFILE_BEGIN) { $message = $timing[1];
continue; $traces = $timing[4];
}
if (($pos = mb_strpos($message, "#")) !== false) { if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos); $url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1); $body = mb_substr($message, $pos + 1);
...@@ -70,44 +83,39 @@ EOD; ...@@ -70,44 +83,39 @@ EOD;
}, },
]); ]);
} }
$runLinks = ''; $ajaxUrl = Html::url(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
$c = 0;
\Yii::$app->elasticsearch->open();
foreach(\Yii::$app->elasticsearch->nodes as $node) {
$pos = mb_strpos($url, ' ');
$type = mb_substr($url, 0, $pos);
if ($type == 'GET' && !empty($body)) {
$type = 'POST';
}
$host = $node['http_address'];
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
}
$nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1);
$nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true';
$nodeBody = json_encode($body);
\Yii::$app->view->registerJs(<<<JS \Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i-$c').on('click', function() { $('#elastic-link-$i').on('click', function() {
$('#elastic-result-$i').html('Sending $type request to $nodeUrl...'); var result = $('#elastic-result-$i');
$('#elastic-result-$i').parent('tr').show(); result.html('Sending request...');
result.parent('tr').show();
$.ajax({ $.ajax({
type: "$type", type: "POST",
url: "$nodeUrl", url: "$ajaxUrl",
body: $nodeBody,
success: function( data ) { success: function( data ) {
$('#elastic-result-$i').html(data); $('#elastic-time-$i').html(data.time);
$('#elastic-result-$i').html(data.result);
}, },
dataType: "text" error: function(jqXHR, textStatus, errorThrown) {
$('#elastic-time-$i').html('');
$('#elastic-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
},
dataType: "json"
}); });
return false; return false;
}); });
JS JS
, View::POS_READY); , View::POS_READY);
$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>'; $runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$c++; $rows[] = <<<HTML
} <tr>
$rows[] = "<tr><td style=\"width: 80%;\"><div><b>$url</b><br/><p>$body</p>$traceString</div></td><td style=\"width: 20%;\">$runLinks</td></tr><tr style=\"display: none;\"><td colspan=\"2\" id=\"elastic-result-$i\"></td></tr>"; <td style="width: 10%;">$duration</td>
<td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
<td style="width: 15%;">$runLink</td>
</tr>
<tr style="display: none;"><td id="elastic-time-$i"></td><td colspan="3" id="elastic-result-$i"></td></tr>
HTML;
$i++; $i++;
} }
$rows = implode("\n", $rows); $rows = implode("\n", $rows);
...@@ -117,8 +125,9 @@ JS ...@@ -117,8 +125,9 @@ JS
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;"> <table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead> <thead>
<tr> <tr>
<th style="width: 80%;">Url / Query</th> <th style="width: 10%;">Time</th>
<th style="width: 20%;">Run Query on node</th> <th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -130,7 +139,7 @@ HTML; ...@@ -130,7 +139,7 @@ HTML;
private $_timings; private $_timings;
protected function calculateTimings() public function calculateTimings()
{ {
if ($this->_timings !== null) { if ($this->_timings !== null) {
return $this->_timings; return $this->_timings;
......
...@@ -172,3 +172,5 @@ Add the following to you application config to enable it: ...@@ -172,3 +172,5 @@ Add the following to you application config to enable it:
], ],
// ... // ...
``` ```
![elasticsearch DebugPanel](README-debug.png)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment