diff --git a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php index aa5ebb0e96..bf8c337d78 100755 --- a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php +++ b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPlugin.php @@ -1,112 +1,135 @@ <?php /* * Copyright 2011 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. */ class DarkConsoleXHProfPlugin extends DarkConsolePlugin { protected $xhprofID; - protected $xhprofData; public function getName() { $run = $this->getData(); if ($run) { return '<span style="color: #ff00ff;">•</span> XHProf'; } return 'XHProf'; } public function getDescription() { return 'Provides detailed PHP profiling information through XHProf.'; } public function generateData() { return $this->xhprofID; } public function getXHProfRunID() { return $this->xhprofID; } public function render() { if (!DarkConsoleXHProfPluginAPI::isProfilerAvailable()) { return '<p>The "xhprof" PHP extension is not available. Install xhprof '. 'to enable the XHProf plugin.'; } + + $run = $this->getXHProfRunID(); + if ($run) { + return '<a href="/xhprof/profile/'.$run.'/">View Run</a>'; + } else { + $hidden = array(); + $data = array('__profile__' => 'page') + $_GET; + + foreach ($data as $k => $v) { + $hidden[] = phutil_render_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $k, + 'value' => $v, + )); + } + $hidden = implode("\n", $hidden); + + + return + '<form method="get">'. + $hidden. + '<button>Enable XHProf</button>'. + '</form>'; + } + } + - return '...'; + public function willShutdown() { + if (isset($_REQUEST['__profile__']) && + $_REQUEST['__profile__'] != 'all') { + $this->xhprofID = DarkConsoleXHProfPluginAPI::stopProfiler(); + } } } /* public function render() { $run = $this->getData(); if ($run) { $uri = 'http://www.intern.facebook.com/intern/phprof/?run='.$run; return <x:frag> <h1>XHProf Results</h1> <div class="XHProfPlugin"> <a href={$uri} target="_blank" class="XHProfPlugin">Permalink</a> <iframe src={$uri} width="100%" height="600" /> </div> </x:frag>; } $uri = URI::getRequestURI(); return <x:frag> <h1>XHProf</h1> <form action={$uri} method="get" class="EnableFeature"> <fieldset> <legend>Enable Profiling</legend> <p>Profiling was not enabled for this request. Click the button below to rerun the request with profiling enabled.</p> <button type="submit" name="_profile_" value="all" style="margin: 2px 1em; width: 75%;"> Profile Page (With Includes) </button> <button type="submit" name="_profile_" value="exec" style="margin: 2px 1em; width: 75%;"> Profile Page (No Includes) </button> </fieldset> </form> </x:frag>; } - public function willShutdown() { - if (empty($_REQUEST['_profile_']) || - $_REQUEST['_profile_'] != 'complete') { - require_module('profiling/phprof/bootstrap'); - $this->xhprofData = FB_HotProfiler::stop_hotprofiler(); - } - } - public function didShutdown() { if ($this->xhprofData) { require_module_lazy('profiling/phprof'); $this->xhprofID = phprof_save_run($this->xhprofData); } } } -*/ +*/ \ No newline at end of file diff --git a/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php b/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php index 27551f0d20..bc9535bfef 100755 --- a/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php +++ b/src/aphront/console/plugin/xhprof/api/DarkConsoleXHProfPluginAPI.php @@ -1,84 +1,80 @@ <?php /* * Copyright 2011 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 DarkConsoleXHProfPluginAPI { private static $profilerStarted; public static function isProfilerAvailable() { return extension_loaded('xhprof'); } public static function includeXHProfLib() { // TODO: this is incredibly stupid, but we may not have Phutil metamodule // stuff loaded yet so we can't just phutil_get_library_root() our way // to victory. $root = __FILE__; for ($ii = 0; $ii < 7; $ii++) { $root = dirname($root); } require_once $root.'/externals/xhprof/xhprof_lib.php'; } - public static function hookProfiler($section) { + public static function hookProfiler() { if (empty($_REQUEST['__profile__'])) { return; } - if ($section != $_REQUEST['__profile__']) { - return; - } - if (!self::isProfilerAvailable()) { return; } if (self::$profilerStarted) { return; } self::startProfiler(); self::$profilerStarted = true; } public static function startProfiler() { self::includeXHProfLib(); xhprof_enable(); } public static function stopProfiler() { if (self::$profilerStarted) { $data = xhprof_disable(); $data = serialize($data); $file_class = 'PhabricatorFile'; PhutilSymbolLoader::loadClass($file_class); $file = call_user_func( array($file_class, 'newFromFileData'), $data, array( 'mime-type' => 'application/xhprof', 'name' => 'profile.xhprof', )); return $file->getPHID(); } else { return null; } } } diff --git a/src/applications/xhprof/view/symbol/PhabricatorXHProfProfileSymbolView.php b/src/applications/xhprof/view/symbol/PhabricatorXHProfProfileSymbolView.php index 2936f9a94e..522641f087 100644 --- a/src/applications/xhprof/view/symbol/PhabricatorXHProfProfileSymbolView.php +++ b/src/applications/xhprof/view/symbol/PhabricatorXHProfProfileSymbolView.php @@ -1,163 +1,167 @@ <?php /* * Copyright 2011 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. */ class PhabricatorXHProfProfileSymbolView extends AphrontView { private $profileData; private $baseURI; private $symbol; public function setProfileData(array $data) { $this->profileData = $data; return $this; } public function setBaseURI($uri) { $this->baseURI = $uri; return $this; } public function setSymbol($symbol) { $this->symbol = $symbol; return $this; } public function render() { DarkConsoleXHProfPluginAPI::includeXHProfLib(); $data = $this->profileData; $GLOBALS['display_calls'] = true; $totals = array(); $flat = xhprof_compute_flat_info($data, $totals); unset($GLOBALS['display_calls']); $symbol = $this->symbol; $children = array(); $parents = array(); foreach ($this->profileData as $key => $counters) { - list($parent, $child) = explode('==>', $key, 2); + if (strpos($key, '==>') !== false) { + list($parent, $child) = explode('==>', $key, 2); + } else { + continue; + } if ($parent == $symbol) { $children[$key] = $child; } else if ($child == $symbol) { $parents[$key] = $parent; } } $base_uri = $this->baseURI; $rows = array(); $rows[] = array( 'Metrics for this Call', '', '', '', '', '', ); $rows[] = array( phutil_render_tag( 'a', array( 'href' => $base_uri.'?symbol='.$symbol, ), phutil_escape_html($symbol)), $flat[$symbol]['ct'], $flat[$symbol]['wt'], '', $flat[$symbol]['excl_wt'], '', ); $rows[] = array( 'Parent Calls', '', '', '', '', '', ); foreach ($parents as $key => $name) { $rows[] = array( phutil_render_tag( 'a', array( 'href' => $base_uri.'?symbol='.$name, ), phutil_escape_html($name)), $data[$key]['ct'], $data[$key]['wt'], '', $data[$key]['wt'], '', ); } $rows[] = array( 'Child Calls', '', '', '', '', '', ); foreach ($children as $key => $name) { $rows[] = array( phutil_render_tag( 'a', array( 'href' => $base_uri.'?symbol='.$name, ), phutil_escape_html($name)), $data[$key]['ct'], $data[$key]['wt'], '', $data[$key]['wt'], '', ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Symbol', 'Count', 'Incl Wall Time', '%', 'Excl Wall Time', '%', )); $table->setColumnClasses( array( 'wide pri', 'n', 'n', 'n', 'n', 'n', )); $panel = new AphrontPanelView(); $panel->setHeader('XHProf Profile'); $panel->appendChild($table); return $panel->render(); } } diff --git a/webroot/index.php b/webroot/index.php index 69e65bf6b2..f8d6bd3a9d 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -1,142 +1,150 @@ <?php /* * Copyright 2011 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. */ $env = getenv('PHABRICATOR_ENV'); if (!$env) { header('Content-Type: text/plain'); die( "CONFIG ERROR: ". "The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ". "your httpd.conf to include 'SetEnv PHABRICATOR_ENV <env>', where '<env>' ". "is one of 'development', 'production', or a custom environment."); } $conf = phabricator_read_config_file($env); $conf['phabricator.env'] = $env; setup_aphront_basics(); phutil_require_module('phabricator', 'infrastructure/env'); PhabricatorEnv::setEnvConfig($conf); phutil_require_module('phabricator', 'aphront/console/plugin/xhprof/api'); -DarkConsoleXHProfPluginAPI::hookProfiler('all'); +DarkConsoleXHProfPluginAPI::hookProfiler(); $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; // Based on the host and path, choose which application should serve the // request. The default is the Aphront demo, but you'll want to replace this // with whichever other applications you're running. switch ($host) { default: phutil_require_module('phutil', 'autoload'); phutil_autoload_class('AphrontDefaultApplicationConfiguration'); $application = new AphrontDefaultApplicationConfiguration(); break; } $application->setHost($host); $application->setPath($path); $application->willBuildRequest(); $request = $application->buildRequest(); $application->setRequest($request); list($controller, $uri_data) = $application->buildController(); try { $controller->willBeginExecution(); $controller->willProcessRequest($uri_data); $response = $controller->processRequest(); } catch (AphrontRedirectException $ex) { $response = id(new AphrontRedirectResponse()) ->setURI($ex->getURI()); } catch (Exception $ex) { $response = $application->handleException($ex); } $response = $application->willSendResponse($response); $response->setRequest($request); $response_string = $response->buildResponseString(); $code = $response->getHTTPResponseCode(); if ($code != 200) { header("HTTP/1.0 {$code}"); } $headers = $response->getCacheHeaders(); $headers = array_merge($headers, $response->getHeaders()); foreach ($headers as $header) { list($header, $value) = $header; header("{$header}: {$value}"); } if (isset($_REQUEST['__profile__']) && ($_REQUEST['__profile__'] == 'all')) { $profile = DarkConsoleXHProfPluginAPI::stopProfiler(); - $profile = print_r($profile, true); + $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 { + echo $profile; } } echo $response_string; /** * @group aphront */ function setup_aphront_basics() { $aphront_root = dirname(dirname(__FILE__)); $libraries_root = dirname($aphront_root); ini_set('include_path', ini_get('include_path').':'.$libraries_root.'/'); @include_once 'libphutil/src/__phutil_library_init__.php'; if (!@constant('__LIBPHUTIL__')) { echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ". "include the parent directory of libphutil/.\n"; exit(1); } if (!ini_get('date.timezone')) { date_default_timezone_set('America/Los_Angeles'); } phutil_load_library($libraries_root.'/arcanist/src'); phutil_load_library($aphront_root.'/src'); } function __autoload($class_name) { PhutilSymbolLoader::loadClass($class_name); } function phabricator_read_config_file($config) { $root = dirname(dirname(__FILE__)); $conf = include $root.'/conf/'.$config.'.conf.php'; if ($conf === false) { throw new Exception("Failed to read config file '{$config}'."); } return $conf; }