Merge "Improve docs for Title::getInternalURL/getCanonicalURL"
[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 bool Whether MediaWiki is in a SkinTemplate output context */
37 protected $templated = false;
38 /** @var array All of the params passed from $wgProfiler */
39 protected $params = [];
40 /** @var IContextSource Current request context */
41 protected $context = null;
42 /** @var TransactionProfiler */
43 protected $trxProfiler;
44 /** @var Profiler */
45 private static $instance = null;
46
47 /**
48 * @param array $params
49 */
50 public function __construct( array $params ) {
51 if ( isset( $params['profileID'] ) ) {
52 $this->profileID = $params['profileID'];
53 }
54 $this->params = $params;
55 $this->trxProfiler = new TransactionProfiler();
56 }
57
58 /**
59 * Singleton
60 * @return Profiler
61 */
62 final public static function instance() {
63 if ( self::$instance === null ) {
64 global $wgProfiler, $wgProfileLimit;
65
66 $params = [
67 'class' => ProfilerStub::class,
68 'sampling' => 1,
69 'threshold' => $wgProfileLimit,
70 'output' => [],
71 ];
72 if ( is_array( $wgProfiler ) ) {
73 $params = array_merge( $params, $wgProfiler );
74 }
75
76 $inSample = mt_rand( 0, $params['sampling'] - 1 ) === 0;
77 // wfIsCLI() is not available yet
78 if ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' || !$inSample ) {
79 $params['class'] = ProfilerStub::class;
80 }
81
82 if ( !is_array( $params['output'] ) ) {
83 $params['output'] = [ $params['output'] ];
84 }
85
86 self::$instance = new $params['class']( $params );
87 }
88 return self::$instance;
89 }
90
91 /**
92 * Replace the current profiler with $profiler if no non-stub profiler is set
93 *
94 * @param Profiler $profiler
95 * @throws MWException
96 * @since 1.25
97 */
98 final public static function replaceStubInstance( Profiler $profiler ) {
99 if ( self::$instance && !( self::$instance instanceof ProfilerStub ) ) {
100 throw new MWException( 'Could not replace non-stub profiler instance.' );
101 } else {
102 self::$instance = $profiler;
103 }
104 }
105
106 /**
107 * @param string $id
108 */
109 public function setProfileID( $id ) {
110 $this->profileID = $id;
111 }
112
113 /**
114 * @return string
115 */
116 public function getProfileID() {
117 if ( $this->profileID === false ) {
118 return WikiMap::getCurrentWikiDbDomain()->getId();
119 } else {
120 return $this->profileID;
121 }
122 }
123
124 /**
125 * Sets the context for this Profiler
126 *
127 * @param IContextSource $context
128 * @since 1.25
129 */
130 public function setContext( $context ) {
131 $this->context = $context;
132 }
133
134 /**
135 * Gets the context for this Profiler
136 *
137 * @return IContextSource
138 * @since 1.25
139 */
140 public function getContext() {
141 if ( $this->context ) {
142 return $this->context;
143 } else {
144 wfDebug( __METHOD__ . " called and \$context is null. " .
145 "Return RequestContext::getMain(); for sanity\n" );
146 return RequestContext::getMain();
147 }
148 }
149
150 public function profileIn( $functionname ) {
151 wfDeprecated( __METHOD__, '1.33' );
152 }
153
154 public function profileOut( $functionname ) {
155 wfDeprecated( __METHOD__, '1.33' );
156 }
157
158 /**
159 * Mark the start of a custom profiling frame (e.g. DB queries).
160 * The frame ends when the result of this method falls out of scope.
161 *
162 * @param string $section
163 * @return ScopedCallback|null
164 * @since 1.25
165 */
166 abstract public function scopedProfileIn( $section );
167
168 /**
169 * @param SectionProfileCallback|null &$section
170 */
171 public function scopedProfileOut( SectionProfileCallback &$section = null ) {
172 $section = null;
173 }
174
175 /**
176 * @return TransactionProfiler
177 * @since 1.25
178 */
179 public function getTransactionProfiler() {
180 return $this->trxProfiler;
181 }
182
183 /**
184 * Close opened profiling sections
185 */
186 abstract public function close();
187
188 /**
189 * Get all usable outputs.
190 *
191 * @throws MWException
192 * @return ProfilerOutput[]
193 * @since 1.25
194 */
195 private function getOutputs() {
196 $outputs = [];
197 foreach ( $this->params['output'] as $outputType ) {
198 // The class may be specified as either the full class name (for
199 // example, 'ProfilerOutputStats') or (for backward compatibility)
200 // the trailing portion of the class name (for example, 'stats').
201 $outputClass = strpos( $outputType, 'ProfilerOutput' ) === false
202 ? 'ProfilerOutput' . ucfirst( $outputType )
203 : $outputType;
204 if ( !class_exists( $outputClass ) ) {
205 throw new MWException( "'$outputType' is an invalid output type" );
206 }
207 $outputInstance = new $outputClass( $this, $this->params );
208 if ( $outputInstance->canUse() ) {
209 $outputs[] = $outputInstance;
210 }
211 }
212 return $outputs;
213 }
214
215 /**
216 * Log the data to the backing store for all ProfilerOutput instances that have one
217 *
218 * @since 1.25
219 */
220 public function logData() {
221 $request = $this->getContext()->getRequest();
222
223 $timeElapsed = $request->getElapsedTime();
224 $timeElapsedThreshold = $this->params['threshold'];
225 if ( $timeElapsed <= $timeElapsedThreshold ) {
226 return;
227 }
228
229 $outputs = [];
230 foreach ( $this->getOutputs() as $output ) {
231 if ( !$output->logsToOutput() ) {
232 $outputs[] = $output;
233 }
234 }
235
236 if ( $outputs ) {
237 $stats = $this->getFunctionStats();
238 foreach ( $outputs as $output ) {
239 $output->log( $stats );
240 }
241 }
242 }
243
244 /**
245 * Log the data to the script/request output for all ProfilerOutput instances that do so
246 *
247 * @throws MWException
248 * @since 1.26
249 */
250 public function logDataPageOutputOnly() {
251 $outputs = [];
252 foreach ( $this->getOutputs() as $output ) {
253 if ( $output->logsToOutput() ) {
254 $outputs[] = $output;
255 }
256 }
257
258 if ( $outputs ) {
259 $stats = $this->getFunctionStats();
260 foreach ( $outputs as $output ) {
261 $output->log( $stats );
262 }
263 }
264 }
265
266 /**
267 * Get the content type sent out to the client.
268 * Used for profilers that output instead of store data.
269 * @return string
270 * @since 1.25
271 */
272 public function getContentType() {
273 foreach ( headers_list() as $header ) {
274 if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
275 return $m[1];
276 }
277 }
278 return null;
279 }
280
281 /**
282 * Mark this call as templated or not
283 *
284 * @param bool $t
285 */
286 public function setTemplated( $t ) {
287 $this->templated = $t;
288 }
289
290 /**
291 * Was this call as templated or not
292 *
293 * @return bool
294 */
295 public function getTemplated() {
296 return $this->templated;
297 }
298
299 /**
300 * Get the aggregated inclusive profiling data for each method
301 *
302 * The percent time for each time is based on the current "total" time
303 * used is based on all methods so far. This method can therefore be
304 * called several times in between several profiling calls without the
305 * delays in usage of the profiler skewing the results. A "-total" entry
306 * is always included in the results.
307 *
308 * When a call chain involves a method invoked within itself, any
309 * entries for the cyclic invocation should be be demarked with "@".
310 * This makes filtering them out easier and follows the xhprof style.
311 *
312 * @return array List of method entries arrays, each having:
313 * - name : method name
314 * - calls : the number of invoking calls
315 * - real : real time elapsed (ms)
316 * - %real : percent real time
317 * - cpu : CPU time elapsed (ms)
318 * - %cpu : percent CPU time
319 * - memory : memory used (bytes)
320 * - %memory : percent memory used
321 * - min_real : min real time in a call (ms)
322 * - max_real : max real time in a call (ms)
323 * @since 1.25
324 */
325 abstract public function getFunctionStats();
326
327 /**
328 * Returns a profiling output to be stored in debug file
329 *
330 * @return string
331 */
332 abstract public function getOutput();
333 }