diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
index 48e6ef848e..54800c50f8 100644
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -27,7 +27,7 @@ celerity_register_resource_map(array(
),
'aphront-dark-console-css' =>
array(
- 'uri' => '/res/e7011594/rsrc/css/aphront/dark-console.css',
+ 'uri' => '/res/a7d1dbf1/rsrc/css/aphront/dark-console.css',
'type' => 'css',
'requires' =>
array(
diff --git a/src/aphront/console/plugin/base/DarkConsolePlugin.php b/src/aphront/console/plugin/base/DarkConsolePlugin.php
index 7350979925..5fe185d105 100644
--- a/src/aphront/console/plugin/base/DarkConsolePlugin.php
+++ b/src/aphront/console/plugin/base/DarkConsolePlugin.php
@@ -61,6 +61,10 @@ abstract class DarkConsolePlugin {
return $this->request;
}
+ public function getRequestURI() {
+ return $this->getRequest()->getRequestURI();
+ }
+
public function isPermanent() {
return false;
}
diff --git a/src/aphront/console/plugin/services/DarkConsoleServicesPlugin.php b/src/aphront/console/plugin/services/DarkConsoleServicesPlugin.php
index dc053dfa6e..5cda555c6f 100644
--- a/src/aphront/console/plugin/services/DarkConsoleServicesPlugin.php
+++ b/src/aphront/console/plugin/services/DarkConsoleServicesPlugin.php
@@ -30,22 +30,198 @@ class DarkConsoleServicesPlugin extends DarkConsolePlugin {
public function generateData() {
+ $log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
+ foreach ($log as $key => $entry) {
+ $config = $entry['config'];
+ 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'])) {
+ $conn = new AphrontMySQLDatabaseConnection($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 '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(),
+ );
+ }
+ }
+ }
+
return array(
'start' => $GLOBALS['__start__'],
- 'log' => PhutilServiceProfiler::getInstance()->getServiceCallLog(),
+ 'end' => microtime(true),
+ 'log' => $log,
);
}
public function render() {
$data = $this->getData();
$log = $data['log'];
+ $results = array();
+
+ $results[] =
+ '
';
+
+ $page_total = $data['end'] - $data['start'];
+ $totals = array();
+ $counts = array();
+
+ foreach ($log as $row) {
+ $totals[$row['type']] += $row['duration'];
+ $counts[$row['type']]++;
+ }
+ $totals['All'] = array_sum($totals);
+ $counts['All'] = array_sum($counts);
+
+ $table = new AphrontTableView();
+ $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();
$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_escape_html($row['explain']['reason']);
+ $analysis = phutil_render_tag(
+ 'span',
+ array(
+ 'class' => 'explain-sev-'.$row['explain']['sev'],
+ ),
+ $analysis);
+ }
+
$info = phutil_escape_html($info);
break;
case 'connect':
@@ -70,6 +246,7 @@ class DarkConsoleServicesPlugin extends DarkConsolePlugin {
'+'.number_format(1000 * ($row['begin'] - $data['start'])).' ms',
number_format(1000000 * $row['duration']).' us',
$info,
+ $analysis,
);
}
@@ -79,7 +256,8 @@ class DarkConsoleServicesPlugin extends DarkConsolePlugin {
null,
'n',
'n',
- 'wide wrap',
+ 'wide',
+ '',
));
$table->setHeaders(
array(
@@ -87,9 +265,12 @@ class DarkConsoleServicesPlugin extends DarkConsolePlugin {
'Start',
'Duration',
'Details',
+ 'Analysis',
));
- return $table->render();
+ $results[] = $table->render();
+
+ return implode("\n", $results);
}
}
diff --git a/src/aphront/console/plugin/services/__init__.php b/src/aphront/console/plugin/services/__init__.php
index 00234db119..a89b976384 100644
--- a/src/aphront/console/plugin/services/__init__.php
+++ b/src/aphront/console/plugin/services/__init__.php
@@ -7,6 +7,8 @@
phutil_require_module('phabricator', 'aphront/console/plugin/base');
+phutil_require_module('phabricator', 'storage/connection/mysql');
+phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phutil', 'markup');
diff --git a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php
index c12181493b..31550e98a2 100644
--- a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php
+++ b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php
@@ -44,36 +44,58 @@ class DarkConsoleXHProfPlugin extends DarkConsolePlugin {
public function render() {
if (!DarkConsoleXHProfPluginAPI::isProfilerAvailable()) {
+ $href = PhabricatorEnv::getDoclink('article/Installation_Guide.html');
+ $install_guide = phutil_render_tag(
+ 'a',
+ array(
+ 'href' => $href,
+ 'class' => 'bright-link',
+ ),
+ 'Installation Guide');
return
- 'The "xhprof" PHP extension is not available. Install xhprof '.
- 'to enable the XHProf plugin.';
+ '
'.
+ 'The "xhprof" PHP extension is not available. Install xhprof '.
+ 'to enable the XHProf console plugin. You can find instructions in '.
+ 'the '.$install_guide.'.'.
+ '
';
}
+ $result = array();
+
$run = $this->getXHProfRunID();
- if ($run) {
- return 'View Run';
- } else {
- $hidden = array();
- $data = array('__profile__' => 'page') + $_GET;
- foreach ($data as $k => $v) {
- $hidden[] = phutil_render_tag(
- 'input',
+ $header =
+ '';
+ $result[] = $header;
-
- return
- '';
+ if ($run) {
+ $result[] =
+ 'Profile Permalink'.
+ '';
+ } else {
+ $result[] =
+ ''.
+ 'Profiling was not enabled for this page. Use the button above '.
+ 'to enable it.'.
+ '
';
}
+
+ return implode("\n", $result);
}
diff --git a/src/aphront/console/plugin/xhprof/__init__.php b/src/aphront/console/plugin/xhprof/__init__.php
index 32fd5d88d9..1e2cbd9cf3 100644
--- a/src/aphront/console/plugin/xhprof/__init__.php
+++ b/src/aphront/console/plugin/xhprof/__init__.php
@@ -8,6 +8,7 @@
phutil_require_module('phabricator', 'aphront/console/plugin/base');
phutil_require_module('phabricator', 'aphront/console/plugin/xhprof/api');
+phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'markup');
diff --git a/src/applications/files/controller/list/PhabricatorFileListController.php b/src/applications/files/controller/list/PhabricatorFileListController.php
index f78af9fd2c..85befebfcd 100644
--- a/src/applications/files/controller/list/PhabricatorFileListController.php
+++ b/src/applications/files/controller/list/PhabricatorFileListController.php
@@ -22,6 +22,7 @@ class PhabricatorFileListController extends PhabricatorFileController {
$request = $this->getRequest();
+ $author = null;
$author_username = $request->getStr('author');
if ($author_username) {
$author = id(new PhabricatorUser())->loadOneWhere(
diff --git a/src/applications/xhprof/controller/base/PhabricatorXHProfController.php b/src/applications/xhprof/controller/base/PhabricatorXHProfController.php
index fff91acb0d..b6e3b73d48 100644
--- a/src/applications/xhprof/controller/base/PhabricatorXHProfController.php
+++ b/src/applications/xhprof/controller/base/PhabricatorXHProfController.php
@@ -28,6 +28,14 @@ abstract class PhabricatorXHProfController extends PhabricatorController {
$page->appendChild($view);
$response = new AphrontWebpageResponse();
+
+ if (isset($data['frame'])) {
+ $response->setFrameable(true);
+ $page->setFrameable(true);
+ $page->setShowChrome(false);
+ $page->setDisableConsole(true);
+ }
+
return $response->setContent($page->render());
}
diff --git a/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php b/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php
index 1eca5b23be..cf0b954d7d 100644
--- a/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php
+++ b/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php
@@ -58,6 +58,7 @@ class PhabricatorXHProfProfileController
$view,
array(
'title' => 'Profile',
+ 'frame' => $request->getBool('frame'),
));
}
}
diff --git a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
index 93e120233e..4f5a21bc01 100644
--- a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
+++ b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
@@ -213,8 +213,9 @@ class AphrontMySQLDatabaseConnection extends AphrontDatabaseConnection {
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
- 'type' => 'query',
- 'query' => $raw_query,
+ 'type' => 'query',
+ 'config' => $this->configuration,
+ 'query' => $raw_query,
));
$result = @mysql_query($raw_query, $this->connection);
diff --git a/src/view/page/standard/PhabricatorStandardPageView.php b/src/view/page/standard/PhabricatorStandardPageView.php
index e5dec0e373..b5abe90c11 100644
--- a/src/view/page/standard/PhabricatorStandardPageView.php
+++ b/src/view/page/standard/PhabricatorStandardPageView.php
@@ -27,6 +27,8 @@ class PhabricatorStandardPageView extends AphrontPageView {
private $request;
private $isAdminInterface;
private $showChrome = true;
+ private $isFrameable = false;
+ private $disableConsole;
public function setIsAdminInterface($is_admin_interface) {
$this->isAdminInterface = $is_admin_interface;
@@ -51,6 +53,16 @@ class PhabricatorStandardPageView extends AphrontPageView {
return $this;
}
+ public function setFrameable($frameable) {
+ $this->isFrameable = $frameable;
+ return $this;
+ }
+
+ public function setDisableConsole($disable) {
+ $this->disableConsole = $disable;
+ return $this;
+ }
+
public function getApplicationName() {
return $this->applicationName;
}
@@ -103,7 +115,7 @@ class PhabricatorStandardPageView extends AphrontPageView {
"You must set the Request to render a PhabricatorStandardPageView.");
}
- $console = $this->getRequest()->getApplicationConfiguration()->getConsole();
+ $console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('phabricator-core-buttons-css');
@@ -133,10 +145,16 @@ class PhabricatorStandardPageView extends AphrontPageView {
protected function getHead() {
+
+ $framebust = null;
+ if (!$this->isFrameable) {
+ $framebust = '(top != self) && top.location.replace(self.location.href);';
+ }
+
$response = CelerityAPI::getStaticResourceResponse();
$head =
''.
$response->renderResourcesOfType('css').
@@ -185,7 +203,7 @@ class PhabricatorStandardPageView extends AphrontPageView {
}
protected function getBody() {
- $console = $this->getRequest()->getApplicationConfiguration()->getConsole();
+ $console = $this->getConsole();
$tabs = array();
foreach ($this->tabs as $name => $tab) {
@@ -345,4 +363,10 @@ class PhabricatorStandardPageView extends AphrontPageView {
return implode(' ', $classes);
}
+ private function getConsole() {
+ if ($this->disableConsole) {
+ return null;
+ }
+ return $this->getRequest()->getApplicationConfiguration()->getConsole();
+ }
}
diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css
index 7fe3d766b5..6c7cda8baf 100644
--- a/webroot/rsrc/css/aphront/dark-console.css
+++ b/webroot/rsrc/css/aphront/dark-console.css
@@ -87,3 +87,68 @@ a.dark-console-tab-selected {
height: 2px;
}
+.explain-sev-1 {
+ color: #33ff33;
+}
+
+.explain-sev-2 {
+ color: #99ff33;
+}
+
+.explain-sev-3 {
+ color: #ccff33;
+}
+
+.explain-sev-4 {
+ color: #ffff33;
+}
+
+.explain-sev-5 {
+ color: #ffcc33;
+}
+
+.explain-sev-6 {
+ color: #ffffff;
+ font-weight: bold;
+ background: #aa0000;
+ padding: 0 1em;
+ border: 2px solid #ffff00;
+}
+
+.explain-sev-7 {
+ color: #aaaaaa;
+}
+
+.dark-console-panel-header {
+ background: #606060;
+ border-bottom: 1px solid #505050;
+ padding: .25em 1em .25em 0;
+}
+
+.dark-console-panel-header h1 {
+ padding: 1em;
+ font-size: 12px;
+ font-weight: normal;
+}
+
+.dark-console-panel-header .button {
+ margin-top: .5em;
+ float: right;
+}
+
+.dark-console-panel a.bright-link {
+ color: #00cfff;
+ font-weight: bold;
+}
+
+.dark-console iframe {
+ width: 98%;
+ margin: .5em 1%;
+ height: 450px;
+ border: 0;
+}
+
+.dark-console-no-content {
+ padding: 1.5em 2em;
+ font-style: italic;
+}