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