3 final class DarkConsoleServicesPlugin
extends DarkConsolePlugin
{
5 protected $observations;
7 public function getName() {
8 return pht('Services');
11 public function getDescription() {
12 return pht('Information about services.');
15 public static function getQueryAnalyzerHeader() {
16 return 'X-Phabricator-QueryAnalyzer';
19 public static function isQueryAnalyzerRequested() {
20 if (!empty($_REQUEST['__analyze__'])) {
24 $header = AphrontRequest
::getHTTPHeader(self
::getQueryAnalyzerHeader());
32 public function didStartup() {
33 $should_analyze = self
::isQueryAnalyzerRequested();
35 if ($should_analyze) {
36 PhutilServiceProfiler
::getInstance()
37 ->setCollectStackTraces(true);
45 * @phutil-external-symbol class PhabricatorStartup
47 public function generateData() {
48 $should_analyze = self
::isQueryAnalyzerRequested();
50 $log = PhutilServiceProfiler
::getInstance()->getServiceCallLog();
51 foreach ($log as $key => $entry) {
52 $config = idx($entry, 'config', array());
53 unset($log[$key]['config']);
55 if (!$should_analyze) {
56 $log[$key]['explain'] = array(
59 'reason' => pht('Disabled'),
61 // Query analysis is disabled for this request, so don't do any of it.
65 if ($entry['type'] != 'query') {
69 // For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
70 // causing table scans, etc.
71 if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {
72 $conn = PhabricatorDatabaseRef
::newRawConnection($entry['config']);
74 $explain = queryfx_all(
83 foreach ($explain as $table) {
84 $size *= (int)$table['rows'];
86 switch ($table['type']) {
89 $cur_reason = 'Index';
93 $cur_reason = 'Const';
97 $cur_reason = 'EqRef';
101 $cur_reason = 'Range';
109 $cur_reason = 'Fulltext';
112 if (preg_match('/Using where/', $table['Extra'])) {
113 if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
115 $cur_reason = pht('Small Table Scan');
118 $cur_reason = pht('TABLE SCAN!');
122 $cur_reason = pht('Whole Table');
126 if (preg_match('/No tables used/i', $table['Extra'])) {
128 $cur_reason = pht('No Tables');
129 } else if (preg_match('/Impossible/i', $table['Extra'])) {
131 $cur_reason = pht('Empty');
134 $cur_reason = pht("Can't Analyze");
139 if ($cur_badness > $badness) {
140 $badness = $cur_badness;
141 $reason = $cur_reason;
145 $log[$key]['explain'] = array(
150 } catch (Exception
$ex) {
151 $log[$key]['explain'] = array(
154 'reason' => $ex->getMessage(),
161 'start' => PhabricatorStartup
::getStartTime(),
162 'end' => microtime(true),
164 'analyzeURI' => (string)$this
166 ->alter('__analyze__', true),
167 'didAnalyze' => $should_analyze,
171 public function renderPanel() {
172 $data = $this->getData();
177 $results[] = phutil_tag(
179 array('class' => 'dark-console-panel-header'),
184 'href' => $data['analyzeURI'],
185 'class' => $data['didAnalyze'] ?
186 'disabled button' : 'button button-green',
188 pht('Analyze Query Plans')),
189 phutil_tag('h1', array(), pht('Calls to External Services')),
190 phutil_tag('div', array('style' => 'clear: both;')),
193 $page_total = $data['end'] - $data['start'];
197 foreach ($log as $row) {
198 $totals[$row['type']] = idx($totals, $row['type'], 0) +
$row['duration'];
199 $counts[$row['type']] = idx($counts, $row['type'], 0) +
1;
201 $totals['All Services'] = array_sum($totals);
202 $counts['All Services'] = array_sum($counts);
204 $totals['Entire Page'] = $page_total;
205 $counts['Entire Page'] = 0;
208 foreach ($totals as $type => $total) {
211 number_format($counts[$type]),
212 pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))),
213 sprintf('%.1f%%', 100 * $totals[$type] / $page_total),
216 $summary_table = new AphrontTableView($summary);
217 $summary_table->setColumnClasses(
224 $summary_table->setHeaders(
232 $results[] = $summary_table->render();
235 foreach ($log as $row) {
239 switch ($row['type']) {
241 $info = $row['query'];
242 $info = wordwrap($info, 128, "\n", true);
244 if (!empty($row['explain'])) {
245 $analysis = phutil_tag(
248 'class' => 'explain-sev-'.$row['explain']['sev'],
250 $row['explain']['reason']);
255 $info = $row['host'].':'.$row['database'];
258 $info = $row['command'];
262 $info = $row['method'];
272 $offset = ($row['begin'] - $data['start']);
276 pht('+%s ms', new PhutilNumber(1000 * $offset)),
277 pht('%s us', new PhutilNumber(1000000 * $row['duration'])),
282 if (isset($row['trace'])) {
293 $table = new AphrontTableView($rows);
294 $table->setColumnClasses(
311 $results[] = $table->render();
313 return phutil_implode_html("\n", $results);