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