-
-
Save jeffery/2154198 to your computer and use it in GitHub Desktop.
A helper script for analyzing the output of a PHP xdebug code trace
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Analyzes the output of an XDebug script trace | |
* | |
* The original version can be found here: | |
* http://svn.xdebug.org/cgi-bin/viewvc.cgi/xdebug/trunk/contrib/tracefile-analyser.php?root=xdebug | |
* | |
* This version was created to work in PHP 5.2 | |
*/ | |
if ( $argc <= 1 || $argc > 4 ) | |
{ | |
showUsage(); | |
} | |
$fileName = $argv[1]; | |
$sortKey = 'time-own'; | |
$elements = 25; | |
if ( $argc > 2 ) | |
{ | |
$sortKey = $argv[2]; | |
if ( !in_array( $sortKey, array( 'calls', 'time-inclusive', 'memory-inclusive', 'time-own', 'memory-own' ) ) ) | |
{ | |
showUsage(); | |
} | |
} | |
if ( $argc > 3 ) | |
{ | |
$elements = (int) $argv[3]; | |
} | |
$o = new drXdebugTraceFileParser( $argv[1] ); | |
$o->parse(); | |
$functions = $o->getFunctions( $sortKey ); | |
// find longest function name | |
$maxLen = 0; | |
foreach( $functions as $name => $f ) | |
{ | |
if ( strlen( $name ) > $maxLen ) | |
{ | |
$maxLen = strlen( $name ); | |
} | |
} | |
echo "Showing the {$elements} most costly calls sorted by '{$sortKey}'.\n\n"; | |
echo " ", str_repeat( ' ', $maxLen - 8 ), " Inclusive Own\n"; | |
echo "function", str_repeat( ' ', $maxLen - 8 ), "#calls time memory time memory\n"; | |
echo "--------", str_repeat( '-', $maxLen - 8 ), "----------------------------------------\n"; | |
// display functions | |
$c = 0; | |
foreach( $functions as $name => $f ) | |
{ | |
$c++; | |
if ( $c > $elements ) | |
{ | |
break; | |
} | |
printf( "%-{$maxLen}s %5d %3.4f %8d %3.4f %8d\n", | |
$name, $f['calls'], | |
$f['time-inclusive'], $f['memory-inclusive'], | |
$f['time-own'], $f['memory-own'] ); | |
} | |
function showUsage() | |
{ | |
echo "usage:\n\tphp run-cli tracefile [sortkey] [elements]\n\n"; | |
echo "Allowed sortkeys:\n\tcalls, time-inclusive, memory-inclusive, time-own, memory-own\n"; | |
die(); | |
} | |
class drXdebugTraceFileParser | |
{ | |
protected $handle; | |
/** | |
* Stores the last function, time and memory for the entry point per | |
* stack depth. int=>array(string, float, int). | |
*/ | |
protected $stack; | |
/** | |
* Stores per function the total time and memory increases and calls | |
* string=>array(float, int, int) | |
*/ | |
protected $functions; | |
/** | |
* Stores which functions are on the stack | |
*/ | |
protected $stackFunctions; | |
public function __construct( $fileName ) | |
{ | |
$this->handle = fopen( $fileName, 'r' ); | |
if ( !$this->handle ) | |
{ | |
throw new Exception( "Can't open '$fileName'" ); | |
} | |
$this->stack[-1] = array( '', 0, 0, 0, 0 ); | |
$this->stack[ 0] = array( '', 0, 0, 0, 0 ); | |
$this->stackFunctions = array(); | |
} | |
public function parse() | |
{ | |
echo "\nparsing...\n"; | |
$c = 0; | |
$size = fstat( $this->handle ); | |
$size = $size['size']; | |
$read = 0; | |
while ( !feof( $this->handle ) ) | |
{ | |
$buffer = fgets( $this->handle, 4096 ); | |
$read += strlen( $buffer ); | |
$this->parseLine( $buffer ); | |
$c++; | |
if ( $c % 25000 === 0 ) | |
{ | |
printf( " (%5.2f%%)\n", ( $read / $size ) * 100 ); | |
} | |
} | |
echo "\nDone.\n\n"; | |
} | |
private function parseLine( $line ) | |
{ | |
/* | |
if ( preg_match( '@^Version: (.*)@', $line, $matches ) ) | |
{ | |
} | |
else if ( preg_match( '@^File format: (.*)@', $line, $matches ) ) | |
{ | |
} | |
else if ( preg_match( '@^TRACE.*@', $line, $matches ) ) | |
{ | |
} | |
else // assume a normal line | |
*/ | |
{ | |
$parts = explode( "\t", $line ); | |
if ( count( $parts ) < 5 ) | |
{ | |
return; | |
} | |
$depth = $parts[0]; | |
$funcNr = $parts[1]; | |
$time = $parts[3]; | |
$memory = $parts[4]; | |
if ( $parts[2] == '0' ) // function entry | |
{ | |
$funcName = $parts[5]; | |
$intFunc = $parts[6]; | |
$this->stack[$depth] = array( $funcName, $time, $memory, 0, 0 ); | |
array_push( $this->stackFunctions, $funcName ); | |
} | |
else if ( $parts[2] == '1' ) // function exit | |
{ | |
list( $funcName, $prevTime, $prevMem, $nestedTime, $nestedMemory ) = $this->stack[$depth]; | |
// collapse data onto functions array | |
$dTime = $time - $prevTime; | |
$dMemory = $memory - $prevMem; | |
$this->stack[$depth - 1][3] += $dTime; | |
$this->stack[$depth - 1][4] += $dMemory; | |
array_pop( $this->stackFunctions ); | |
$this->addToFunction( $funcName, $dTime, $dMemory, $nestedTime, $nestedMemory ); | |
} | |
} | |
} | |
protected function addToFunction( $function, $time, $memory, $nestedTime, $nestedMemory ) | |
{ | |
if ( !isset( $this->functions[$function] ) ) | |
{ | |
$this->functions[$function] = array( 0, 0, 0, 0, 0 ); | |
} | |
$elem = &$this->functions[$function]; | |
$elem[0]++; | |
if ( !in_array( $function, $this->stackFunctions ) ) { | |
$elem[1] += $time; | |
$elem[2] += $memory; | |
$elem[3] += $nestedTime; | |
$elem[4] += $nestedMemory; | |
} | |
} | |
public function getFunctions( $sortKey = null ) | |
{ | |
$result = array(); | |
foreach ( $this->functions as $name => $function ) | |
{ | |
$result[$name] = array( | |
'calls' => $function[0], | |
'time-inclusive' => $function[1], | |
'memory-inclusive' => $function[2], | |
'time-children' => $function[3], | |
'memory-children' => $function[4], | |
'time-own' => $function[1] - $function[3], | |
'memory-own' => $function[2] - $function[4] | |
); | |
} | |
if ( $sortKey !== null ) | |
{ | |
$sort = new SortByKey($sortKey); | |
$sort->sort($result); | |
} | |
return $result; | |
} | |
} | |
class SortByKey | |
{ | |
private $key; | |
public function __construct($key) { $this->key = $key; } | |
private function sortCallback ($a, $b) { | |
return ( $a[$this->key] > $b[$this->key] ) ? -1 : ( $a[$this->key] < $b[$this->key] ? 1 : 0 ); | |
} | |
public function sort( array &$data ) { | |
uasort($data, array($this, 'sortCallback')); | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment