mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Do sampled profiling of requests
Summary: People have occasionally complained about phabricator being slow. We have the access log to look at to see when slowness happens, but it doesn't tell us much about why it happened. Since it's usually a sporadic issue that's reported, it's hard to reproduce and then profile. This change will allow us to collect sampled profiles so we can look at them when slowness occurs. Test Plan: checking that sampling works correctly: - set rate to 0; do several page loads; check no new entries in table - set rate to 1; check that there's a new row in the table for each page load - set rate to 10; check that some requests write to table and some don't check new ui for samples: - load /xhprof/list/all/, see a list with a lot of samples - load /xhprof/list/sampled/, see only sampled runs - load /xhprof/list/manual/, see only non-sampled runs - load /xhprof/list/my-runs/, se only my manual runs Reviewers: vrana, epriestley Reviewed By: epriestley CC: aran, Korvin Differential Revision: https://secure.phabricator.com/D3458
This commit is contained in:
parent
a52af0b3ae
commit
5978bbfc64
11 changed files with 296 additions and 30 deletions
|
@ -1273,9 +1273,12 @@ return array(
|
|||
// since the redirect happens in Javascript.
|
||||
'debug.stop-on-redirect' => false,
|
||||
|
||||
// Enable this to always profile every page. This is very slow! You should
|
||||
// only enable it when debugging.
|
||||
'debug.profile-every-request' => false,
|
||||
// Set the rate for how often to do sampled profiling. On average, one
|
||||
// request for every number of requests specified here will be sampled.
|
||||
// Set this value to 0 to completely disable profiling. In a production
|
||||
// environment, this value should either be set to 0 (to disable) or to
|
||||
// a large number (to sample only a few requests).
|
||||
'debug.profile-rate' => 0,
|
||||
|
||||
|
||||
// -- Previews ------------------------------------------------------------- //
|
||||
|
|
14
resources/sql/patches/xhprof.sql
Normal file
14
resources/sql/patches/xhprof.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
CREATE DATABASE {$NAMESPACE}_xhprof;
|
||||
CREATE TABLE {$NAMESPACE}_xhprof.xhprof_sample (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
`filePHID` VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
`sampleRate` INT NOT NULL,
|
||||
`usTotal` BIGINT UNSIGNED NOT NULL,
|
||||
`hostname` VARCHAR(255) COLLATE utf8_bin,
|
||||
`requestPath` VARCHAR(255) COLLATE utf8_bin,
|
||||
`controller` VARCHAR(255) COLLATE utf8_bin,
|
||||
`userPHID` VARCHAR(64) COLLATE utf8_bin,
|
||||
`dateCreated` BIGINT UNSIGNED NOT NULL,
|
||||
`dateModified` BIGINT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY (filePHID)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -1116,10 +1116,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHPASTViewStreamController' => 'applications/xhpastview/controller/PhabricatorXHPASTViewStreamController.php',
|
||||
'PhabricatorXHPASTViewTreeController' => 'applications/xhpastview/controller/PhabricatorXHPASTViewTreeController.php',
|
||||
'PhabricatorXHProfController' => 'applications/xhprof/controller/PhabricatorXHProfController.php',
|
||||
'PhabricatorXHProfDAO' => 'applications/xhprof/storage/PhabricatorXHProfDAO.php',
|
||||
'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/PhabricatorXHProfProfileController.php',
|
||||
'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php',
|
||||
'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php',
|
||||
'PhabricatorXHProfSample' => 'applications/xhprof/storage/PhabricatorXHProfSample.php',
|
||||
'PhabricatorXHProfSampleListController' => 'applications/xhprof/controller/PhabricatorXHProfSampleListController.php',
|
||||
'PhabricatorXHProfSampleListView' => 'applications/xhprof/view/PhabricatorXHProfSampleListView.php',
|
||||
'PhameAllBlogListController' => 'applications/phame/controller/blog/list/PhameAllBlogListController.php',
|
||||
'PhameAllPostListController' => 'applications/phame/controller/post/list/PhameAllPostListController.php',
|
||||
'PhameBlog' => 'applications/phame/storage/PhameBlog.php',
|
||||
|
@ -2205,10 +2209,14 @@ phutil_register_library_map(array(
|
|||
'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController',
|
||||
'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController',
|
||||
'PhabricatorXHProfController' => 'PhabricatorController',
|
||||
'PhabricatorXHProfDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController',
|
||||
'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView',
|
||||
'PhabricatorXHProfProfileView' => 'AphrontView',
|
||||
'PhabricatorXHProfSample' => 'PhabricatorXHProfDAO',
|
||||
'PhabricatorXHProfSampleListController' => 'PhabricatorXHProfController',
|
||||
'PhabricatorXHProfSampleListView' => 'AphrontView',
|
||||
'PhameAllBlogListController' => 'PhameBlogListBaseController',
|
||||
'PhameAllPostListController' => 'PhamePostListBaseController',
|
||||
'PhameBlog' => 'PhameDAO',
|
||||
|
|
|
@ -126,6 +126,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
),
|
||||
|
||||
'/xhprof/' => array(
|
||||
'list/(?P<view>[^/]+)/' => 'PhabricatorXHProfSampleListController',
|
||||
'profile/(?P<phid>[^/]+)/' => 'PhabricatorXHProfProfileController',
|
||||
),
|
||||
|
||||
|
|
|
@ -34,11 +34,20 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
return $_REQUEST['__profile__'];
|
||||
}
|
||||
|
||||
if (PhabricatorEnv::getEnvConfig('debug.profile-every-request')) {
|
||||
return PhabricatorEnv::getEnvConfig('debug.profile-every-request');
|
||||
static $profilerRequested = null;
|
||||
|
||||
if (!isset($profilerRequested)) {
|
||||
if (PhabricatorEnv::getEnvConfig('debug.profile-rate')) {
|
||||
$rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
|
||||
if (mt_rand(1, $rate) == 1) {
|
||||
$profilerRequested = true;
|
||||
} else {
|
||||
$profilerRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return $profilerRequested;
|
||||
}
|
||||
|
||||
public static function includeXHProfLib() {
|
||||
|
@ -73,9 +82,7 @@ final class DarkConsoleXHProfPluginAPI {
|
|||
|
||||
public static function startProfiler() {
|
||||
self::includeXHProfLib();
|
||||
// Note: HPHP's implementation of XHProf currently requires an argument
|
||||
// to xhprof_enable() -- see Facebook Task #531011.
|
||||
xhprof_enable(0);
|
||||
xhprof_enable();
|
||||
}
|
||||
|
||||
public static function stopProfiler() {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorXHProfSampleListController
|
||||
extends PhabricatorXHProfController {
|
||||
|
||||
private $view;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->view = $data['view'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$pager = new AphrontPagerView();
|
||||
$pager->setOffset($request->getInt('page'));
|
||||
|
||||
switch ($this->view) {
|
||||
case 'sampled':
|
||||
$clause = '`sampleRate` > 0';
|
||||
$show_type = false;
|
||||
break;
|
||||
case 'my-runs':
|
||||
$clause = qsprintf(
|
||||
id(new PhabricatorXHProfSample())->establishConnection('r'),
|
||||
'`sampleRate` = 0 AND `userPHID` = %s',
|
||||
$request->getUser()->getPHID());
|
||||
$show_type = false;
|
||||
break;
|
||||
case 'manual':
|
||||
$clause = '`sampleRate` = 0';
|
||||
$show_type = false;
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
$clause = '1 = 1';
|
||||
$show_type = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$samples = id(new PhabricatorXHProfSample())->loadAllWhere(
|
||||
'%Q ORDER BY dateCreated DESC LIMIT %d, %d',
|
||||
$clause,
|
||||
$pager->getOffset(),
|
||||
$pager->getPageSize() + 1);
|
||||
|
||||
$samples = $pager->sliceResults($samples);
|
||||
$pager->setURI($request->getRequestURI(), 'page');
|
||||
|
||||
$table = new PhabricatorXHProfSampleListView();
|
||||
$table->setUser($request->getUser());
|
||||
$table->setSamples($samples);
|
||||
$table->setShowType($show_type);
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('XHProf Samples');
|
||||
$panel->appendChild($table);
|
||||
$panel->appendChild($pager);
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$panel,
|
||||
array('title' => 'XHProf Samples'));
|
||||
|
||||
}
|
||||
}
|
24
src/applications/xhprof/storage/PhabricatorXHProfDAO.php
Normal file
24
src/applications/xhprof/storage/PhabricatorXHProfDAO.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorXHProfDAO extends PhabricatorLiskDAO {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'xhprof';
|
||||
}
|
||||
}
|
28
src/applications/xhprof/storage/PhabricatorXHProfSample.php
Normal file
28
src/applications/xhprof/storage/PhabricatorXHProfSample.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorXHProfSample extends PhabricatorXHProfDAO {
|
||||
|
||||
protected $filePHID;
|
||||
protected $usTotal;
|
||||
protected $sampleRate;
|
||||
protected $hostname;
|
||||
protected $requestPath;
|
||||
protected $controller;
|
||||
protected $userPHID;
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
final class PhabricatorXHProfSampleListView extends AphrontView {
|
||||
|
||||
private $samples;
|
||||
private $user;
|
||||
private $showType = false;
|
||||
|
||||
public function setSamples(array $samples) {
|
||||
assert_instances_of($samples, 'PhabricatorXHProfSample');
|
||||
$this->samples = $samples;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setShowType($show_type) {
|
||||
$this->showType = $show_type;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$rows = array();
|
||||
|
||||
if (!$this->user) {
|
||||
throw new Exception("Call setUser() before rendering!");
|
||||
}
|
||||
|
||||
$user_phids = mpull($this->samples, 'getUserPHID');
|
||||
$users = id(new PhabricatorObjectHandleData($user_phids))->loadObjects();
|
||||
foreach ($this->samples as $sample) {
|
||||
$sample_link = phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/xhprof/profile/'.$sample->getFilePHID().'/',
|
||||
),
|
||||
$sample->getFilePHID());
|
||||
if ($this->showType) {
|
||||
if ($sample->getSampleRate() == 0) {
|
||||
$sample_link .= ' (manual run)';
|
||||
} else {
|
||||
$sample_link .= ' (sampled)';
|
||||
}
|
||||
}
|
||||
$rows[] = array(
|
||||
$sample_link,
|
||||
phabricator_datetime($sample->getDateCreated(), $this->user),
|
||||
number_format($sample->getUsTotal())." \xCE\xBCs",
|
||||
$sample->getHostname(),
|
||||
$sample->getRequestPath(),
|
||||
$sample->getController(),
|
||||
idx($users, $sample->getUserPHID()),
|
||||
);
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($rows);
|
||||
$table->setHeaders(
|
||||
array(
|
||||
'Sample',
|
||||
'Date',
|
||||
'Wall Time',
|
||||
'Hostname',
|
||||
'Request Path',
|
||||
'Controller',
|
||||
'User',
|
||||
));
|
||||
$table->setColumnClasses(
|
||||
array(
|
||||
'',
|
||||
'',
|
||||
'right',
|
||||
'wide wrap',
|
||||
'',
|
||||
'',
|
||||
));
|
||||
|
||||
return $table->render();
|
||||
}
|
||||
|
||||
}
|
|
@ -979,6 +979,9 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'pastepolicy.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('pastepolicy.sql'),
|
||||
'xhprof.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('xhprof.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -228,27 +228,6 @@ $headers = array_merge($headers, $response->getHeaders());
|
|||
|
||||
$sink->writeHeaders($headers);
|
||||
|
||||
// TODO: This shouldn't be possible in a production-configured environment.
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerRequested() &&
|
||||
DarkConsoleXHProfPluginAPI::isProfilerRequested() === 'all') {
|
||||
$profile = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
$profile =
|
||||
'<div style="text-align: center; background: #ff00ff; padding: 1em;
|
||||
font-size: 24px; font-weight: bold;">'.
|
||||
'<a href="/xhprof/profile/'.$profile.'/">'.
|
||||
'>>> View Profile <<<'.
|
||||
'</a>'.
|
||||
'</div>';
|
||||
if (strpos($response_string, '<body>') !== false) {
|
||||
$response_string = str_replace(
|
||||
'<body>',
|
||||
'<body>'.$profile,
|
||||
$response_string);
|
||||
} else {
|
||||
$sink->writeData($profile);
|
||||
}
|
||||
}
|
||||
|
||||
$sink->writeData($response_string);
|
||||
|
||||
if ($access_log) {
|
||||
|
@ -260,6 +239,26 @@ if ($access_log) {
|
|||
$access_log->write();
|
||||
}
|
||||
|
||||
if (DarkConsoleXHProfPluginAPI::isProfilerRequested()) {
|
||||
$profile = DarkConsoleXHProfPluginAPI::stopProfiler();
|
||||
$profile_sample = id(new PhabricatorXHProfSample())
|
||||
->setFilePHID($profile);
|
||||
if (empty($_REQUEST['__profile__'])) {
|
||||
$sample_rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
|
||||
} else {
|
||||
$sample_rate = 0;
|
||||
}
|
||||
$profile_sample->setSampleRate($sample_rate);
|
||||
if ($access_log) {
|
||||
$profile_sample->setUsTotal($access_log->getData('T'))
|
||||
->setHostname($access_log->getData('h'))
|
||||
->setRequestPath($access_log->getData('U'))
|
||||
->setController($access_log->getData('C'))
|
||||
->setUserPHID($request->getUser()->getPHID());
|
||||
}
|
||||
$profile_sample->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group aphront
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue