diff --git a/src/applications/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php index a14ed4b541..4a26665e0a 100644 --- a/src/applications/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/applications/console/plugin/DarkConsoleServicesPlugin.php @@ -1,315 +1,315 @@ setCollectStackTraces(true); } return null; } /** * @phutil-external-symbol class PhabricatorStartup */ public function generateData() { $should_analyze = self::isQueryAnalyzerRequested(); $log = PhutilServiceProfiler::getInstance()->getServiceCallLog(); foreach ($log as $key => $entry) { $config = idx($entry, 'config', array()); unset($log[$key]['config']); if (!$should_analyze) { $log[$key]['explain'] = array( 'sev' => 7, 'size' => null, 'reason' => pht('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'])) { $conn = PhabricatorDatabaseRef::newRawConnection($entry['config']); 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; case 'fulltext': $cur_badness = 3; $cur_reason = 'Fulltext'; break; case 'ALL': if (preg_match('/Using where/', $table['Extra'])) { if ($table['rows'] < 256 && !empty($table['possible_keys'])) { $cur_badness = 2; $cur_reason = pht('Small Table Scan'); } else { $cur_badness = 6; $cur_reason = pht('TABLE SCAN!'); } } else { $cur_badness = 3; $cur_reason = pht('Whole Table'); } break; default: if (preg_match('/No tables used/i', $table['Extra'])) { $cur_badness = 1; $cur_reason = pht('No Tables'); } else if (preg_match('/Impossible/i', $table['Extra'])) { $cur_badness = 1; $cur_reason = pht('Empty'); } else { $cur_badness = 4; $cur_reason = pht("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(), ); } } } return array( 'start' => PhabricatorStartup::getStartTime(), 'end' => microtime(true), 'log' => $log, 'analyzeURI' => (string)$this ->getRequestURI() ->alter('__analyze__', true), 'didAnalyze' => $should_analyze, ); } public function renderPanel() { $data = $this->getData(); $log = $data['log']; $results = array(); $results[] = phutil_tag( 'div', array('class' => 'dark-console-panel-header'), array( phutil_tag( 'a', array( 'href' => $data['analyzeURI'], 'class' => $data['didAnalyze'] ? 'disabled button' : 'button button-green', ), pht('Analyze Query Plans')), phutil_tag('h1', array(), pht('Calls to External Services')), phutil_tag('div', array('style' => 'clear: both;')), )); $page_total = $data['end'] - $data['start']; $totals = array(); $counts = array(); foreach ($log as $row) { $totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration']; $counts[$row['type']] = idx($counts, $row['type'], 0) + 1; } $totals['All Services'] = array_sum($totals); $counts['All Services'] = array_sum($counts); $totals['Entire Page'] = $page_total; $counts['Entire Page'] = 0; $summary = array(); foreach ($totals as $type => $total) { $summary[] = array( $type, number_format($counts[$type]), pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))), sprintf('%.1f%%', 100 * $totals[$type] / $page_total), ); } $summary_table = new AphrontTableView($summary); $summary_table->setColumnClasses( array( '', 'n', 'n', 'wide', )); $summary_table->setHeaders( array( pht('Type'), pht('Count'), pht('Total Cost'), pht('Page Weight'), )); $results[] = $summary_table->render(); $rows = array(); foreach ($log as $row) { $analysis = null; switch ($row['type']) { case 'query': $info = $row['query']; $info = wordwrap($info, 128, "\n", true); if (!empty($row['explain'])) { $analysis = phutil_tag( 'span', array( 'class' => 'explain-sev-'.$row['explain']['sev'], ), $row['explain']['reason']); } break; case 'connect': $info = $row['host'].':'.$row['database']; break; case 'exec': $info = $row['command']; break; case 's3': case 'conduit': $info = $row['method']; break; case 'http': $info = $row['uri']; break; default: $info = '-'; break; } $offset = ($row['begin'] - $data['start']); $rows[] = array( $row['type'], pht('+%s ms', new PhutilNumber(1000 * $offset)), pht('%s us', new PhutilNumber(1000000 * $row['duration'])), $info, $analysis, ); - if ($row['trace']) { + if (isset($row['trace'])) { $rows[] = array( null, null, null, $row['trace'], null, ); } } $table = new AphrontTableView($rows); $table->setColumnClasses( array( null, 'n', 'n', 'wide prewrap', '', )); $table->setHeaders( array( pht('Event'), pht('Start'), pht('Duration'), pht('Details'), pht('Analysis'), )); $results[] = $table->render(); return phutil_implode_html("\n", $results); } } diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php index a7d9570c57..fa03bcafdd 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -1,127 +1,127 @@ setAncestorClass('PhabricatorFerretInterface') ->execute(); $type_map = array(); foreach ($all_objects as $object) { $phid_type = phid_get_type($object->generatePHID()); $type_map[$phid_type] = array( 'object' => $object, 'engine' => $object->newFerretEngine(), ); } $types = $query->getParameter('types'); if ($types) { $type_map = array_select_keys($type_map, $types); } $offset = (int)$query->getParameter('offset', 0); $limit = (int)$query->getParameter('limit', 25); // NOTE: For now, it's okay to query with the omnipotent viewer here // because we're just returning PHIDs which we'll filter later. $viewer = PhabricatorUser::getOmnipotentUser(); $type_results = array(); $metadata = array(); foreach ($type_map as $type => $spec) { $engine = $spec['engine']; $object = $spec['object']; $local_query = new PhabricatorSavedQuery(); $local_query->setParameter('query', $query->getParameter('query')); $project_phids = $query->getParameter('projectPHIDs'); if ($project_phids) { $local_query->setParameter('projectPHIDs', $project_phids); } $subscriber_phids = $query->getParameter('subscriberPHIDs'); if ($subscriber_phids) { $local_query->setParameter('subscriberPHIDs', $subscriber_phids); } $search_engine = $engine->newSearchEngine() ->setViewer($viewer); $engine_query = $search_engine->buildQueryFromSavedQuery($local_query) ->setViewer($viewer); $engine_query ->withFerretQuery($engine, $query) ->setOrder('relevance') ->setLimit($offset + $limit); $results = $engine_query->execute(); $results = mpull($results, null, 'getPHID'); $type_results[$type] = $results; $metadata += $engine_query->getFerretMetadata(); if (!$this->fulltextTokens) { $this->fulltextTokens = $engine_query->getFerretTokens(); } } $list = array(); foreach ($type_results as $type => $results) { $list += $results; } // Currently, the list is grouped by object type. For example, all the // tasks might be first, then all the revisions, and so on. In each group, // the results are ordered properly. // Reorder the results so that the highest-ranking results come first, // no matter which object types they belong to. - $metadata = msort($metadata, 'getRelevanceSortVector'); + $metadata = msortv($metadata, 'getRelevanceSortVector'); $list = array_select_keys($list, array_keys($metadata)) + $list; $result_slice = array_slice($list, $offset, $limit, true); return array_keys($result_slice); } public function indexExists() { return true; } public function getIndexStats() { return false; } public function getFulltextTokens() { return $this->fulltextTokens; } }