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