2011-02-02 13:48:52 -08:00
|
|
|
<?php
|
|
|
|
|
|
2011-09-14 08:02:31 -07:00
|
|
|
/**
|
|
|
|
|
* @group console
|
|
|
|
|
*/
|
2012-03-13 11:18:11 -07:00
|
|
|
final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
|
2011-02-02 13:48:52 -08:00
|
|
|
|
|
|
|
|
protected $observations;
|
|
|
|
|
|
|
|
|
|
public function getName() {
|
|
|
|
|
return 'Services';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getDescription() {
|
|
|
|
|
return 'Information about services.';
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-25 06:11:39 -08:00
|
|
|
/**
|
|
|
|
|
* @phutil-external-symbol class PhabricatorStartup
|
|
|
|
|
*/
|
2011-02-02 22:38:42 -08:00
|
|
|
public function generateData() {
|
2011-05-20 21:15:00 -07:00
|
|
|
|
2011-07-09 09:45:19 -07:00
|
|
|
$log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
|
|
|
|
|
foreach ($log as $key => $entry) {
|
2011-07-12 09:41:34 -07:00
|
|
|
$config = idx($entry, 'config', array());
|
2011-07-09 09:45:19 -07:00
|
|
|
unset($log[$key]['config']);
|
|
|
|
|
|
|
|
|
|
if (empty($_REQUEST['__analyze__'])) {
|
|
|
|
|
$log[$key]['explain'] = array(
|
|
|
|
|
'sev' => 7,
|
|
|
|
|
'size' => null,
|
|
|
|
|
'reason' => 'Disabled',
|
|
|
|
|
);
|
|
|
|
|
// Query analysis is disabled for this request, so don't do any of it.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($entry['type'] != 'query') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
|
|
|
|
|
// causing table scans, etc.
|
|
|
|
|
if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {
|
2012-04-06 21:29:19 -07:00
|
|
|
$conn = PhabricatorEnv::newObjectFromConfig(
|
|
|
|
|
'mysql.implementation',
|
|
|
|
|
array($entry['config']));
|
2011-07-09 09:45:19 -07:00
|
|
|
try {
|
|
|
|
|
$explain = queryfx_all(
|
|
|
|
|
$conn,
|
|
|
|
|
'EXPLAIN %Q',
|
|
|
|
|
$entry['query']);
|
|
|
|
|
|
|
|
|
|
$badness = 0;
|
|
|
|
|
$size = 1;
|
|
|
|
|
$reason = null;
|
|
|
|
|
|
|
|
|
|
foreach ($explain as $table) {
|
|
|
|
|
$size *= (int)$table['rows'];
|
|
|
|
|
|
|
|
|
|
switch ($table['type']) {
|
|
|
|
|
case 'index':
|
|
|
|
|
$cur_badness = 1;
|
|
|
|
|
$cur_reason = 'Index';
|
|
|
|
|
break;
|
|
|
|
|
case 'const':
|
|
|
|
|
$cur_badness = 1;
|
|
|
|
|
$cur_reason = 'Const';
|
|
|
|
|
break;
|
|
|
|
|
case 'eq_ref';
|
|
|
|
|
$cur_badness = 2;
|
|
|
|
|
$cur_reason = 'EqRef';
|
|
|
|
|
break;
|
|
|
|
|
case 'range':
|
|
|
|
|
$cur_badness = 3;
|
|
|
|
|
$cur_reason = 'Range';
|
|
|
|
|
break;
|
|
|
|
|
case 'ref':
|
|
|
|
|
$cur_badness = 3;
|
|
|
|
|
$cur_reason = 'Ref';
|
|
|
|
|
break;
|
2012-04-20 10:53:55 -07:00
|
|
|
case 'fulltext':
|
|
|
|
|
$cur_badness = 3;
|
|
|
|
|
$cur_reason = 'Fulltext';
|
|
|
|
|
break;
|
2011-07-09 09:45:19 -07:00
|
|
|
case 'ALL':
|
|
|
|
|
if (preg_match('/Using where/', $table['Extra'])) {
|
|
|
|
|
if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
|
|
|
|
|
$cur_badness = 2;
|
|
|
|
|
$cur_reason = 'Small Table Scan';
|
|
|
|
|
} else {
|
|
|
|
|
$cur_badness = 6;
|
|
|
|
|
$cur_reason = 'TABLE SCAN!';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$cur_badness = 3;
|
|
|
|
|
$cur_reason = 'Whole Table';
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (preg_match('/No tables used/i', $table['Extra'])) {
|
|
|
|
|
$cur_badness = 1;
|
|
|
|
|
$cur_reason = 'No Tables';
|
|
|
|
|
} else if (preg_match('/Impossible/i', $table['Extra'])) {
|
|
|
|
|
$cur_badness = 1;
|
|
|
|
|
$cur_reason = 'Empty';
|
|
|
|
|
} else {
|
|
|
|
|
$cur_badness = 4;
|
|
|
|
|
$cur_reason = "Can't Analyze";
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($cur_badness > $badness) {
|
|
|
|
|
$badness = $cur_badness;
|
|
|
|
|
$reason = $cur_reason;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$log[$key]['explain'] = array(
|
|
|
|
|
'sev' => $badness,
|
|
|
|
|
'size' => $size,
|
|
|
|
|
'reason' => $reason,
|
|
|
|
|
);
|
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
|
$log[$key]['explain'] = array(
|
|
|
|
|
'sev' => 5,
|
|
|
|
|
'size' => null,
|
|
|
|
|
'reason' => $ex->getMessage(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-20 21:15:00 -07:00
|
|
|
return array(
|
2012-12-25 06:11:39 -08:00
|
|
|
'start' => PhabricatorStartup::getStartTime(),
|
2011-07-09 09:45:19 -07:00
|
|
|
'end' => microtime(true),
|
|
|
|
|
'log' => $log,
|
2011-05-20 21:15:00 -07:00
|
|
|
);
|
2011-02-02 13:48:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function render() {
|
2011-02-02 22:38:42 -08:00
|
|
|
$data = $this->getData();
|
2011-05-20 21:15:00 -07:00
|
|
|
$log = $data['log'];
|
2011-07-09 09:45:19 -07:00
|
|
|
$results = array();
|
|
|
|
|
|
|
|
|
|
$results[] =
|
|
|
|
|
'<div class="dark-console-panel-header">'.
|
|
|
|
|
phutil_render_tag(
|
|
|
|
|
'a',
|
|
|
|
|
array(
|
|
|
|
|
'href' => $this->getRequestURI()->alter('__analyze__', true),
|
|
|
|
|
'class' => isset($_REQUEST['__analyze__'])
|
|
|
|
|
? 'disabled button'
|
|
|
|
|
: 'green button',
|
|
|
|
|
),
|
|
|
|
|
'Analyze Query Plans').
|
|
|
|
|
'<h1>Calls to External Services</h1>'.
|
|
|
|
|
'<div style="clear: both;"></div>'.
|
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
|
|
$page_total = $data['end'] - $data['start'];
|
|
|
|
|
$totals = array();
|
|
|
|
|
$counts = array();
|
|
|
|
|
|
|
|
|
|
foreach ($log as $row) {
|
2011-07-12 09:41:34 -07:00
|
|
|
$totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration'];
|
|
|
|
|
$counts[$row['type']] = idx($counts, $row['type'], 0) + 1;
|
2011-07-09 09:45:19 -07:00
|
|
|
}
|
2012-01-04 07:08:37 -08:00
|
|
|
$totals['All Services'] = array_sum($totals);
|
|
|
|
|
$counts['All Services'] = array_sum($counts);
|
|
|
|
|
|
|
|
|
|
$totals['Entire Page'] = $page_total;
|
2012-01-05 09:09:28 -08:00
|
|
|
$counts['Entire Page'] = 0;
|
2011-07-09 09:45:19 -07:00
|
|
|
|
|
|
|
|
$summary = array();
|
|
|
|
|
foreach ($totals as $type => $total) {
|
|
|
|
|
$summary[] = array(
|
|
|
|
|
$type,
|
|
|
|
|
number_format($counts[$type]),
|
|
|
|
|
number_format((int)(1000000 * $totals[$type])).' us',
|
|
|
|
|
sprintf('%.1f%%', 100 * $totals[$type] / $page_total),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
$summary_table = new AphrontTableView($summary);
|
|
|
|
|
$summary_table->setColumnClasses(
|
|
|
|
|
array(
|
|
|
|
|
'',
|
|
|
|
|
'n',
|
|
|
|
|
'n',
|
|
|
|
|
'wide',
|
|
|
|
|
));
|
|
|
|
|
$summary_table->setHeaders(
|
|
|
|
|
array(
|
|
|
|
|
'Type',
|
|
|
|
|
'Count',
|
|
|
|
|
'Total Cost',
|
|
|
|
|
'Page Weight',
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$results[] = $summary_table->render();
|
2011-02-02 22:38:42 -08:00
|
|
|
|
|
|
|
|
$rows = array();
|
2011-05-20 21:15:00 -07:00
|
|
|
foreach ($log as $row) {
|
2011-02-02 22:38:42 -08:00
|
|
|
|
2011-07-09 09:45:19 -07:00
|
|
|
$analysis = null;
|
|
|
|
|
|
2011-05-16 16:18:33 -07:00
|
|
|
switch ($row['type']) {
|
|
|
|
|
case 'query':
|
2011-02-02 22:38:42 -08:00
|
|
|
$info = $row['query'];
|
2011-07-09 09:45:19 -07:00
|
|
|
$info = wordwrap($info, 128, "\n", true);
|
|
|
|
|
|
|
|
|
|
if (!empty($row['explain'])) {
|
|
|
|
|
$analysis = phutil_escape_html($row['explain']['reason']);
|
|
|
|
|
$analysis = phutil_render_tag(
|
|
|
|
|
'span',
|
|
|
|
|
array(
|
|
|
|
|
'class' => 'explain-sev-'.$row['explain']['sev'],
|
|
|
|
|
),
|
|
|
|
|
$analysis);
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-02 22:38:42 -08:00
|
|
|
$info = phutil_escape_html($info);
|
|
|
|
|
break;
|
2011-05-16 16:18:33 -07:00
|
|
|
case 'connect':
|
2011-02-02 22:38:42 -08:00
|
|
|
$info = $row['host'].':'.$row['database'];
|
|
|
|
|
$info = phutil_escape_html($info);
|
2011-05-16 16:18:33 -07:00
|
|
|
break;
|
|
|
|
|
case 'exec':
|
|
|
|
|
$info = $row['command'];
|
|
|
|
|
$info = phutil_escape_html($info);
|
|
|
|
|
break;
|
|
|
|
|
case 'conduit':
|
|
|
|
|
$info = $row['method'];
|
|
|
|
|
$info = phutil_escape_html($info);
|
2011-02-02 22:38:42 -08:00
|
|
|
break;
|
2012-05-11 17:36:27 -07:00
|
|
|
case 'http':
|
|
|
|
|
$info = $row['uri'];
|
|
|
|
|
$info = phutil_escape_html($info);
|
|
|
|
|
break;
|
2011-02-02 22:38:42 -08:00
|
|
|
default:
|
|
|
|
|
$info = '-';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rows[] = array(
|
2011-05-16 16:18:33 -07:00
|
|
|
phutil_escape_html($row['type']),
|
2011-05-20 21:15:00 -07:00
|
|
|
'+'.number_format(1000 * ($row['begin'] - $data['start'])).' ms',
|
2011-05-16 16:18:33 -07:00
|
|
|
number_format(1000000 * $row['duration']).' us',
|
2011-02-02 22:38:42 -08:00
|
|
|
$info,
|
2011-07-09 09:45:19 -07:00
|
|
|
$analysis,
|
2011-02-02 22:38:42 -08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$table = new AphrontTableView($rows);
|
|
|
|
|
$table->setColumnClasses(
|
|
|
|
|
array(
|
|
|
|
|
null,
|
|
|
|
|
'n',
|
2011-05-20 21:15:00 -07:00
|
|
|
'n',
|
2011-07-09 09:45:19 -07:00
|
|
|
'wide',
|
|
|
|
|
'',
|
2011-02-02 22:38:42 -08:00
|
|
|
));
|
|
|
|
|
$table->setHeaders(
|
|
|
|
|
array(
|
|
|
|
|
'Event',
|
2011-05-20 21:15:00 -07:00
|
|
|
'Start',
|
2011-02-02 22:38:42 -08:00
|
|
|
'Duration',
|
|
|
|
|
'Details',
|
2011-07-09 09:45:19 -07:00
|
|
|
'Analysis',
|
2011-02-02 22:38:42 -08:00
|
|
|
));
|
|
|
|
|
|
2011-07-09 09:45:19 -07:00
|
|
|
$results[] = $table->render();
|
|
|
|
|
|
|
|
|
|
return implode("\n", $results);
|
2011-02-02 13:48:52 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|