Profiler::logData()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 0
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, ScopedCallback.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

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