Path: blob/master/src/applications/console/plugin/DarkConsoleServicesPlugin.php
13402 views
<?php12final class DarkConsoleServicesPlugin extends DarkConsolePlugin {34protected $observations;56public function getName() {7return pht('Services');8}910public function getDescription() {11return pht('Information about services.');12}1314public static function getQueryAnalyzerHeader() {15return 'X-Phabricator-QueryAnalyzer';16}1718public static function isQueryAnalyzerRequested() {19if (!empty($_REQUEST['__analyze__'])) {20return true;21}2223$header = AphrontRequest::getHTTPHeader(self::getQueryAnalyzerHeader());24if ($header) {25return true;26}2728return false;29}3031public function didStartup() {32$should_analyze = self::isQueryAnalyzerRequested();3334if ($should_analyze) {35PhutilServiceProfiler::getInstance()36->setCollectStackTraces(true);37}3839return null;40}414243/**44* @phutil-external-symbol class PhabricatorStartup45*/46public function generateData() {47$should_analyze = self::isQueryAnalyzerRequested();4849$log = PhutilServiceProfiler::getInstance()->getServiceCallLog();50foreach ($log as $key => $entry) {51$config = idx($entry, 'config', array());52unset($log[$key]['config']);5354if (!$should_analyze) {55$log[$key]['explain'] = array(56'sev' => 7,57'size' => null,58'reason' => pht('Disabled'),59);60// Query analysis is disabled for this request, so don't do any of it.61continue;62}6364if ($entry['type'] != 'query') {65continue;66}6768// For each SELECT query, go issue an EXPLAIN on it so we can flag stuff69// causing table scans, etc.70if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {71$conn = PhabricatorDatabaseRef::newRawConnection($entry['config']);72try {73$explain = queryfx_all(74$conn,75'EXPLAIN %Q',76$entry['query']);7778$badness = 0;79$size = 1;80$reason = null;8182foreach ($explain as $table) {83$size *= (int)$table['rows'];8485switch ($table['type']) {86case 'index':87$cur_badness = 1;88$cur_reason = 'Index';89break;90case 'const':91$cur_badness = 1;92$cur_reason = 'Const';93break;94case 'eq_ref';95$cur_badness = 2;96$cur_reason = 'EqRef';97break;98case 'range':99$cur_badness = 3;100$cur_reason = 'Range';101break;102case 'ref':103$cur_badness = 3;104$cur_reason = 'Ref';105break;106case 'fulltext':107$cur_badness = 3;108$cur_reason = 'Fulltext';109break;110case 'ALL':111if (preg_match('/Using where/', $table['Extra'])) {112if ($table['rows'] < 256 && !empty($table['possible_keys'])) {113$cur_badness = 2;114$cur_reason = pht('Small Table Scan');115} else {116$cur_badness = 6;117$cur_reason = pht('TABLE SCAN!');118}119} else {120$cur_badness = 3;121$cur_reason = pht('Whole Table');122}123break;124default:125if (preg_match('/No tables used/i', $table['Extra'])) {126$cur_badness = 1;127$cur_reason = pht('No Tables');128} else if (preg_match('/Impossible/i', $table['Extra'])) {129$cur_badness = 1;130$cur_reason = pht('Empty');131} else {132$cur_badness = 4;133$cur_reason = pht("Can't Analyze");134}135break;136}137138if ($cur_badness > $badness) {139$badness = $cur_badness;140$reason = $cur_reason;141}142}143144$log[$key]['explain'] = array(145'sev' => $badness,146'size' => $size,147'reason' => $reason,148);149} catch (Exception $ex) {150$log[$key]['explain'] = array(151'sev' => 5,152'size' => null,153'reason' => $ex->getMessage(),154);155}156}157}158159return array(160'start' => PhabricatorStartup::getStartTime(),161'end' => microtime(true),162'log' => $log,163'analyzeURI' => (string)$this164->getRequestURI()165->alter('__analyze__', true),166'didAnalyze' => $should_analyze,167);168}169170public function renderPanel() {171$data = $this->getData();172173$log = $data['log'];174$results = array();175176$results[] = phutil_tag(177'div',178array('class' => 'dark-console-panel-header'),179array(180phutil_tag(181'a',182array(183'href' => $data['analyzeURI'],184'class' => $data['didAnalyze'] ?185'disabled button' : 'button button-green',186),187pht('Analyze Query Plans')),188phutil_tag('h1', array(), pht('Calls to External Services')),189phutil_tag('div', array('style' => 'clear: both;')),190));191192$page_total = $data['end'] - $data['start'];193$totals = array();194$counts = array();195196foreach ($log as $row) {197$totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration'];198$counts[$row['type']] = idx($counts, $row['type'], 0) + 1;199}200$totals['All Services'] = array_sum($totals);201$counts['All Services'] = array_sum($counts);202203$totals['Entire Page'] = $page_total;204$counts['Entire Page'] = 0;205206$summary = array();207foreach ($totals as $type => $total) {208$summary[] = array(209$type,210number_format($counts[$type]),211pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))),212sprintf('%.1f%%', 100 * $totals[$type] / $page_total),213);214}215$summary_table = new AphrontTableView($summary);216$summary_table->setColumnClasses(217array(218'',219'n',220'n',221'wide',222));223$summary_table->setHeaders(224array(225pht('Type'),226pht('Count'),227pht('Total Cost'),228pht('Page Weight'),229));230231$results[] = $summary_table->render();232233$rows = array();234foreach ($log as $row) {235236$analysis = null;237238switch ($row['type']) {239case 'query':240$info = $row['query'];241$info = wordwrap($info, 128, "\n", true);242243if (!empty($row['explain'])) {244$analysis = phutil_tag(245'span',246array(247'class' => 'explain-sev-'.$row['explain']['sev'],248),249$row['explain']['reason']);250}251252break;253case 'connect':254$info = $row['host'].':'.$row['database'];255break;256case 'exec':257$info = $row['command'];258break;259case 's3':260case 'conduit':261$info = $row['method'];262break;263case 'http':264$info = $row['uri'];265break;266default:267$info = '-';268break;269}270271$offset = ($row['begin'] - $data['start']);272273$rows[] = array(274$row['type'],275pht('+%s ms', new PhutilNumber(1000 * $offset)),276pht('%s us', new PhutilNumber(1000000 * $row['duration'])),277$info,278$analysis,279);280281if (isset($row['trace'])) {282$rows[] = array(283null,284null,285null,286$row['trace'],287null,288);289}290}291292$table = new AphrontTableView($rows);293$table->setColumnClasses(294array(295null,296'n',297'n',298'wide prewrap',299'',300));301$table->setHeaders(302array(303pht('Event'),304pht('Start'),305pht('Duration'),306pht('Details'),307pht('Analysis'),308));309310$results[] = $table->render();311312return phutil_implode_html("\n", $results);313}314}315316317