]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Profiler.php
MediaWiki 1.11.0
[autoinstalls/mediawiki.git] / includes / Profiler.php
1 <?php
2 /**
3  * This file is only included if profiling is enabled
4  */
5
6 $wgProfiling = true;
7
8 /**
9  * @param $functioname name of the function we will profile
10  */
11 function wfProfileIn($functionname) {
12         global $wgProfiler;
13         $wgProfiler->profileIn($functionname);
14 }
15
16 /**
17  * @param $functioname name of the function we have profiled
18  */
19 function wfProfileOut($functionname = 'missing') {
20         global $wgProfiler;
21         $wgProfiler->profileOut($functionname);
22 }
23
24 function wfGetProfilingOutput($start, $elapsed) {
25         global $wgProfiler;
26         return $wgProfiler->getOutput($start, $elapsed);
27 }
28
29 function wfProfileClose() {
30         global $wgProfiler;
31         $wgProfiler->close();
32 }
33
34 if (!function_exists('memory_get_usage')) {
35         # Old PHP or --enable-memory-limit not compiled in
36         function memory_get_usage() {
37                 return 0;
38         }
39 }
40
41 /**
42  * @todo document
43  * @addtogroup Profiler
44  */
45 class Profiler {
46         var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
47         var $mCalls = array (), $mTotals = array ();
48
49         function __construct() {
50                 // Push an entry for the pre-profile setup time onto the stack
51                 global $wgRequestTime;
52                 if ( !empty( $wgRequestTime ) ) {
53                         $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, 0 );
54                         $this->mStack[] = array( '-setup', 1, $wgRequestTime, 0, microtime(true), 0 );
55                 } else {
56                         $this->profileIn( '-total' );
57                 }
58         }
59
60         function profileIn($functionname) {
61                 global $wgDebugFunctionEntry;
62                 if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
63                         wfDebug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n");
64                 }
65                 $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage());
66         }
67
68         function profileOut($functionname) {
69                 $memory = memory_get_usage();
70                 $time = $this->getTime();
71
72                 global $wgDebugFunctionEntry;
73
74                 if ($wgDebugFunctionEntry && function_exists('wfDebug')) {
75                         wfDebug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
76                 }
77
78                 $bit = array_pop($this->mWorkStack);
79
80                 if (!$bit) {
81                         wfDebug("Profiling error, !\$bit: $functionname\n");
82                 } else {
83                         //if ($wgDebugProfiling) {
84                                 if ($functionname == 'close') {
85                                         $message = "Profile section ended by close(): {$bit[0]}";
86                                         wfDebug( "$message\n" );
87                                         $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
88                                 }
89                                 elseif ($bit[0] != $functionname) {
90                                         $message = "Profiling error: in({$bit[0]}), out($functionname)";
91                                         wfDebug( "$message\n" );
92                                         $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
93                                 }
94                         //}
95                         $bit[] = $time;
96                         $bit[] = $memory;
97                         $this->mStack[] = $bit;
98                 }
99         }
100
101         function close() {
102                 while (count($this->mWorkStack)) {
103                         $this->profileOut('close');
104                 }
105         }
106
107         function getOutput() {
108                 global $wgDebugFunctionEntry;
109                 $wgDebugFunctionEntry = false;
110
111                 if (!count($this->mStack) && !count($this->mCollated)) {
112                         return "No profiling output\n";
113                 }
114                 $this->close();
115
116                 global $wgProfileCallTree;
117                 if ($wgProfileCallTree) {
118                         return $this->getCallTree();
119                 } else {
120                         return $this->getFunctionReport();
121                 }
122         }
123
124         function getCallTree($start = 0) {
125                 return implode('', array_map(array (& $this, 'getCallTreeLine'), $this->remapCallTree($this->mStack)));
126         }
127
128         function remapCallTree($stack) {
129                 if (count($stack) < 2) {
130                         return $stack;
131                 }
132                 $outputs = array ();
133                 for ($max = count($stack) - 1; $max > 0;) {
134                         /* Find all items under this entry */
135                         $level = $stack[$max][1];
136                         $working = array ();
137                         for ($i = $max -1; $i >= 0; $i --) {
138                                 if ($stack[$i][1] > $level) {
139                                         $working[] = $stack[$i];
140                                 } else {
141                                         break;
142                                 }
143                         }
144                         $working = $this->remapCallTree(array_reverse($working));
145                         $output = array ();
146                         foreach ($working as $item) {
147                                 array_push($output, $item);
148                         }
149                         array_unshift($output, $stack[$max]);
150                         $max = $i;
151
152                         array_unshift($outputs, $output);
153                 }
154                 $final = array ();
155                 foreach ($outputs as $output) {
156                         foreach ($output as $item) {
157                                 $final[] = $item;
158                         }
159                 }
160                 return $final;
161         }
162
163         function getCallTreeLine($entry) {
164                 list ($fname, $level, $start, /* $x */, $end) = $entry;
165                 $delta = $end - $start;
166                 $space = str_repeat(' ', $level);
167
168                 # The ugly double sprintf is to work around a PHP bug,
169                 # which has been fixed in recent releases.
170                 return sprintf( "%10s %s %s\n",
171                         trim( sprintf( "%7.3f", $delta * 1000.0 ) ),
172                         $space, $fname );
173         }
174
175         function getTime() {
176                 return microtime(true);
177                 #return $this->getUserTime();
178         }
179
180         function getUserTime() {
181                 $ru = getrusage();
182                 return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
183         }
184
185         function getFunctionReport() {
186                 $width = 140;
187                 $nameWidth = $width - 65;
188                 $format =      "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d  (%13.3f -%13.3f) [%d]\n";
189                 $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
190                 $prof = "\nProfiling data\n";
191                 $prof .= sprintf($titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem');
192                 $this->mCollated = array ();
193                 $this->mCalls = array ();
194                 $this->mMemory = array ();
195
196                 # Estimate profiling overhead
197                 $profileCount = count($this->mStack);
198                 wfProfileIn('-overhead-total');
199                 for ($i = 0; $i < $profileCount; $i ++) {
200                         wfProfileIn('-overhead-internal');
201                         wfProfileOut('-overhead-internal');
202                 }
203                 wfProfileOut('-overhead-total');
204
205                 # First, subtract the overhead!
206                 foreach ($this->mStack as $entry) {
207                         $fname = $entry[0];
208                         $start = $entry[2];
209                         $end = $entry[4];
210                         $elapsed = $end - $start;
211                         $memory = $entry[5] - $entry[3];
212
213                         if ($fname == '-overhead-total') {
214                                 $overheadTotal[] = $elapsed;
215                                 $overheadMemory[] = $memory;
216                         }
217                         elseif ($fname == '-overhead-internal') {
218                                 $overheadInternal[] = $elapsed;
219                         }
220                 }
221                 $overheadTotal = array_sum($overheadTotal) / count($overheadInternal);
222                 $overheadMemory = array_sum($overheadMemory) / count($overheadInternal);
223                 $overheadInternal = array_sum($overheadInternal) / count($overheadInternal);
224
225                 # Collate
226                 foreach ($this->mStack as $index => $entry) {
227                         $fname = $entry[0];
228                         $start = $entry[2];
229                         $end = $entry[4];
230                         $elapsed = $end - $start;
231
232                         $memory = $entry[5] - $entry[3];
233                         $subcalls = $this->calltreeCount($this->mStack, $index);
234
235                         if (!preg_match('/^-overhead/', $fname)) {
236                                 # Adjust for profiling overhead (except special values with elapsed=0
237                                 if ( $elapsed ) {
238                                         $elapsed -= $overheadInternal;
239                                         $elapsed -= ($subcalls * $overheadTotal);
240                                         $memory -= ($subcalls * $overheadMemory);
241                                 }
242                         }
243
244                         if (!array_key_exists($fname, $this->mCollated)) {
245                                 $this->mCollated[$fname] = 0;
246                                 $this->mCalls[$fname] = 0;
247                                 $this->mMemory[$fname] = 0;
248                                 $this->mMin[$fname] = 1 << 24;
249                                 $this->mMax[$fname] = 0;
250                                 $this->mOverhead[$fname] = 0;
251                         }
252
253                         $this->mCollated[$fname] += $elapsed;
254                         $this->mCalls[$fname]++;
255                         $this->mMemory[$fname] += $memory;
256                         $this->mMin[$fname] = min($this->mMin[$fname], $elapsed);
257                         $this->mMax[$fname] = max($this->mMax[$fname], $elapsed);
258                         $this->mOverhead[$fname] += $subcalls;
259                 }
260
261                 $total = @ $this->mCollated['-total'];
262                 $this->mCalls['-overhead-total'] = $profileCount;
263
264                 # Output
265                 arsort($this->mCollated, SORT_NUMERIC);
266                 foreach ($this->mCollated as $fname => $elapsed) {
267                         $calls = $this->mCalls[$fname];
268                         $percent = $total ? 100. * $elapsed / $total : 0;
269                         $memory = $this->mMemory[$fname];
270                         $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
271
272                         global $wgProfileToDatabase;
273                         if ($wgProfileToDatabase) {
274                                 Profiler :: logToDB($fname, (float) ($elapsed * 1000), $calls);
275                         }
276                 }
277                 $prof .= "\nTotal: $total\n\n";
278
279                 return $prof;
280         }
281
282         /**
283          * Counts the number of profiled function calls sitting under
284          * the given point in the call graph. Not the most efficient algo.
285          *
286          * @param $stack Array:
287          * @param $start Integer:
288          * @return Integer
289          * @private
290          */
291         function calltreeCount($stack, $start) {
292                 $level = $stack[$start][1];
293                 $count = 0;
294                 for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) {
295                         $count ++;
296                 }
297                 return $count;
298         }
299
300         /**
301          * @static
302          */
303         function logToDB($name, $timeSum, $eventCount) {
304                 # Do not log anything if database is readonly (bug 5375)
305                 if( wfReadOnly() ) { return; }
306
307                 # Warning: $wguname is a live patch, it should be moved to Setup.php
308                 global $wguname, $wgProfilePerHost;
309
310                 $fname = 'Profiler::logToDB';
311                 $dbw = wfGetDB(DB_MASTER);
312                 if (!is_object($dbw))
313                         return false;
314                 $errorState = $dbw->ignoreErrors( true );
315                 $profiling = $dbw->tableName('profiling');
316
317                 $name = substr($name, 0, 255);
318                 $encname = $dbw->strencode($name);
319                 
320                 if ($wgProfilePerHost) {
321                         $pfhost = $wguname['nodename'];
322                 } else {
323                         $pfhost = '';
324                 }
325
326                 $sql = "UPDATE $profiling "."SET pf_count=pf_count+{$eventCount}, "."pf_time=pf_time + {$timeSum} ".
327                         "WHERE pf_name='{$encname}' AND pf_server='{$pfhost}'";
328                 $dbw->query($sql);
329
330                 $rc = $dbw->affectedRows();
331                 if ($rc == 0) {
332                         $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
333                                 'pf_time' => $timeSum, 'pf_server' => $pfhost ), $fname, array ('IGNORE'));
334                 }
335                 // When we upgrade to mysql 4.1, the insert+update
336                 // can be merged into just a insert with this construct added:
337                 //     "ON DUPLICATE KEY UPDATE ".
338                 //     "pf_count=pf_count + VALUES(pf_count), ".
339                 //     "pf_time=pf_time + VALUES(pf_time)";
340                 $dbw->ignoreErrors( $errorState );
341         }
342
343         /**
344          * Get the function name of the current profiling section
345          */
346         function getCurrentSection() {
347                 $elt = end($this->mWorkStack);
348                 return $elt[0];
349         }
350         
351         static function getCaller( $level ) {
352                 $backtrace = wfDebugBacktrace();
353                 if ( isset( $backtrace[$level] ) ) {
354                         if ( isset( $backtrace[$level]['class'] ) ) {
355                                 $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
356                         } else {
357                                 $caller = $backtrace[$level]['function'];
358                         }
359                 } else {
360                         $caller = 'unknown';
361                 }
362                 return $caller;
363         }
364
365 }
366
367