Commit 513e98cf by Qiang Xue

Merge pull request #1697 from Ragazzo/debug_module_db_panel_improvements

improved database panel in debug module
parents 0788e104 d5bc3c70
...@@ -29,7 +29,7 @@ class Exact extends Base ...@@ -29,7 +29,7 @@ class Exact extends Base
if (!$this->partial) { if (!$this->partial) {
return (mb_strtolower($this->value, 'utf8') == mb_strtolower($value, 'utf8')); return (mb_strtolower($this->value, 'utf8') == mb_strtolower($value, 'utf8'));
} else { } else {
return (mb_strpos($value, $this->value) !== false); return (mb_strpos(mb_strtolower($value, 'utf8'), mb_strtolower($this->value,'utf8')) !== false);
} }
} }
......
<?php
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Db represents the model behind the search form about current request database queries.
*/
class Db extends Base
{
/**
* @var string type attribute input search value
*/
public $type;
/**
* @var integer query attribute input search value
*/
public $query;
public function rules()
{
return [
[['type', 'query'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'type' => 'Type',
'query' => 'Query',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params
* @param array $models
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => [
'pageSize' => 10,
],
'sort' => [
'attributes' => ['duration','type','query'],
'defaultOrder' => [
'duration' => SORT_DESC,
],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'type', true);
$this->addCondition($filter, 'query', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
...@@ -7,10 +7,10 @@ ...@@ -7,10 +7,10 @@
namespace yii\debug\panels; namespace yii\debug\panels;
use Yii;
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\debug\models\search\Db;
/** /**
* Debugger panel that collects and displays database queries performed. * Debugger panel that collects and displays database queries performed.
...@@ -20,6 +20,17 @@ use yii\helpers\Html; ...@@ -20,6 +20,17 @@ use yii\helpers\Html;
*/ */
class DbPanel extends Panel class DbPanel extends Panel
{ {
/**
* @var array db queries info extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @var array current database request timings
*/
private $_timings;
public function getName() public function getName()
{ {
return 'Database'; return 'Database';
...@@ -29,61 +40,27 @@ class DbPanel extends Panel ...@@ -29,61 +40,27 @@ class DbPanel extends Panel
{ {
$timings = $this->calculateTimings(); $timings = $this->calculateTimings();
$queryCount = count($timings); $queryCount = count($timings);
$queryTime = 0; $queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
foreach ($timings as $timing) {
$queryTime += $timing[3]; return Yii::$app->view->render('panels/db/summary',[
} 'timings' => $this->calculateTimings(),
$queryTime = number_format($queryTime * 1000) . ' ms'; 'panel' => $this,
$url = $this->getUrl(); 'queryCount' => $queryCount,
$output = <<<EOD 'queryTime' => $queryTime,
<div class="yii-debug-toolbar-block"> ]);
<a href="$url" title="Executed $queryCount database queries which took $queryTime.">
DB <span class="label">$queryCount</span> <span class="label">$queryTime</span>
</a>
</div>
EOD;
return $queryCount > 0 ? $output : '';
} }
public function getDetail() public function getDetail()
{ {
$timings = $this->calculateTimings(); $searchModel = new Db();
ArrayHelper::multisort($timings, 3, SORT_DESC); $dataProvider = $searchModel->search($_GET, $this->getModels());
$rows = [];
foreach ($timings as $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);
$procedure = Html::encode($timing[1]);
$traces = $timing[4];
if (!empty($traces)) {
$procedure .= Html::ul($traces, [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
$rows[] = "<tr><td style=\"width: 80px;\">$duration</td><td>$procedure</td>";
}
$rows = implode("\n", $rows);
return <<<EOD
<h1>Database Queries</h1>
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 80px;">Time</th>
<th>Query</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
EOD;
}
private $_timings; return Yii::$app->view->render('panels/db/detail',[
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
protected function calculateTimings() protected function calculateTimings()
{ {
...@@ -120,4 +97,57 @@ EOD; ...@@ -120,4 +97,57 @@ EOD;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']); $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
return ['messages' => $messages]; return ['messages' => $messages];
} }
/**
* Returns total queries time.
* @param array $timings
* @return integer total time
*/
protected function getTotalQueryTime($timings)
{
$queryTime = 0;
foreach ($timings as $timing) {
$queryTime += $timing[3];
}
return $queryTime;
}
/**
* Returns array of models that represents logs of the current request. Can be used with data providers,
* like yii\data\ArrayDataProvider.
* @return array models
*/
protected function getModels()
{
if ($this->_models === null || $refresh) {
$this->_models = [];
$timings = $this->calculateTimings();
foreach($timings as $dbTiming) {
$this->_models[] = [
'type' => $this->detectQueryType($dbTiming[1]),
'query' => $dbTiming[1],
'duration' => ($dbTiming[3] * 1000), #in milliseconds
'trace' => $dbTiming[4],
];
}
}
return $this->_models;
}
/**
* Detects databse timing type. Detecting is produced through simple parsing to the first space|tab|new row.
* First word before space is timing type. If there is no such words, timing will have empty type.
* @param string $timing timing procedure string
* @return string query type select|insert|delete|etc
*/
protected function detectQueryType($timing)
{
$timing = ltrim($timing);
preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? $matches[0] : '';
}
} }
...@@ -24,7 +24,7 @@ class LogPanel extends Panel ...@@ -24,7 +24,7 @@ class LogPanel extends Panel
/** /**
* @var array log messages extracted to array as models, to use with data provider. * @var array log messages extracted to array as models, to use with data provider.
*/ */
private $_models ; private $_models;
public function getName() public function getName()
{ {
......
<?php
use yii\helpers\Html;
use yii\grid\GridView;
?>
<h1>Database Queries</h1>
<?php
echo GridView::widget([
'dataProvider' => $dataProvider,
'id' => 'db-panel-detailed-grid',
'filterModel' => $searchModel,
'filterUrl' => $panel->getUrl(),
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'attribute' => 'duration',
'value' => function ($data)
{
return sprintf('%.1f ms',$data['duration']);
},
],
[
'attribute' => 'type',
'value' => function ($data)
{
return Html::encode(mb_strtoupper($data['type'],'utf8'));
},
],
[
'attribute' => 'query',
'value' => function ($data)
{
$query = Html::encode($data['query']);
if (!empty($data['trace'])) {
$query .= Html::ul($data['trace'], [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
return $query;
},
'format' => 'html',
'options' => [
'width' => '70%',
],
]
],
]);
?>
\ No newline at end of file
<?php if ($queryCount): ?>
<div class="yii-debug-toolbar-block">
<a href="$url" title="Executed <?php echo $queryCount; ?> database queries which took <?php echo $queryTime; ?>.">
DB <span class="label"><?php echo $queryCount; ?></span> <span class="label"><?php echo $queryTime; ?></span>
</a>
</div>
<?php endif; ?>
\ 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