Updated the 19 build version to 20081106
[moodle.git] / lib / profilerlib.php
blobc21294020ec544134c65ea96b5c94fda1d5fd96d
1 <?php
2 ini_set('display_errors', "On");
4 require_once 'PEAR.php';
6 if(!function_exists('scandir'))
8 function scandir($dir, $sortorder = 0)
10 if(is_dir($dir))
12 $dirlist = opendir($dir);
14 while( ($file = readdir($dirlist)) !== false)
16 if(!is_dir($file))
18 $files[] = $file;
22 ($sortorder == 0) ? asort($files) : arsort($files);
24 return $files;
26 else
28 return FALSE;
29 break;
35 /**
36 * Command-line options parsing class.
38 * @author Andrei Zmievski <andrei@php.net>
41 class Console_Getopt {
42 /**
43 * Parses the command-line options.
45 * The first parameter to this function should be the list of command-line
46 * arguments without the leading reference to the running program.
48 * The second parameter is a string of allowed short options. Each of the
49 * option letters can be followed by a colon ':' to specify that the option
50 * requires an argument, or a double colon '::' to specify that the option
51 * takes an optional argument.
53 * The third argument is an optional array of allowed long options. The
54 * leading '--' should not be included in the option name. Options that
55 * require an argument should be followed by '=', and options that take an
56 * option argument should be followed by '=='.
58 * The return value is an array of two elements: the list of parsed
59 * options and the list of non-option command-line arguments. Each entry in
60 * the list of parsed options is a pair of elements - the first one
61 * specifies the option, and the second one specifies the option argument,
62 * if there was one.
64 * Long and short options can be mixed.
66 * Most of the semantics of this function are based on GNU getopt_long().
68 * @param array $args an array of command-line arguments
69 * @param string $short_options specifies the list of allowed short options
70 * @param array $long_options specifies the list of allowed long options
72 * @return array two-element array containing the list of parsed options and
73 * the non-option arguments
75 * @access public
78 function getopt2($args, $short_options, $long_options = null)
80 return Console_Getopt::doGetopt(2, $args, $short_options, $long_options);
83 /**
84 * This function expects $args to start with the script name (POSIX-style).
85 * Preserved for backwards compatibility.
86 * @see getopt2()
88 function getopt($args, $short_options, $long_options = null)
90 return Console_Getopt::doGetopt(1, $args, $short_options, $long_options);
93 /**
94 * The actual implementation of the argument parsing code.
96 function doGetopt($version, $args, $short_options, $long_options = null)
98 // in case you pass directly readPHPArgv() as the first arg
99 if (PEAR::isError($args)) {
100 return $args;
102 if (empty($args)) {
103 return array(array(), array());
105 $opts = array();
106 $non_opts = array();
108 settype($args, 'array');
110 if ($long_options) {
111 sort($long_options);
115 * Preserve backwards compatibility with callers that relied on
116 * erroneous POSIX fix.
118 if ($version < 2) {
119 if (isset($args[0]{0}) && $args[0]{0} != '-') {
120 array_shift($args);
124 reset($args);
125 while (list($i, $arg) = each($args)) {
127 /* The special element '--' means explicit end of
128 options. Treat the rest of the arguments as non-options
129 and end the loop. */
130 if ($arg == '--') {
131 $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
132 break;
135 if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
136 $non_opts = array_merge($non_opts, array_slice($args, $i));
137 break;
138 } elseif (strlen($arg) > 1 && $arg{1} == '-') {
139 $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
140 if (PEAR::isError($error))
141 return $error;
142 } else {
143 $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
144 if (PEAR::isError($error))
145 return $error;
149 return array($opts, $non_opts);
153 * @access private
156 function _parseShortOption($arg, $short_options, &$opts, &$args)
158 for ($i = 0; $i < strlen($arg); $i++) {
159 $opt = $arg{$i};
160 $opt_arg = null;
162 /* Try to find the short option in the specifier string. */
163 if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
165 return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt");
168 if (strlen($spec) > 1 && $spec{1} == ':') {
169 if (strlen($spec) > 2 && $spec{2} == ':') {
170 if ($i + 1 < strlen($arg)) {
171 /* Option takes an optional argument. Use the remainder of
172 the arg string if there is anything left. */
173 $opts[] = array($opt, substr($arg, $i + 1));
174 break;
176 } else {
177 /* Option requires an argument. Use the remainder of the arg
178 string if there is anything left. */
179 if ($i + 1 < strlen($arg)) {
180 $opts[] = array($opt, substr($arg, $i + 1));
181 break;
182 } else if (list(, $opt_arg) = each($args))
183 /* Else use the next argument. */;
184 else
185 return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
189 $opts[] = array($opt, $opt_arg);
194 * @access private
197 function _parseLongOption($arg, $long_options, &$opts, &$args)
199 @list($opt, $opt_arg) = explode('=', $arg);
200 $opt_len = strlen($opt);
202 for ($i = 0; $i < count($long_options); $i++) {
203 $long_opt = $long_options[$i];
204 $opt_start = substr($long_opt, 0, $opt_len);
206 /* Option doesn't match. Go on to the next one. */
207 if ($opt_start != $opt)
208 continue;
210 $opt_rest = substr($long_opt, $opt_len);
212 /* Check that the options uniquely matches one of the allowed
213 options. */
214 if ($opt_rest != '' && $opt{0} != '=' &&
215 $i + 1 < count($long_options) &&
216 $opt == substr($long_options[$i+1], 0, $opt_len)) {
217 return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous");
220 if (substr($long_opt, -1) == '=') {
221 if (substr($long_opt, -2) != '==') {
222 /* Long option requires an argument.
223 Take the next argument if one wasn't specified. */;
224 if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
225 return PEAR::raiseError("Console_Getopt: option --$opt requires an argument");
228 } else if ($opt_arg) {
229 return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument");
232 $opts[] = array('--' . $opt, $opt_arg);
233 return;
236 return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
240 * Safely read the $argv PHP array across different PHP configurations.
241 * Will take care on register_globals and register_argc_argv ini directives
243 * @access public
244 * @return mixed the $argv PHP array or PEAR error if not registered
246 function readPHPArgv()
248 global $argv;
249 if (!is_array($argv)) {
250 if (!@is_array($_SERVER['argv'])) {
251 if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
252 return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)");
254 return $GLOBALS['HTTP_SERVER_VARS']['argv'];
256 return $_SERVER['argv'];
258 return $argv;
265 * Profiler adapted from Pear::APD's pprofp script. Not quite there yet, I need
266 * to get this to accept a similar list of arguments as the script does,
267 * and process them the same way. Also make sure that the file being loaded
268 * is the right one. Also support multiple pids used in one page load (up to 4 so far).
269 * Then output all this in a nicely formatted table.
271 class Profiler
273 var $stimes;
274 var $utimes;
275 var $calls;
276 var $c_stimes;
277 var $c_utimes;
278 var $mem;
281 * Concatenates all the pprof files generated by apd_set_pprof_trace()
282 * and returns the resulting string, which can then be processed by
283 * get_profiling();
284 * It also deletes these files once finished, in order to limit
285 * cluttering of the filesystem. This can be switched off by
286 * providing "false" as the only argument to this function.
288 * WARNING: If you switch cleanup off, profiling data will
289 * accumulate from one pageload to the next.
291 * @param boolean $cleanup Whether to delete pprof files or not.
292 * @return String Profiling raw data
294 function _get_pprofp($cleanup = true)
296 global $CFG, $USER;
297 // List all files under our temporary directory
298 $tempdir = $CFG->dataroot . '/temp/profile/' . $USER->id;
299 if ($files = scandir($tempdir)) {
300 // Concatenate the files
301 print_r($files);
302 } else {
303 print "Error: Profiler could not read the directory $tempdir.";
304 return false;
308 // Return a handle to the resulting file
311 if(($DATA = fopen($dataFile, "r")) == FALSE) {
312 return "Failed to open $dataFile for reading\n";
314 return $handle;
319 * Returns profiling information gathered using APD functions.
320 * Accepts a numerical array of command-line arguments.
322 * @usage Profiler::get_profiling($args)
323 * Sort options
324 * -a Sort by alphabetic names of subroutines.
325 * -l Sort by number of calls to subroutines
326 * -m Sort by memory used in a function call.
327 * -r Sort by real time spent in subroutines.
328 * -R Sort by real time spent in subroutines (inclusive of child calls).
329 * -s Sort by system time spent in subroutines.
330 * -S Sort by system time spent in subroutines (inclusive of child calls).
331 * -u Sort by user time spent in subroutines.
332 * -U Sort by user time spent in subroutines (inclusive of child calls).
333 * -v Sort by average amount of time spent in subroutines.
334 * -z Sort by user+system time spent in subroutines. (default)
336 * Display options
337 * -c Display Real time elapsed alongside call tree.
338 * -i Suppress reporting for php builtin functions
339 * -O <cnt> Specifies maximum number of subroutines to display. (default 15)
340 * -t Display compressed call tree.
341 * -T Display uncompressed call tree.
343 * Example array: array('-a', '-l');
345 * @param Array $args
346 * @return String Profiling info
348 function get_profiling($args)
350 $con = new Console_Getopt;
351 array_shift($args);
353 $shortoptions = 'acg:hiIlmMrRsStTuUO:vzZ';
354 $retval = $con->getopt( $args, $shortoptions);
355 if(is_object($retval)) {
356 usage();
359 $opt['O'] = 20;
360 foreach ($retval[0] as $kv_array) {
361 $opt[$kv_array[0]] = $kv_array[1];
364 $DATA = Profiler::_get_pprofp();
366 $cfg = array();
367 $this->parse_info('HEADER', $DATA, $cfg);
369 $callstack = array();
370 $calls = array();
371 $indent_cur = 0;
372 $file_hash = array();
373 $this->mem = array();
374 $t_rtime = 0;
375 $t_stime = 0;
376 $t_utime = 0;
377 $c_rtimes = array();
378 $this->c_stimes = array();
379 $this->c_utimes = array();
380 $rtimes = array();
381 $this->stimes = array();
382 $this->utimes = array();
383 $rtotal = 0;
384 $stotal = 0;
385 $utotal = 0;
386 $last_memory = 0;
388 $symbol_hash = array();
389 $symbol_type = array();
391 while($line = fgets($DATA)) {
392 $line = rtrim($line);
393 if(preg_match("/^END_TRACE/", $line)){
394 break;
396 list($token, $data) = preg_split("/ /",$line, 2);
397 if($token == '!') {
398 list ($index, $file) = preg_split("/ /", $data, 2);
399 $file_hash[$index] = $file;
400 continue;
402 if( $token == '&') {
403 list ($index, $name, $type) = preg_split("/ /", $data, 3);
404 $symbol_hash[$index] = $name;
405 $symbol_type[$index] = $type;
406 continue;
408 if( $token == '+') {
409 list($index, $file, $line) = preg_split("/ /",$data, 3);
410 if(array_key_exists('i',$opt) && $symbol_type[$index] == 1) {
411 continue;
413 $index_cur = $index;
414 $calls[$index_cur]++;
415 array_push($callstack, $index_cur);
416 if(array_key_exists('T', $opt)) {
417 if(array_key_exists('c', $opt)) {
418 $retstring .= sprintf("%2.02f ", $rtotal/1000000);
420 $retstring .= str_repeat(" ", $indent_cur).$symbol_hash[$index_cur]."\n";
421 if(array_key_exists('m', $opt)) {
422 $retstring .= str_repeat(" ", $indent_cur)."C: $file_hash[$file]:$line M: $memory\n";
425 elseif(array_key_exists('t', $opt)) {
426 if ( $indent_last == $indent_cur && $index_last == $index_cur ) {
427 $repcnt++;
429 else {
430 if ( $repcnt ) {
431 $repstr = ' ('.++$repcnt.'x)';
433 if(array_key_exists('c', $opt)) {
434 $retstring .= sprintf("%2.02f ", $rtotal/1000000);
436 $retstring .= str_repeat(" ", $indent_last).$symbol_hash[$index_last].$repstr."\n";
437 if(array_key_exists('m', $opt)) {
438 $retstring .= str_repeat(" ", $indent_cur)."C: $file_hash[$file_last]:$line_last M: $memory\n";
440 $repstr = '';
441 $repcnt = 0;
442 $index_last = $index_cur;
443 $indent_last = $indent_cur;
444 $file_last = $file;
445 $line_last = $line;
448 $indent_cur++;
449 continue;
451 if( $token == '@') {
452 list($file_no, $line_no, $ut, $st, $rt) = preg_split("/ /", $data);
453 $top = array_pop($callstack);
454 $this->utimes[$top] += $ut;
455 $utotal += $ut;
456 $this->stimes[$top] += $st;
457 $stotal += $st;
458 $rtimes[$top] += $rt;
459 $rtotal += $rt;
460 array_push($callstack, $top);
461 foreach ($callstack as $stack_element) {
462 $this->c_utimes[$stack_element] += $ut;
463 $this->c_stimes[$stack_element] += $st;
464 $c_rtimes[$stack_element] += $rt;
466 continue;
468 if ($token == '-') {
469 list ($index, $memory) = preg_split("/ /", $data, 2);
470 if(array_key_exists('i',$opt) && $symbol_type[$index] == 1)
472 continue;
474 $this->mem[$index] += ($memory - $last_memory);
475 $last_memory = $memory;
476 $indent_cur--;
477 $tmp = array_pop($callstack);
478 continue;
481 $this->parse_info('FOOTER', $DATA, $cfg);
482 $sort = 'by_time';
483 if(array_key_exists('l', $opt)) { $sort = 'by_calls'; }
484 if(array_key_exists('m', $opt)) { $sort = 'by_mem'; }
485 if(array_key_exists('a', $opt)) { $sort = 'by_name'; }
486 if(array_key_exists('v', $opt)) { $sort = 'by_avgcpu'; }
487 if(array_key_exists('r', $opt)) { $sort = 'by_rtime'; }
488 if(array_key_exists('R', $opt)) { $sort = 'by_c_rtime'; }
489 if(array_key_exists('s', $opt)) { $sort = 'by_stime'; }
490 if(array_key_exists('S', $opt)) { $sort = 'by_c_stime'; }
491 if(array_key_exists('u', $opt)) { $sort = 'by_utime'; }
492 if(array_key_exists('U', $opt)) { $sort = 'by_c_utime'; }
493 if(array_key_exists('Z', $opt)) { $sort = 'by_c_time'; }
494 if( !count($symbol_hash)) {
495 continue;
498 $retstring .= sprintf("
499 Trace for %s
500 Total Elapsed Time = %4.2f
501 Total System Time = %4.2f
502 Total User Time = %4.2f
503 ", $cfg['caller'], $rtotal/1000000, $stotal/1000000, $utotal/1000000);
505 $retstring .= "\n
506 Real User System secs/ cumm
507 %Time (excl/cumm) (excl/cumm) (excl/cumm) Calls call s/call Memory Usage Name
508 --------------------------------------------------------------------------------------\n";
509 $l = 0;
510 $itotal = 0;
511 $percall = 0;
512 $cpercall = 0;
514 uksort($symbol_hash, $sort);
515 foreach (array_keys($symbol_hash) as $j) {
516 if(array_key_exists('i', $opt) && $symbol_type[$j] == 1) {
517 continue;
519 if ($l++ < $opt['O']) {
520 $pcnt = 100*($this->stimes[$j] + $this->utimes[$j])/($utotal + $stotal + $itotal);
521 $c_pcnt = 100* ($this->c_stimes[$j] + $this->c_utimes[$j])/($utotal + $stotal + $itotal);
522 $rsecs = $rtimes[$j]/1000000;
523 $ssecs = $this->stimes[$j]/1000000;
524 $usecs = $this->utimes[$j]/1000000;
525 $c_rsecs = $c_rtimes[$j]/1000000;
526 $c_ssecs = $this->c_stimes[$j]/1000000;
527 $c_usecs = $this->c_utimes[$j]/1000000;
528 $ncalls = $calls[$j];
529 if(array_key_exists('z', $opt)) {
530 $percall = ($usecs + $ssecs)/$ncalls;
531 $cpercall = ($c_usecs + $c_ssecs)/$ncalls;
532 if($utotal + $stotal) {
533 $pcnt = 100*($this->stimes[$j] + $this->utimes[$j])/($utotal + $stotal);
535 else {
536 $pcnt = 100;
539 if(array_key_exists('Z', $opt)) {
540 $percall = ($usecs + $ssecs)/$ncalls;
541 $cpercall = ($c_usecs + $c_ssecs)/$ncalls;
542 if($utotal + $stotal) {
543 $pcnt = 100*($this->c_stimes[$j] + $this->c_utimes[$j])/($utotal + $stotal);
545 else {
546 $pcnt = 100;
549 if(array_key_exists('r', $opt)) {
550 $percall = ($rsecs)/$ncalls;
551 $cpercall = ($c_rsecs)/$ncalls;
552 if($rtotal) {
553 $pcnt = 100*$rtimes[$j]/$rtotal;
555 else {
556 $pcnt = 100;
559 if(array_key_exists('R', $opt)) {
560 $percall = ($rsecs)/$ncalls;
561 $cpercall = ($c_rsecs)/$ncalls;
562 if($rtotal) {
563 $pcnt = 100*$c_rtimes[$j]/$rtotal;
565 else {
566 $pcnt = 100;
569 if(array_key_exists('u', $opt)) {
570 $percall = ($usecs)/$ncalls;
571 $cpercall = ($c_usecs)/$ncalls;
572 if($utotal) {
573 $pcnt = 100*$this->utimes[$j]/$utotal;
575 else {
576 $pcnt = 100;
579 if(array_key_exists('U', $opt)) {
580 $percall = ($usecs)/$ncalls;
581 $cpercall = ($c_usecs)/$ncalls;
582 if($utotal) {
583 $pcnt = 100*$this->c_utimes[$j]/$utotal;
585 else {
586 $pcnt = 100;
589 if(array_key_exists('s', $opt)) {
590 $percall = ($ssecs)/$ncalls;
591 $cpercall = ($c_ssecs)/$ncalls;
592 if($stotal) {
593 $pcnt = 100*$this->stimes[$j]/$stotal;
595 else {
596 $pcnt = 100;
599 if(array_key_exists('S', $opt)) {
600 $percall = ($ssecs)/$ncalls;
601 $cpercall = ($c_ssecs)/$ncalls;
602 if($stotal) {
603 $pcnt = 100*$this->c_stimes[$j]/$stotal;
605 else {
606 $pcnt = 100;
609 // $cpercall = ($c_usecs + $c_ssecs)/$ncalls;
610 $mem_usage = $this->mem[$j];
611 $name = $symbol_hash[$j];
612 $retstring .= sprintf("%3.01f %2.02f %2.02f %2.02f %2.02f %2.02f %2.02f %4d %2.04f %2.04f %12d %s\n",
613 $pcnt, $rsecs, $c_rsecs, $usecs, $c_usecs, $ssecs, $c_ssecs, $ncalls, $percall, $cpercall, $mem_usage, $name);
614 return $retstring;
617 return $retstring;
620 function usage() {
621 return <<<EOD
622 Profiler::get_profiling(\$args)
623 Sort options
624 -a Sort by alphabetic names of subroutines.
625 -l Sort by number of calls to subroutines
626 -m Sort by memory used in a function call.
627 -r Sort by real time spent in subroutines.
628 -R Sort by real time spent in subroutines (inclusive of child calls).
629 -s Sort by system time spent in subroutines.
630 -S Sort by system time spent in subroutines (inclusive of child calls).
631 -u Sort by user time spent in subroutines.
632 -U Sort by user time spent in subroutines (inclusive of child calls).
633 -v Sort by average amount of time spent in subroutines.
634 -z Sort by user+system time spent in subroutines. (default)
636 Display options
637 -c Display Real time elapsed alongside call tree.
638 -i Suppress reporting for php builtin functions
639 -O <cnt> Specifies maximum number of subroutines to display. (default 15)
640 -t Display compressed call tree.
641 -T Display uncompressed call tree.
643 EOD;
644 exit(1);
647 function parse_info($tag, $datasource, &$cfg) {
648 while($line = fgets($datasource)) {
649 $line = rtrim($line);
650 if(preg_match("/^END_$tag$/", $line)) {
651 break;
653 if(preg_match("/(\w+)=(.*)/", $line, $matches)) {
654 $cfg[$matches[1]] = $matches[2];
659 function num_cmp($a, $b) {
660 if (intval($a) > intval($b)) { return 1;}
661 elseif(intval($a) < intval($b)) { return -1;}
662 else {return 0;}
665 function by_time($a,$b) {
666 return $this->num_cmp(($this->stimes[$b] + $this->utimes[$b]),($this->stimes[$a] + $this->utimes[$a]));
669 function by_c_time($a,$b) {
670 return $this->num_cmp(($this->c_stimes[$b] + $this->c_utimes[$b]),($this->c_stimes[$a] + $this->c_utimes[$a]));
673 function by_avgcpu($a,$b) {
674 return $this->num_cmp(($this->stimes[$b] + $this->utimes[$b])/$this->calls[$b],($this->stimes[$a] + $this->utimes[$a])/$this->calls[$a]);
677 function by_calls($a, $b) {
678 return $this->num_cmp($this->calls[$b], $this->calls[$a]);
681 function by_rtime($a,$b) {
682 return $this->num_cmp($this->rtimes[$b], $this->rtimes[$a]);
685 function by_c_rtime($a,$b) {
686 return $this->num_cmp($this->c_rtimes[$b], $this->c_rtimes[$a]);
689 function by_stime($a,$b) {
690 return $this->num_cmp($this->stimes[$b], $this->stimes[$a]);
693 function by_c_stime($a,$b) {
694 return $this->num_cmp($this->c_stimes[$b], $this->c_stimes[$a]);
697 function by_utime($a,$b) {
698 return $this->num_cmp($this->utimes[$b], $this->utimes[$a]);
701 function by_c_utime($a,$b) {
702 return $this->num_cmp($this->c_utimes[$b], $this->c_utimes[$a]);
705 function by_mem($a, $b) {
706 return $this->num_cmp($this->mem[$b], $this->mem[$a]);