Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 72 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
Profiler | |
0.00% |
0 / 72 |
|
0.00% |
0 / 13 |
1640 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
init | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
instance | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
setProfileID | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getProfileID | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
scopedProfileIn | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
scopedProfileOut | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTransactionProfiler | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
close | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getOutputs | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
logData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
56 | |||
logDataPageOutputOnly | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
getContentType | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
setAllowOutput | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllowOutput | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFunctionStats | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getOutput | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | use MediaWiki\Logger\LoggerFactory; |
22 | use MediaWiki\WikiMap\WikiMap; |
23 | use Psr\Log\LoggerInterface; |
24 | use Wikimedia\Rdbms\TransactionProfiler; |
25 | use Wikimedia\ScopedCallback; |
26 | |
27 | /** |
28 | * @defgroup Profiler Profiler |
29 | */ |
30 | |
31 | /** |
32 | * Profiler base class that defines the interface and some shared |
33 | * functionality. |
34 | * |
35 | * @ingroup Profiler |
36 | */ |
37 | abstract class Profiler { |
38 | /** @var string|false Profiler ID for bucketing data */ |
39 | protected $profileID = false; |
40 | /** @var array All of the params passed from $wgProfiler */ |
41 | protected $params = []; |
42 | /** @var TransactionProfiler */ |
43 | protected $trxProfiler; |
44 | /** @var LoggerInterface */ |
45 | protected $logger; |
46 | /** @var bool */ |
47 | private $allowOutput = false; |
48 | |
49 | /** @var Profiler */ |
50 | private static $instance = null; |
51 | |
52 | /** |
53 | * @param array $params See $wgProfiler. |
54 | */ |
55 | public function __construct( array $params ) { |
56 | if ( isset( $params['profileID'] ) ) { |
57 | $this->profileID = $params['profileID']; |
58 | } |
59 | $this->params = $params; |
60 | $this->trxProfiler = new TransactionProfiler(); |
61 | $this->logger = LoggerFactory::getInstance( 'profiler' ); |
62 | } |
63 | |
64 | /** |
65 | * @internal For use by Setup.php |
66 | * @param array $profilerConf Value from $wgProfiler |
67 | */ |
68 | final public static function init( array $profilerConf ): void { |
69 | $params = $profilerConf + [ |
70 | 'class' => ProfilerStub::class, |
71 | 'sampling' => 1, |
72 | 'threshold' => 0.0, |
73 | 'output' => [], |
74 | 'cliEnable' => false, |
75 | ]; |
76 | |
77 | // Avoid global func wfIsCLI() during setup |
78 | $isCLI = ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ); |
79 | $inSample = $params['sampling'] === 1 || mt_rand( 1, $params['sampling'] ) === 1; |
80 | if ( |
81 | !$inSample || |
82 | // On CLI, profiling is disabled by default, and can be explicitly enabled |
83 | // via the `--profiler` option, which MediaWiki\Maintenance\MaintenanceRunner::setup |
84 | // translates into 'cliEnable'. |
85 | // See also $wgProfiler docs. |
86 | // |
87 | // For this to work, Setup.php must call Profiler::init() after handling of |
88 | // MW_FINAL_SETUP_CALLBACK, which is what doMaintenance.php uses to call |
89 | // MaintenanceRunner::setup. |
90 | ( $isCLI && !$params['cliEnable'] ) |
91 | ) { |
92 | $params['class'] = ProfilerStub::class; |
93 | } |
94 | |
95 | if ( !is_array( $params['output'] ) ) { |
96 | $params['output'] = [ $params['output'] ]; |
97 | } |
98 | |
99 | self::$instance = new $params['class']( $params ); |
100 | } |
101 | |
102 | /** |
103 | * @return Profiler |
104 | */ |
105 | final public static function instance() { |
106 | if ( !self::$instance ) { |
107 | trigger_error( 'Called Profiler::instance before settings are loaded', E_USER_WARNING ); |
108 | self::init( [] ); |
109 | } |
110 | |
111 | return self::$instance; |
112 | } |
113 | |
114 | /** |
115 | * @deprecated since 1.41, unused. Can override this base class. |
116 | * @param string $id |
117 | */ |
118 | public function setProfileID( $id ) { |
119 | wfDeprecated( __METHOD__, '1.41' ); |
120 | $this->profileID = $id; |
121 | } |
122 | |
123 | /** |
124 | * @return string |
125 | */ |
126 | public function getProfileID() { |
127 | if ( $this->profileID === false ) { |
128 | return WikiMap::getCurrentWikiDbDomain()->getId(); |
129 | } else { |
130 | return $this->profileID; |
131 | } |
132 | } |
133 | |
134 | /** |
135 | * Mark the start of a custom profiling frame (e.g. DB queries). |
136 | * The frame ends when the result of this method falls out of scope. |
137 | * |
138 | * @param string $section |
139 | * @return ScopedCallback|null |
140 | * @since 1.25 |
141 | */ |
142 | abstract public function scopedProfileIn( $section ); |
143 | |
144 | /** |
145 | * @param SectionProfileCallback|null &$section |
146 | */ |
147 | public function scopedProfileOut( ?SectionProfileCallback &$section = null ) { |
148 | $section = null; |
149 | } |
150 | |
151 | /** |
152 | * @return TransactionProfiler |
153 | * @since 1.25 |
154 | */ |
155 | public function getTransactionProfiler() { |
156 | return $this->trxProfiler; |
157 | } |
158 | |
159 | /** |
160 | * Close opened profiling sections |
161 | */ |
162 | abstract public function close(); |
163 | |
164 | /** |
165 | * Get all usable outputs. |
166 | * |
167 | * @return ProfilerOutput[] |
168 | * @since 1.25 |
169 | */ |
170 | private function getOutputs() { |
171 | $outputs = []; |
172 | foreach ( $this->params['output'] as $outputType ) { |
173 | // The class may be specified as either the full class name (for |
174 | // example, 'ProfilerOutputStats') or (for backward compatibility) |
175 | // the trailing portion of the class name (for example, 'stats'). |
176 | $outputClass = strpos( $outputType, 'ProfilerOutput' ) === false |
177 | ? 'ProfilerOutput' . ucfirst( $outputType ) |
178 | : $outputType; |
179 | if ( !class_exists( $outputClass ) ) { |
180 | throw new UnexpectedValueException( "'$outputType' is an invalid output type" ); |
181 | } |
182 | $outputInstance = new $outputClass( $this, $this->params ); |
183 | if ( $outputInstance->canUse() ) { |
184 | $outputs[] = $outputInstance; |
185 | } |
186 | } |
187 | return $outputs; |
188 | } |
189 | |
190 | /** |
191 | * Log data to all the applicable backing stores |
192 | * |
193 | * This logs the profiling data to the backing store for each configured ProfilerOutput |
194 | * instance. It also logs any request data for the TransactionProfiler instance. |
195 | * |
196 | * @since 1.25 |
197 | */ |
198 | public function logData() { |
199 | if ( $this->params['threshold'] > 0.0 ) { |
200 | // Note, this is also valid for CLI processes. |
201 | $timeElapsed = microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT']; |
202 | if ( $timeElapsed <= $this->params['threshold'] ) { |
203 | return; |
204 | } |
205 | } |
206 | |
207 | $outputs = []; |
208 | foreach ( $this->getOutputs() as $output ) { |
209 | if ( !$output->logsToOutput() ) { |
210 | $outputs[] = $output; |
211 | } |
212 | } |
213 | |
214 | if ( $outputs ) { |
215 | $stats = $this->getFunctionStats(); |
216 | foreach ( $outputs as $output ) { |
217 | $output->log( $stats ); |
218 | } |
219 | } |
220 | } |
221 | |
222 | /** |
223 | * Log the data to the script/request output for all ProfilerOutput instances that do so |
224 | * |
225 | * @since 1.26 |
226 | */ |
227 | public function logDataPageOutputOnly() { |
228 | if ( !$this->allowOutput ) { |
229 | return; |
230 | } |
231 | |
232 | $outputs = []; |
233 | foreach ( $this->getOutputs() as $output ) { |
234 | if ( $output->logsToOutput() ) { |
235 | $outputs[] = $output; |
236 | } |
237 | } |
238 | |
239 | if ( $outputs ) { |
240 | $stats = $this->getFunctionStats(); |
241 | foreach ( $outputs as $output ) { |
242 | $output->log( $stats ); |
243 | } |
244 | } |
245 | } |
246 | |
247 | /** |
248 | * Get the Content-Type for deciding how to format appended profile output. |
249 | * |
250 | * Disabled by default. Enable via setAllowOutput(). |
251 | * |
252 | * @see ProfilerOutputText |
253 | * @since 1.25 |
254 | * @return string|null Returns null if disabled or no Content-Type found. |
255 | */ |
256 | public function getContentType() { |
257 | if ( $this->allowOutput ) { |
258 | foreach ( headers_list() as $header ) { |
259 | if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) { |
260 | return $m[1]; |
261 | } |
262 | } |
263 | } |
264 | return null; |
265 | } |
266 | |
267 | /** |
268 | * Enable appending profiles to standard output. |
269 | * |
270 | * @since 1.34 |
271 | */ |
272 | public function setAllowOutput() { |
273 | $this->allowOutput = true; |
274 | } |
275 | |
276 | /** |
277 | * Whether appending profiles is allowed. |
278 | * |
279 | * @deprecated since 1.41. Unused. |
280 | * |
281 | * @since 1.34 |
282 | * @return bool |
283 | */ |
284 | public function getAllowOutput() { |
285 | wfDeprecated( __METHOD__, '1.41' ); |
286 | return $this->allowOutput; |
287 | } |
288 | |
289 | /** |
290 | * Get the aggregated inclusive profiling data for each method |
291 | * |
292 | * The percent time for each time is based on the current "total" time |
293 | * used is based on all methods so far. This method can therefore be |
294 | * called several times in between several profiling calls without the |
295 | * delays in usage of the profiler skewing the results. A "-total" entry |
296 | * is always included in the results. |
297 | * |
298 | * When a call chain involves a method invoked within itself, any |
299 | * entries for the cyclic invocation should be demarked with "@". |
300 | * This makes filtering them out easier and follows the xhprof style. |
301 | * |
302 | * @return array[] List of method entries arrays, each having: |
303 | * - name : method name |
304 | * - calls : the number of invoking calls |
305 | * - real : real time elapsed (ms) |
306 | * - %real : percent real time |
307 | * - cpu : CPU time elapsed (ms) |
308 | * - %cpu : percent CPU time |
309 | * - memory : memory used (bytes) |
310 | * - %memory : percent memory used |
311 | * - min_real : min real time in a call (ms) |
312 | * - max_real : max real time in a call (ms) |
313 | * @since 1.25 |
314 | */ |
315 | abstract public function getFunctionStats(); |
316 | |
317 | /** |
318 | * Returns a profiling output to be stored in debug file |
319 | * |
320 | * @return string |
321 | */ |
322 | abstract public function getOutput(); |
323 | } |