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
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['id'] = $id;
$this->panels[$id] = Yii::createObject($config);
......
......@@ -31,6 +31,12 @@ class Panel extends Component
*/
public $module;
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
......
......@@ -27,6 +27,15 @@ class DefaultController extends Controller
*/
public $summary;
public function actions()
{
$actions = [];
foreach($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function actionIndex()
{
return $this->render('index', ['manifest' => $this->getManifest()]);
......@@ -82,7 +91,7 @@ class DefaultController extends Controller
return $this->_manifest;
}
protected function loadData($tag)
public function loadData($tag)
{
$manifest = $this->getManifest();
if (isset($manifest[$tag])) {
......
......@@ -94,7 +94,7 @@ class Command extends Component
*/
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
return new QueryBuilder($this);
}
public function get($url, $options = [], $body = null)
public function get($url, $options = [], $body = null, $raw = false)
{
$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)
......@@ -189,37 +189,43 @@ class Connection extends Component
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();
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();
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();
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body);
return $this->httpRequest('DELETE', $this->createUrl($url, $options), $body, $raw);
}
private function createUrl($path, $options = [])
{
$url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($options)) {
$url .= '?' . http_build_query($options);
if (!is_string($path)) {
$url = implode('/', array_map(function($a) {
return urlencode(is_array($a) ? implode(',', $a) : $a);
}, $path));
if (!empty($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];
}
protected function httpRequest($method, $url, $requestBody = null)
protected function httpRequest($method, $url, $requestBody = null, $raw = false)
{
$method = strtoupper($method);
......@@ -228,7 +234,7 @@ class Connection extends Component
$body = '';
$options = [
CURLOPT_USERAGENT => 'Yii2 Framework ' . __CLASS__,
CURLOPT_USERAGENT => 'Yii Framework 2 ' . __CLASS__,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
// http://www.php.net/manual/en/function.curl-setopt.php#82418
......@@ -264,8 +270,11 @@ class Connection extends Component
if (is_array($url)) {
list($host, $q) = $url;
if (strncmp($host, 'inet[/', 6) == 0) {
$host = substr($host, 6, -1);
if (strncmp($host, 'inet[', 5) == 0) {
$host = substr($host, 5, -1);
if (($pos = strpos($host, '/')) !== false) {
$host = substr($host, $pos + 1);
}
}
$profile = $method . ' ' . $q . '#' . $requestBody;
$url = 'http://' . $host . '/' . $q;
......@@ -312,7 +321,7 @@ class Connection extends Component
]);
}
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'], [
'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 @@
namespace yii\elasticsearch;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger;
use yii\helpers\Html;
use yii\web\View;
......@@ -20,6 +21,17 @@ use yii\web\View;
*/
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()
{
return 'Elasticsearch';
......@@ -47,13 +59,14 @@ EOD;
public function getDetail()
{
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = [];
$i = 0;
foreach ($this->data['messages'] as $log) {
list ($message, $level, $category, $time, $traces) = $log;
if ($level == Logger::LEVEL_PROFILE_BEGIN) {
continue;
}
foreach ($timings as $logId => $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);
$message = $timing[1];
$traces = $timing[4];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
......@@ -66,48 +79,43 @@ EOD;
$traceString .= Html::ul($traces, [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
$runLinks = '';
$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
$('#elastic-link-$i-$c').on('click', function() {
$('#elastic-result-$i').html('Sending $type request to $nodeUrl...');
$('#elastic-result-$i').parent('tr').show();
$ajaxUrl = Html::url(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i').on('click', function() {
var result = $('#elastic-result-$i');
result.html('Sending request...');
result.parent('tr').show();
$.ajax({
type: "$type",
url: "$nodeUrl",
body: $nodeBody,
type: "POST",
url: "$ajaxUrl",
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;
});
JS
, View::POS_READY);
$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>';
$c++;
}
$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>";
$runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$rows[] = <<<HTML
<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++;
}
$rows = implode("\n", $rows);
......@@ -117,8 +125,9 @@ JS
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 80%;">Url / Query</th>
<th style="width: 20%;">Run Query on node</th>
<th style="width: 10%;">Time</th>
<th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr>
</thead>
<tbody>
......@@ -130,7 +139,7 @@ HTML;
private $_timings;
protected function calculateTimings()
public function calculateTimings()
{
if ($this->_timings !== null) {
return $this->_timings;
......
......@@ -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