@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
1<?php
2
3/**
4 * @phutil-external-symbol function xhprof_enable
5 * @phutil-external-symbol function xhprof_disable
6 */
7final class DarkConsoleXHProfPluginAPI extends Phobject {
8
9 private static $profilerStarted;
10 private static $profilerRunning;
11 private static $profileFilePHID;
12
13 public static function isProfilerAvailable() {
14 return extension_loaded('xhprof');
15 }
16
17 public static function getProfilerHeader() {
18 return 'X-Phabricator-Profiler';
19 }
20
21 public static function isProfilerRequested() {
22 if (!empty($_REQUEST['__profile__'])) {
23 return $_REQUEST['__profile__'];
24 }
25
26 $header = AphrontRequest::getHTTPHeader(self::getProfilerHeader());
27 if ($header) {
28 return $header;
29 }
30
31 return false;
32 }
33
34 private static function shouldStartProfiler() {
35 if (self::isProfilerRequested()) {
36 return true;
37 }
38
39 static $sample_request = null;
40
41 if ($sample_request === null) {
42 if (PhabricatorEnv::getEnvConfig('debug.profile-rate')) {
43 $rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
44 if (mt_rand(1, $rate) == 1) {
45 $sample_request = true;
46 } else {
47 $sample_request = false;
48 }
49 }
50 }
51
52 return $sample_request;
53 }
54
55 public static function isProfilerStarted() {
56 return self::$profilerStarted;
57 }
58
59 private static function isProfilerRunning() {
60 return self::$profilerRunning;
61 }
62
63 public static function includeXHProfLib() {
64 // TODO: this is incredibly stupid, but we may not have Phutil metamodule
65 // stuff loaded yet so we can't just phutil_get_library_root() our way
66 // to victory.
67 $root = __FILE__;
68 for ($ii = 0; $ii < 6; $ii++) {
69 $root = dirname($root);
70 }
71
72 require_once $root.'/externals/xhprof/xhprof_lib.php';
73 }
74
75
76 public static function saveProfilerSample(PhutilDeferredLog $access_log) {
77 $file_phid = self::getProfileFilePHID();
78 if (!$file_phid) {
79 return;
80 }
81
82 if (self::isProfilerRequested()) {
83 $sample_rate = 0;
84 } else {
85 $sample_rate = PhabricatorEnv::getEnvConfig('debug.profile-rate');
86 }
87
88 $profile_sample = id(new PhabricatorXHProfSample())
89 ->setFilePHID($file_phid)
90 ->setSampleRate($sample_rate)
91 ->setUsTotal($access_log->getData('T'))
92 ->setHostname($access_log->getData('h'))
93 ->setRequestPath($access_log->getData('U'))
94 ->setController($access_log->getData('C'))
95 ->setUserPHID($access_log->getData('P'));
96
97 AphrontWriteGuard::allowDangerousUnguardedWrites(true);
98 $caught = null;
99 try {
100 $profile_sample->save();
101 } catch (Exception $ex) {
102 $caught = $ex;
103 }
104 AphrontWriteGuard::allowDangerousUnguardedWrites(false);
105
106 if ($caught) {
107 throw $caught;
108 }
109 }
110
111 public static function hookProfiler() {
112 if (!self::shouldStartProfiler()) {
113 return;
114 }
115
116 if (!self::isProfilerAvailable()) {
117 return;
118 }
119
120 if (self::$profilerStarted) {
121 return;
122 }
123
124 self::startProfiler();
125 }
126
127
128 /**
129 * @phutil-external-symbol class PhabricatorStartup
130 */
131 private static function startProfiler() {
132 PhabricatorStartup::beginStartupPhase('profiler.init');
133
134 self::includeXHProfLib();
135 xhprof_enable();
136
137 self::$profilerStarted = true;
138 self::$profilerRunning = true;
139 }
140
141
142 /**
143 * @phutil-external-symbol class PhabricatorStartup
144 */
145 public static function getProfileFilePHID() {
146 if (!self::isProfilerRunning()) {
147 return;
148 }
149
150 PhabricatorStartup::beginStartupPhase('profiler.stop');
151 self::stopProfiler();
152 PhabricatorStartup::beginStartupPhase('profiler.done');
153
154 return self::$profileFilePHID;
155 }
156
157 private static function stopProfiler() {
158
159 $data = xhprof_disable();
160 $data = @json_encode($data);
161 self::$profilerRunning = false;
162
163 // Since these happen on GET we can't do guarded writes. These also
164 // sometimes happen after we've disposed of the write guard; in this
165 // case we need to disable the whole mechanism.
166
167 $use_scope = AphrontWriteGuard::isGuardActive();
168 if ($use_scope) {
169 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
170 } else {
171 AphrontWriteGuard::allowDangerousUnguardedWrites(true);
172 }
173
174 $caught = null;
175 try {
176 $file = call_user_func(
177 array('PhabricatorFile', 'newFromFileData'),
178 $data,
179 array(
180 'mime-type' => 'application/xhprof',
181 'name' => 'profile.xhprof',
182 ));
183 } catch (Exception $ex) {
184 $caught = $ex;
185 }
186
187 if ($use_scope) {
188 unset($unguarded);
189 } else {
190 AphrontWriteGuard::allowDangerousUnguardedWrites(false);
191 }
192
193 if ($caught) {
194 throw $caught;
195 }
196
197 self::$profileFilePHID = $file->getPHID();
198 }
199
200}