Merge "Don't fallback from uk to ru"
[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
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 }