@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Add a "Startup" to DarkConsole

Summary: Ref T8588. It looks like something slow is happening //before// we start DarkConsole. Add some crude reporting to try to narrow it down.

Test Plan: {F743050}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T8588

Differential Revision: https://secure.phabricator.com/D13956

+219 -15
+2
src/__phutil_library_map__.php
··· 286 286 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', 287 287 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', 288 288 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', 289 + 'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php', 289 290 'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php', 290 291 'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', 291 292 'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php', ··· 3912 3913 'DarkConsolePlugin' => 'Phobject', 3913 3914 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 3914 3915 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 3916 + 'DarkConsoleStartupPlugin' => 'DarkConsolePlugin', 3915 3917 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 3916 3918 'DarkConsoleXHProfPluginAPI' => 'Phobject', 3917 3919 'DefaultDatabaseConfigurationProvider' => array(
+8
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 58 58 * @phutil-external-symbol class PhabricatorStartup 59 59 */ 60 60 public static function runHTTPRequest(AphrontHTTPSink $sink) { 61 + PhabricatorStartup::beginStartupPhase('multimeter'); 61 62 $multimeter = MultimeterControl::newInstance(); 62 63 $multimeter->setEventContext('<http-init>'); 63 64 $multimeter->setEventViewer('<none>'); ··· 67 68 // request object first. 68 69 $write_guard = new AphrontWriteGuard('id'); 69 70 71 + PhabricatorStartup::beginStartupPhase('env.init'); 70 72 PhabricatorEnv::initializeWebEnvironment(); 71 73 72 74 $multimeter->setSampleRate( ··· 78 80 } 79 81 80 82 // This is the earliest we can get away with this, we need env config first. 83 + PhabricatorStartup::beginStartupPhase('log.access'); 81 84 PhabricatorAccessLog::init(); 82 85 $access_log = PhabricatorAccessLog::getLog(); 83 86 PhabricatorStartup::setAccessLog($access_log); ··· 89 92 )); 90 93 91 94 DarkConsoleXHProfPluginAPI::hookProfiler(); 95 + 96 + // We just activated the profiler, so we don't need to keep track of 97 + // startup phases anymore: it can take over from here. 98 + PhabricatorStartup::beginStartupPhase('startup.done'); 99 + 92 100 DarkConsoleErrorLogPluginAPI::registerErrorHandler(); 93 101 94 102 $response = PhabricatorSetupCheck::willProcessRequest();
+84
src/applications/console/plugin/DarkConsoleStartupPlugin.php
··· 1 + <?php 2 + 3 + final class DarkConsoleStartupPlugin extends DarkConsolePlugin { 4 + 5 + public function getName() { 6 + return pht('Startup'); 7 + } 8 + 9 + public function getDescription() { 10 + return pht('Timing information about the startup sequence.'); 11 + } 12 + 13 + /** 14 + * @phutil-external-symbol class PhabricatorStartup 15 + */ 16 + public function generateData() { 17 + return PhabricatorStartup::getPhases(); 18 + } 19 + 20 + public function renderPanel() { 21 + $data = $this->getData(); 22 + 23 + // Compute the time offset and duration of each startup phase. 24 + $prev_key = null; 25 + $init = null; 26 + $phases = array(); 27 + foreach ($data as $key => $value) { 28 + if ($init === null) { 29 + $init = $value; 30 + } 31 + 32 + $offset = (int)floor(1000 * ($value - $init)); 33 + 34 + $phases[$key] = array( 35 + 'time' => $value, 36 + 'offset' => $value - $init, 37 + ); 38 + 39 + 40 + if ($prev_key !== null) { 41 + $phases[$prev_key]['duration'] = $value - $phases[$prev_key]['time']; 42 + } 43 + $prev_key = $key; 44 + } 45 + 46 + // Render the phases. 47 + $rows = array(); 48 + foreach ($phases as $key => $phase) { 49 + $offset_ms = (int)floor(1000 * $phase['offset']); 50 + 51 + if (isset($phase['duration'])) { 52 + $duration_us = (int)floor(1000000 * $phase['duration']); 53 + } else { 54 + $duration_us = null; 55 + } 56 + 57 + $rows[] = array( 58 + $key, 59 + pht('+%s ms', new PhutilNumber($offset_ms)), 60 + ($duration_us === null) 61 + ? pht('-') 62 + : pht('%s us', new PhutilNumber($duration_us)), 63 + null, 64 + ); 65 + } 66 + 67 + return id(new AphrontTableView($rows)) 68 + ->setHeaders( 69 + array( 70 + pht('Phase'), 71 + pht('Offset'), 72 + pht('Duration'), 73 + null, 74 + )) 75 + ->setColumnClasses( 76 + array( 77 + '', 78 + 'n right', 79 + 'n right', 80 + 'wide', 81 + )); 82 + } 83 + 84 + }
+17 -3
src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php
··· 124 124 self::startProfiler(); 125 125 } 126 126 127 + 128 + /** 129 + * @phutil-external-symbol class PhabricatorStartup 130 + */ 127 131 private static function startProfiler() { 132 + PhabricatorStartup::beginStartupPhase('profiler.init'); 133 + 128 134 self::includeXHProfLib(); 129 135 xhprof_enable(); 130 136 ··· 132 138 self::$profilerRunning = true; 133 139 } 134 140 141 + 142 + /** 143 + * @phutil-external-symbol class PhabricatorStartup 144 + */ 135 145 public static function getProfileFilePHID() { 146 + if (!self::isProfilerRunning()) { 147 + return; 148 + } 149 + 150 + PhabricatorStartup::beginStartupPhase('profiler.stop'); 136 151 self::stopProfiler(); 152 + PhabricatorStartup::beginStartupPhase('profiler.done'); 153 + 137 154 return self::$profileFilePHID; 138 155 } 139 156 140 157 private static function stopProfiler() { 141 - if (!self::isProfilerRunning()) { 142 - return; 143 - } 144 158 145 159 $data = xhprof_disable(); 146 160 $data = @json_encode($data);
+19
src/docs/user/field/darkconsole.diviner
··· 138 138 tool to understand problems in PHP is XHProf. 139 139 140 140 141 + Plugin: Startup 142 + =============== 143 + 144 + The "Startup" plugin shows information about startup phases. This information 145 + can provide insight about performance problems which occur before the profiler 146 + can start. 147 + 148 + Normally, the profiler is the best tool for understanding runtime performance, 149 + but some work is performed before the profiler starts (for example, loading 150 + libraries and configuration). If there is a substantial difference between the 151 + wall time reported by the profiler and the "Entire Page" cost reported by the 152 + Services tab, the Startup tab can help account for that time. 153 + 154 + It is normal for starting the profiler to increase the cost of the page 155 + somewhat: the profiler itself adds overhead while it is running, and the page 156 + must do some work after the profiler is stopped to save the profile and 157 + complete other shutdown operations. 158 + 159 + 141 160 Plugin: XHProf 142 161 ============== 143 162
+62 -2
support/PhabricatorStartup.php
··· 34 34 * @task apocalypse In Case Of Apocalypse 35 35 * @task validation Validation 36 36 * @task ratelimit Rate Limiting 37 + * @task phases Startup Phase Timers 37 38 */ 38 39 final class PhabricatorStartup { 39 40 ··· 43 44 private static $capturingOutput; 44 45 private static $rawInput; 45 46 private static $oldMemoryLimit; 47 + private static $phases; 46 48 47 49 // TODO: For now, disable rate limiting entirely by default. We need to 48 50 // iterate on it a bit for Conduit, some of the specific score levels, and ··· 89 91 90 92 91 93 /** 94 + * @param float Request start time, from `microtime(true)`. 92 95 * @task hook 93 96 */ 94 - public static function didStartup() { 95 - self::$startTime = microtime(true); 97 + public static function didStartup($start_time) { 98 + self::$startTime = $start_time; 99 + 100 + self::$phases = array(); 101 + 96 102 self::$accessLog = null; 97 103 98 104 static $registered; ··· 852 858 echo $message; 853 859 854 860 exit(1); 861 + } 862 + 863 + 864 + /* -( Startup Timers )----------------------------------------------------- */ 865 + 866 + 867 + /** 868 + * Record the beginning of a new startup phase. 869 + * 870 + * For phases which occur before @{class:PhabricatorStartup} loads, save the 871 + * time and record it with @{method:recordStartupPhase} after the class is 872 + * available. 873 + * 874 + * @param string Phase name. 875 + * @task phases 876 + */ 877 + public static function beginStartupPhase($phase) { 878 + self::recordStartupPhase($phase, microtime(true)); 879 + } 880 + 881 + 882 + /** 883 + * Record the start time of a previously executed startup phase. 884 + * 885 + * For startup phases which occur after @{class:PhabricatorStartup} loads, 886 + * use @{method:beginStartupPhase} instead. This method can be used to 887 + * record a time before the class loads, then hand it over once the class 888 + * becomes available. 889 + * 890 + * @param string Phase name. 891 + * @param float Phase start time, from `microtime(true)`. 892 + * @task phases 893 + */ 894 + public static function recordStartupPhase($phase, $time) { 895 + self::$phases[$phase] = $time; 896 + } 897 + 898 + 899 + /** 900 + * Get information about startup phase timings. 901 + * 902 + * Sometimes, performance problems can occur before we start the profiler. 903 + * Since the profiler can't examine these phases, it isn't useful in 904 + * understanding their performance costs. 905 + * 906 + * Instead, the startup process marks when it enters various phases using 907 + * @{method:beginStartupPhase}. A later call to this method can retrieve this 908 + * information, which can be examined to gain greater insight into where 909 + * time was spent. The output is still crude, but better than nothing. 910 + * 911 + * @task phases 912 + */ 913 + public static function getPhases() { 914 + return self::$phases; 855 915 } 856 916 857 917 }
+27 -10
webroot/index.php
··· 1 1 <?php 2 2 3 - $phabricator_root = dirname(dirname(__FILE__)); 4 - require_once $phabricator_root.'/support/PhabricatorStartup.php'; 5 - 6 - // If the preamble script exists, load it. 7 - $preamble_path = $phabricator_root.'/support/preamble.php'; 8 - if (file_exists($preamble_path)) { 9 - require_once $preamble_path; 10 - } 11 - 12 - PhabricatorStartup::didStartup(); 3 + phabricator_startup(); 13 4 14 5 try { 6 + PhabricatorStartup::beginStartupPhase('libraries'); 15 7 PhabricatorStartup::loadCoreLibraries(); 8 + 9 + PhabricatorStartup::beginStartupPhase('purge'); 16 10 PhabricatorCaches::destroyRequestCache(); 17 11 12 + PhabricatorStartup::beginStartupPhase('sink'); 18 13 $sink = new AphrontPHPHTTPSink(); 19 14 20 15 try { 16 + PhabricatorStartup::beginStartupPhase('run'); 21 17 AphrontApplicationConfiguration::runHTTPRequest($sink); 22 18 } catch (Exception $ex) { 23 19 try { ··· 36 32 } catch (Exception $ex) { 37 33 PhabricatorStartup::didEncounterFatalException('Core Exception', $ex, false); 38 34 } 35 + 36 + function phabricator_startup() { 37 + // Load the PhabricatorStartup class itself. 38 + $t_startup = microtime(true); 39 + $root = dirname(dirname(__FILE__)); 40 + require_once $root.'/support/PhabricatorStartup.php'; 41 + 42 + // If the preamble script exists, load it. 43 + $t_preamble = microtime(true); 44 + $preamble_path = $root.'/support/preamble.php'; 45 + if (file_exists($preamble_path)) { 46 + require_once $preamble_path; 47 + } 48 + 49 + $t_hook = microtime(true); 50 + PhabricatorStartup::didStartup($t_startup); 51 + 52 + PhabricatorStartup::recordStartupPhase('startup.init', $t_startup); 53 + PhabricatorStartup::recordStartupPhase('preamble', $t_preamble); 54 + PhabricatorStartup::recordStartupPhase('hook', $t_hook); 55 + }