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