[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/console/plugin/ -> DarkConsoleServicesPlugin.php (source)

   1  <?php
   2  
   3  final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
   4  
   5    protected $observations;
   6  
   7    public function getName() {
   8      return 'Services';
   9    }
  10  
  11    public function getDescription() {
  12      return 'Information about services.';
  13    }
  14  
  15    public static function getQueryAnalyzerHeader() {
  16      return 'X-Phabricator-QueryAnalyzer';
  17    }
  18  
  19    public static function isQueryAnalyzerRequested() {
  20      if (!empty($_REQUEST['__analyze__'])) {
  21        return true;
  22      }
  23  
  24      $header = AphrontRequest::getHTTPHeader(self::getQueryAnalyzerHeader());
  25      if ($header) {
  26        return true;
  27      }
  28  
  29      return false;
  30    }
  31  
  32    /**
  33     * @phutil-external-symbol class PhabricatorStartup
  34     */
  35    public function generateData() {
  36      $should_analyze = self::isQueryAnalyzerRequested();
  37  
  38      $log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
  39      foreach ($log as $key => $entry) {
  40        $config = idx($entry, 'config', array());
  41        unset($log[$key]['config']);
  42  
  43        if (!$should_analyze) {
  44          $log[$key]['explain'] = array(
  45            'sev'     => 7,
  46            'size'    => null,
  47            'reason'  => 'Disabled',
  48          );
  49          // Query analysis is disabled for this request, so don't do any of it.
  50          continue;
  51        }
  52  
  53        if ($entry['type'] != 'query') {
  54          continue;
  55        }
  56  
  57        // For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
  58        // causing table scans, etc.
  59        if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {
  60          $conn = PhabricatorEnv::newObjectFromConfig(
  61            'mysql.implementation',
  62            array($entry['config']));
  63          try {
  64            $explain = queryfx_all(
  65              $conn,
  66              'EXPLAIN %Q',
  67              $entry['query']);
  68  
  69            $badness = 0;
  70            $size    = 1;
  71            $reason  = null;
  72  
  73            foreach ($explain as $table) {
  74              $size *= (int)$table['rows'];
  75  
  76              switch ($table['type']) {
  77                case 'index':
  78                  $cur_badness = 1;
  79                  $cur_reason  = 'Index';
  80                  break;
  81                case 'const':
  82                  $cur_badness = 1;
  83                  $cur_reason = 'Const';
  84                  break;
  85                case 'eq_ref';
  86                  $cur_badness = 2;
  87                  $cur_reason = 'EqRef';
  88                  break;
  89                case 'range':
  90                  $cur_badness = 3;
  91                  $cur_reason = 'Range';
  92                  break;
  93                case 'ref':
  94                  $cur_badness = 3;
  95                  $cur_reason = 'Ref';
  96                  break;
  97                case 'fulltext':
  98                  $cur_badness = 3;
  99                  $cur_reason = 'Fulltext';
 100                  break;
 101                case 'ALL':
 102                  if (preg_match('/Using where/', $table['Extra'])) {
 103                    if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
 104                      $cur_badness = 2;
 105                      $cur_reason = 'Small Table Scan';
 106                    } else {
 107                      $cur_badness = 6;
 108                      $cur_reason = 'TABLE SCAN!';
 109                    }
 110                  } else {
 111                    $cur_badness = 3;
 112                    $cur_reason = 'Whole Table';
 113                  }
 114                  break;
 115                default:
 116                  if (preg_match('/No tables used/i', $table['Extra'])) {
 117                    $cur_badness = 1;
 118                    $cur_reason = 'No Tables';
 119                  } else if (preg_match('/Impossible/i', $table['Extra'])) {
 120                    $cur_badness = 1;
 121                    $cur_reason = 'Empty';
 122                  } else {
 123                    $cur_badness = 4;
 124                    $cur_reason = "Can't Analyze";
 125                  }
 126                  break;
 127              }
 128  
 129              if ($cur_badness > $badness) {
 130                $badness = $cur_badness;
 131                $reason = $cur_reason;
 132              }
 133            }
 134  
 135            $log[$key]['explain'] = array(
 136              'sev'     => $badness,
 137              'size'    => $size,
 138              'reason'  => $reason,
 139            );
 140          } catch (Exception $ex) {
 141            $log[$key]['explain'] = array(
 142              'sev'     => 5,
 143              'size'    => null,
 144              'reason'  => $ex->getMessage(),
 145            );
 146          }
 147        }
 148      }
 149  
 150      return array(
 151        'start' => PhabricatorStartup::getStartTime(),
 152        'end'   => microtime(true),
 153        'log'   => $log,
 154        'analyzeURI' => (string)$this
 155          ->getRequestURI()
 156          ->alter('__analyze__', true),
 157        'didAnalyze' => $should_analyze,
 158      );
 159    }
 160  
 161    public function renderPanel() {
 162      $data = $this->getData();
 163  
 164      $log = $data['log'];
 165      $results = array();
 166  
 167      $results[] = phutil_tag(
 168        'div',
 169        array('class' => 'dark-console-panel-header'),
 170        array(
 171          phutil_tag(
 172            'a',
 173            array(
 174              'href'  => $data['analyzeURI'],
 175              'class' => $data['didAnalyze'] ? 'disabled button' : 'green button',
 176            ),
 177            pht('Analyze Query Plans')),
 178          phutil_tag('h1', array(), pht('Calls to External Services')),
 179          phutil_tag('div', array('style' => 'clear: both;')),
 180        ));
 181  
 182      $page_total = $data['end'] - $data['start'];
 183      $totals = array();
 184      $counts = array();
 185  
 186      foreach ($log as $row) {
 187        $totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration'];
 188        $counts[$row['type']] = idx($counts, $row['type'], 0) + 1;
 189      }
 190      $totals['All Services'] = array_sum($totals);
 191      $counts['All Services'] = array_sum($counts);
 192  
 193      $totals['Entire Page'] = $page_total;
 194      $counts['Entire Page'] = 0;
 195  
 196      $summary = array();
 197      foreach ($totals as $type => $total) {
 198        $summary[] = array(
 199          $type,
 200          number_format($counts[$type]),
 201          number_format((int)(1000000 * $totals[$type])).' us',
 202          sprintf('%.1f%%', 100 * $totals[$type] / $page_total),
 203        );
 204      }
 205      $summary_table = new AphrontTableView($summary);
 206      $summary_table->setColumnClasses(
 207        array(
 208          '',
 209          'n',
 210          'n',
 211          'wide',
 212        ));
 213      $summary_table->setHeaders(
 214        array(
 215          'Type',
 216          'Count',
 217          'Total Cost',
 218          'Page Weight',
 219        ));
 220  
 221      $results[] = $summary_table->render();
 222  
 223      $rows = array();
 224      foreach ($log as $row) {
 225  
 226        $analysis = null;
 227  
 228        switch ($row['type']) {
 229          case 'query':
 230            $info = $row['query'];
 231            $info = wordwrap($info, 128, "\n", true);
 232  
 233            if (!empty($row['explain'])) {
 234              $analysis = phutil_tag(
 235                'span',
 236                array(
 237                  'class' => 'explain-sev-'.$row['explain']['sev'],
 238                ),
 239                $row['explain']['reason']);
 240            }
 241  
 242            break;
 243          case 'connect':
 244            $info = $row['host'].':'.$row['database'];
 245            break;
 246          case 'exec':
 247            $info = $row['command'];
 248            break;
 249          case 's3':
 250          case 'conduit':
 251            $info = $row['method'];
 252            break;
 253          case 'http':
 254            $info = $row['uri'];
 255            break;
 256          default:
 257            $info = '-';
 258            break;
 259        }
 260  
 261        $rows[] = array(
 262          $row['type'],
 263          '+'.number_format(1000 * ($row['begin'] - $data['start'])).' ms',
 264          number_format(1000000 * $row['duration']).' us',
 265          $info,
 266          $analysis,
 267        );
 268      }
 269  
 270      $table = new AphrontTableView($rows);
 271      $table->setColumnClasses(
 272        array(
 273          null,
 274          'n',
 275          'n',
 276          'wide',
 277          '',
 278        ));
 279      $table->setHeaders(
 280        array(
 281          'Event',
 282          'Start',
 283          'Duration',
 284          'Details',
 285          'Analysis',
 286        ));
 287  
 288      $results[] = $table->render();
 289  
 290      return phutil_implode_html("\n", $results);
 291    }
 292  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1