diff options
Diffstat (limited to 'library')
-rw-r--r-- | library/FileHandler.php | 376 | ||||
-rw-r--r-- | library/Preprocessor.php | 277 | ||||
-rw-r--r-- | library/Reader.php | 492 | ||||
-rw-r--r-- | library/gprof2dot.py | 210 |
4 files changed, 673 insertions, 682 deletions
diff --git a/library/FileHandler.php b/library/FileHandler.php index 7bcce61..05c756c 100644 --- a/library/FileHandler.php +++ b/library/FileHandler.php @@ -7,200 +7,200 @@ require 'Preprocessor.php'; * @author Jacob Oettinger * @author Joakim Nygård */ -class Webgrind_FileHandler{ - - private static $singleton = null; - - - /** - * @return Singleton instance of the filehandler - */ - public static function getInstance(){ - if(self::$singleton==null) - self::$singleton = new self(); - return self::$singleton; - } - - private function __construct(){ - // Get list of files matching the defined format - $files = $this->getFiles(Webgrind_Config::xdebugOutputFormat(), Webgrind_Config::xdebugOutputDir()); - - // Get list of preprocessed files +class Webgrind_FileHandler +{ + + private static $singleton = null; + + + /** + * @return Singleton instance of the filehandler + */ + public static function getInstance() { + if (self::$singleton==null) + self::$singleton = new self(); + return self::$singleton; + } + + private function __construct() { + // Get list of files matching the defined format + $files = $this->getFiles(Webgrind_Config::xdebugOutputFormat(), Webgrind_Config::xdebugOutputDir()); + + // Get list of preprocessed files $prepFiles = $this->getPrepFiles('/\\'.Webgrind_Config::$preprocessedSuffix.'$/', Webgrind_Config::storageDir()); - // Loop over the preprocessed files. - foreach($prepFiles as $fileName=>$prepFile){ - $fileName = str_replace(Webgrind_Config::$preprocessedSuffix,'',$fileName); - - // If it is older than its corrosponding original: delete it. - // If it's original does not exist: delete it - if(!isset($files[$fileName]) || $files[$fileName]['mtime']>$prepFile['mtime'] ) - unlink($prepFile['absoluteFilename']); - else - $files[$fileName]['preprocessed'] = true; - } - // Sort by mtime - uasort($files,array($this,'mtimeCmp')); - - $this->files = $files; - } - - /** - * Get the value of the cmd header in $file - * - * @return void string - */ - private function getInvokeUrl($file){ - if (preg_match('/.webgrind$/', $file)) - return 'Webgrind internal'; - - // Grab name of invoked file. - $fp = fopen($file, 'r'); + // Loop over the preprocessed files. + foreach ($prepFiles as $fileName=>$prepFile) { + $fileName = str_replace(Webgrind_Config::$preprocessedSuffix,'',$fileName); + + // If it is older than its corrosponding original: delete it. + // If it's original does not exist: delete it + if (!isset($files[$fileName]) || $files[$fileName]['mtime']>$prepFile['mtime']) + unlink($prepFile['absoluteFilename']); + else + $files[$fileName]['preprocessed'] = true; + } + // Sort by mtime + uasort($files,array($this,'mtimeCmp')); + + $this->files = $files; + } + + /** + * Get the value of the cmd header in $file + * + * @return void string + */ + private function getInvokeUrl($file) { + if (preg_match('/.webgrind$/', $file)) + return 'Webgrind internal'; + + // Grab name of invoked file. + $fp = fopen($file, 'r'); $invokeUrl = ''; - while ((($line = fgets($fp)) !== FALSE) && !strlen($invokeUrl)){ - if (preg_match('/^cmd: (.*)$/', $line, $parts)){ + while ((($line = fgets($fp)) !== FALSE) && !strlen($invokeUrl)) { + if (preg_match('/^cmd: (.*)$/', $line, $parts)) { $invokeUrl = isset($parts[1]) ? $parts[1] : ''; } } fclose($fp); - if (!strlen($invokeUrl)) + if (!strlen($invokeUrl)) $invokeUrl = 'Unknown!'; - return $invokeUrl; - } - - /** - * List of files in $dir whose filename has the format $format - * - * @return array Files - */ - private function getFiles($format, $dir){ - $list = preg_grep($format,scandir($dir)); - $files = array(); - - $scriptFilename = $_SERVER['SCRIPT_FILENAME']; - - # Moved this out of loop to run faster - if (function_exists('xdebug_get_profiler_filename')) - $selfFile = realpath(xdebug_get_profiler_filename()); - else - $selfFile = ''; - - foreach($list as $file){ - $absoluteFilename = $dir.$file; - - // Exclude webgrind preprocessed files - if (false !== strstr($absoluteFilename, Webgrind_Config::$preprocessedSuffix)) - continue; - - // Make sure that script never parses the profile currently being generated. (infinite loop) - if ($selfFile == realpath($absoluteFilename)) - continue; - - $invokeUrl = rtrim($this->getInvokeUrl($absoluteFilename)); - if (Webgrind_Config::$hideWebgrindProfiles && $invokeUrl == dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'index.php') - continue; - - - $files[$file] = array('absoluteFilename' => $absoluteFilename, - 'mtime' => filemtime($absoluteFilename), - 'preprocessed' => false, - 'invokeUrl' => $invokeUrl, - 'filesize' => $this->bytestostring(filesize($absoluteFilename)) - ); - } - return $files; - } - - /** - * List of files in $dir whose filename has the format $format - * - * @return array Files - */ - private function getPrepFiles($format, $dir){ - $list = preg_grep($format,scandir($dir)); - $files = array(); - - $scriptFilename = $_SERVER['SCRIPT_FILENAME']; - - foreach($list as $file){ - $absoluteFilename = $dir.$file; - - // Make sure that script does not include the profile currently being generated. (infinite loop) - if (function_exists('xdebug_get_profiler_filename') && realpath(xdebug_get_profiler_filename())==realpath($absoluteFilename)) - continue; - - $files[$file] = array('absoluteFilename' => $absoluteFilename, - 'mtime' => filemtime($absoluteFilename), - 'preprocessed' => true, - 'filesize' => $this->bytestostring(filesize($absoluteFilename)) - ); - } - return $files; - } - /** - * Get list of available trace files. Optionally including traces of the webgrind script it self - * - * @return array Files - */ - public function getTraceList(){ - $result = array(); - foreach($this->files as $fileName=>$file){ - $result[] = array('filename' => $fileName, - 'invokeUrl' => str_replace($_SERVER['DOCUMENT_ROOT'].'/', '', $file['invokeUrl']), - 'filesize' => $file['filesize'], - 'mtime' => date(Webgrind_Config::$dateFormat, $file['mtime']) - ); - } - return $result; - } - - /** - * Get a trace reader for the specific file. - * - * If the file has not been preprocessed yet this will be done first. - * - * @param string File to read - * @param Cost format for the reader - * @return Webgrind_Reader Reader for $file - */ - public function getTraceReader($file, $costFormat){ - $prepFile = Webgrind_Config::storageDir().$file.Webgrind_Config::$preprocessedSuffix; - try{ - $r = new Webgrind_Reader($prepFile, $costFormat); - } catch (Exception $e){ - // Preprocessed file does not exist or other error - Webgrind_Preprocessor::parse(Webgrind_Config::xdebugOutputDir().$file, $prepFile); - $r = new Webgrind_Reader($prepFile, $costFormat); - } - return $r; - } - - /** - * Comparison function for sorting - * - * @return boolean - */ - private function mtimeCmp($a, $b){ - if ($a['mtime'] == $b['mtime']) - return 0; - - return ($a['mtime'] > $b['mtime']) ? -1 : 1; - } - - /** - * Present a size (in bytes) as a human-readable value - * - * @param int $size size (in bytes) - * @param int $precision number of digits after the decimal point - * @return string - */ - private function bytestostring($size, $precision = 0) { - $sizes = array('YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'KB', 'B'); - $total = count($sizes); - - while($total-- && $size > 1024) { - $size /= 1024; - } - return round($size, $precision).$sizes[$total]; + return $invokeUrl; + } + + /** + * List of files in $dir whose filename has the format $format + * + * @return array Files + */ + private function getFiles($format, $dir) { + $list = preg_grep($format,scandir($dir)); + $files = array(); + + $scriptFilename = $_SERVER['SCRIPT_FILENAME']; + + # Moved this out of loop to run faster + if (function_exists('xdebug_get_profiler_filename')) + $selfFile = realpath(xdebug_get_profiler_filename()); + else + $selfFile = ''; + + foreach ($list as $file) { + $absoluteFilename = $dir.$file; + + // Exclude webgrind preprocessed files + if (false !== strstr($absoluteFilename, Webgrind_Config::$preprocessedSuffix)) + continue; + + // Make sure that script never parses the profile currently being generated. (infinite loop) + if ($selfFile == realpath($absoluteFilename)) + continue; + + $invokeUrl = rtrim($this->getInvokeUrl($absoluteFilename)); + if (Webgrind_Config::$hideWebgrindProfiles && $invokeUrl == dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'index.php') + continue; + + $files[$file] = array('absoluteFilename' => $absoluteFilename, + 'mtime' => filemtime($absoluteFilename), + 'preprocessed' => false, + 'invokeUrl' => $invokeUrl, + 'filesize' => $this->bytestostring(filesize($absoluteFilename)) + ); + } + return $files; + } + + /** + * List of files in $dir whose filename has the format $format + * + * @return array Files + */ + private function getPrepFiles($format, $dir) { + $list = preg_grep($format,scandir($dir)); + $files = array(); + + $scriptFilename = $_SERVER['SCRIPT_FILENAME']; + + foreach ($list as $file) { + $absoluteFilename = $dir.$file; + + // Make sure that script does not include the profile currently being generated. (infinite loop) + if (function_exists('xdebug_get_profiler_filename') && realpath(xdebug_get_profiler_filename())==realpath($absoluteFilename)) + continue; + + $files[$file] = array('absoluteFilename' => $absoluteFilename, + 'mtime' => filemtime($absoluteFilename), + 'preprocessed' => true, + 'filesize' => $this->bytestostring(filesize($absoluteFilename)) + ); + } + return $files; + } + /** + * Get list of available trace files. Optionally including traces of the webgrind script it self + * + * @return array Files + */ + public function getTraceList() { + $result = array(); + foreach ($this->files as $fileName=>$file) { + $result[] = array('filename' => $fileName, + 'invokeUrl' => str_replace($_SERVER['DOCUMENT_ROOT'].'/', '', $file['invokeUrl']), + 'filesize' => $file['filesize'], + 'mtime' => date(Webgrind_Config::$dateFormat, $file['mtime']) + ); + } + return $result; + } + + /** + * Get a trace reader for the specific file. + * + * If the file has not been preprocessed yet this will be done first. + * + * @param string File to read + * @param Cost format for the reader + * @return Webgrind_Reader Reader for $file + */ + public function getTraceReader($file, $costFormat) { + $prepFile = Webgrind_Config::storageDir().$file.Webgrind_Config::$preprocessedSuffix; + try { + $r = new Webgrind_Reader($prepFile, $costFormat); + } catch (Exception $e) { + // Preprocessed file does not exist or other error + Webgrind_Preprocessor::parse(Webgrind_Config::xdebugOutputDir().$file, $prepFile); + $r = new Webgrind_Reader($prepFile, $costFormat); + } + return $r; + } + + /** + * Comparison function for sorting + * + * @return boolean + */ + private function mtimeCmp($a, $b) { + if ($a['mtime'] == $b['mtime']) + return 0; + + return ($a['mtime'] > $b['mtime']) ? -1 : 1; + } + + /** + * Present a size (in bytes) as a human-readable value + * + * @param int $size size (in bytes) + * @param int $precision number of digits after the decimal point + * @return string + */ + private function bytestostring($size, $precision = 0) { + $sizes = array('YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'KB', 'B'); + $total = count($sizes); + + while ($total-- && $size > 1024) { + $size /= 1024; + } + return round($size, $precision).$sizes[$total]; } -}
\ No newline at end of file +} diff --git a/library/Preprocessor.php b/library/Preprocessor.php index b29581c..9205a62 100644 --- a/library/Preprocessor.php +++ b/library/Preprocessor.php @@ -1,70 +1,68 @@ <?php /** * Class for preprocessing callgrind files. - * - * Information from the callgrind file is extracted and written in a binary format for + * + * Information from the callgrind file is extracted and written in a binary format for * fast random access. - * + * * @see http://code.google.com/p/webgrind/wiki/PreprocessedFormat * @see http://valgrind.org/docs/manual/cl-format.html * @package Webgrind * @author Jacob Oettinger - **/ + */ class Webgrind_Preprocessor { - /** - * Fileformat version. Embedded in the output for parsers to use. - */ - const FILE_FORMAT_VERSION = 7; - - /** - * Binary number format used. - * @see http://php.net/pack - */ - const NR_FORMAT = 'V'; - - /** - * Size, in bytes, of the above number format - */ - const NR_SIZE = 4; - - /** - * String name of main function - */ - const ENTRY_POINT = '{main}'; - - - /** - * Extract information from $inFile and store in preprocessed form in $outFile - * - * @param string $inFile Callgrind file to read - * @param string $outFile File to write preprocessed data to - * @return void - **/ - static function parse($inFile, $outFile) - { - $in = @fopen($inFile, 'rb'); - if(!$in) - throw new Exception('Could not open '.$inFile.' for reading.'); - $out = @fopen($outFile, 'w+b'); - if(!$out) - throw new Exception('Could not open '.$outFile.' for writing.'); - - $nextFuncNr = 0; - $functions = array(); - $headers = array(); - $calls = array(); - - - // Read information into memory - while(($line = fgets($in))){ - if(substr($line,0,3)==='fl='){ - // Found invocation of function. Read functionname - list($function) = fscanf($in,"fn=%[^\n\r]s"); - if(!isset($functions[$function])){ - $functions[$function] = array( - 'filename' => substr(trim($line),3), + /** + * Fileformat version. Embedded in the output for parsers to use. + */ + const FILE_FORMAT_VERSION = 7; + + /** + * Binary number format used. + * @see http://php.net/pack + */ + const NR_FORMAT = 'V'; + + /** + * Size, in bytes, of the above number format + */ + const NR_SIZE = 4; + + /** + * String name of main function + */ + const ENTRY_POINT = '{main}'; + + + /** + * Extract information from $inFile and store in preprocessed form in $outFile + * + * @param string $inFile Callgrind file to read + * @param string $outFile File to write preprocessed data to + * @return void + */ + static function parse($inFile, $outFile) { + $in = @fopen($inFile, 'rb'); + if(!$in) + throw new Exception('Could not open '.$inFile.' for reading.'); + $out = @fopen($outFile, 'w+b'); + if(!$out) + throw new Exception('Could not open '.$outFile.' for writing.'); + + $nextFuncNr = 0; + $functions = array(); + $headers = array(); + $calls = array(); + + // Read information into memory + while (($line = fgets($in))) { + if (substr($line,0,3)==='fl=') { + // Found invocation of function. Read functionname + list($function) = fscanf($in,"fn=%[^\n\r]s"); + if (!isset($functions[$function])) { + $functions[$function] = array( + 'filename' => substr(trim($line),3), 'invocationCount' => 0, 'nr' => $nextFuncNr++, 'count' => 0, @@ -72,90 +70,85 @@ class Webgrind_Preprocessor 'summedInclusiveCost' => 0, 'calledFromInformation' => array(), 'subCallInformation' => array() - ); - } - $functions[$function]['invocationCount']++; - // Special case for ENTRY_POINT - it contains summary header - if(self::ENTRY_POINT == $function){ - fgets($in); - $headers[] = fgets($in); - fgets($in); - } - // Cost line - list($lnr, $cost) = fscanf($in,"%d %d"); - $functions[$function]['line'] = $lnr; - $functions[$function]['summedSelfCost'] += $cost; - $functions[$function]['summedInclusiveCost'] += $cost; - } else if(substr($line,0,4)==='cfn=') { - - // Found call to function. ($function should contain function call originates from) - $calledFunctionName = substr(trim($line),4); - // Skip call line - fgets($in); - // Cost line - list($lnr, $cost) = fscanf($in,"%d %d"); - - $functions[$function]['summedInclusiveCost'] += $cost; - - if(!isset($functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr])) - $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr] = array('functionNr'=>$functions[$function]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0); - - $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['callCount']++; - $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['summedCallCost'] += $cost; - - if(!isset($functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr])){ - $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr] = array('functionNr'=>$functions[$calledFunctionName]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0); - } - - $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['callCount']++; - $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['summedCallCost'] += $cost; - - - } else if(strpos($line,': ')!==false){ - // Found header - $headers[] = $line; - } - } - - - // Write output - $functionCount = sizeof($functions); - fwrite($out, pack(self::NR_FORMAT.'*', self::FILE_FORMAT_VERSION, 0, $functionCount)); - // Make room for function addresses - fseek($out,self::NR_SIZE*$functionCount, SEEK_CUR); - $functionAddresses = array(); - foreach($functions as $functionName => $function){ - $functionAddresses[] = ftell($out); - $calledFromCount = sizeof($function['calledFromInformation']); - $subCallCount = sizeof($function['subCallInformation']); - fwrite($out, pack(self::NR_FORMAT.'*', $function['line'], $function['summedSelfCost'], $function['summedInclusiveCost'], $function['invocationCount'], $calledFromCount, $subCallCount)); - // Write called from information - foreach((array)$function['calledFromInformation'] as $call){ - fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost'])); - } - // Write sub call information - foreach((array)$function['subCallInformation'] as $call){ - fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost'])); - } - - fwrite($out, $function['filename']."\n".$functionName."\n"); - } - $headersPos = ftell($out); - // Write headers - foreach($headers as $header){ - fwrite($out,$header); - } - - // Write addresses - fseek($out,self::NR_SIZE, SEEK_SET); - fwrite($out, pack(self::NR_FORMAT, $headersPos)); - // Skip function count - fseek($out,self::NR_SIZE, SEEK_CUR); - // Write function addresses - foreach($functionAddresses as $address){ - fwrite($out, pack(self::NR_FORMAT, $address)); - } - - } - + ); + } + $functions[$function]['invocationCount']++; + // Special case for ENTRY_POINT - it contains summary header + if (self::ENTRY_POINT == $function) { + fgets($in); + $headers[] = fgets($in); + fgets($in); + } + // Cost line + list($lnr, $cost) = fscanf($in,"%d %d"); + $functions[$function]['line'] = $lnr; + $functions[$function]['summedSelfCost'] += $cost; + $functions[$function]['summedInclusiveCost'] += $cost; + } elseif (substr($line,0,4)==='cfn=') { + // Found call to function. ($function should contain function call originates from) + $calledFunctionName = substr(trim($line),4); + // Skip call line + fgets($in); + // Cost line + list($lnr, $cost) = fscanf($in,"%d %d"); + + $functions[$function]['summedInclusiveCost'] += $cost; + + if (!isset($functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr])) + $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr] = array('functionNr'=>$functions[$function]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0); + + $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['callCount']++; + $functions[$calledFunctionName]['calledFromInformation'][$function.':'.$lnr]['summedCallCost'] += $cost; + + if (!isset($functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr])){ + $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr] = array('functionNr'=>$functions[$calledFunctionName]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0); + } + + $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['callCount']++; + $functions[$function]['subCallInformation'][$calledFunctionName.':'.$lnr]['summedCallCost'] += $cost; + } elseif (strpos($line,': ')!==false) { + // Found header + $headers[] = $line; + } + } + + // Write output + $functionCount = sizeof($functions); + fwrite($out, pack(self::NR_FORMAT.'*', self::FILE_FORMAT_VERSION, 0, $functionCount)); + // Make room for function addresses + fseek($out,self::NR_SIZE*$functionCount, SEEK_CUR); + $functionAddresses = array(); + foreach ($functions as $functionName => $function) { + $functionAddresses[] = ftell($out); + $calledFromCount = sizeof($function['calledFromInformation']); + $subCallCount = sizeof($function['subCallInformation']); + fwrite($out, pack(self::NR_FORMAT.'*', $function['line'], $function['summedSelfCost'], $function['summedInclusiveCost'], $function['invocationCount'], $calledFromCount, $subCallCount)); + // Write called from information + foreach ((array)$function['calledFromInformation'] as $call) { + fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost'])); + } + // Write sub call information + foreach ((array)$function['subCallInformation'] as $call) { + fwrite($out, pack(self::NR_FORMAT.'*', $call['functionNr'], $call['line'], $call['callCount'], $call['summedCallCost'])); + } + + fwrite($out, $function['filename']."\n".$functionName."\n"); + } + $headersPos = ftell($out); + // Write headers + foreach ($headers as $header) { + fwrite($out,$header); + } + + // Write addresses + fseek($out,self::NR_SIZE, SEEK_SET); + fwrite($out, pack(self::NR_FORMAT, $headersPos)); + // Skip function count + fseek($out,self::NR_SIZE, SEEK_CUR); + // Write function addresses + foreach ($functionAddresses as $address) { + fwrite($out, pack(self::NR_FORMAT, $address)); + } + } + } diff --git a/library/Reader.php b/library/Reader.php index 35c5113..cb306b5 100644 --- a/library/Reader.php +++ b/library/Reader.php @@ -1,269 +1,267 @@ <?php /** * Class for reading datafiles generated by Webgrind_Preprocessor - * + * * @package Webgrind * @author Jacob Oettinger - **/ + */ class Webgrind_Reader { - /** - * File format version that this reader understands - */ - const FILE_FORMAT_VERSION = 7; - - /** - * Binary number format used. - * @see http://php.net/pack - */ - const NR_FORMAT = 'V'; - - /** - * Size, in bytes, of the above number format - */ - const NR_SIZE = 4; - - /** - * Length of a call information block - */ - const CALLINFORMATION_LENGTH = 4; - /** - * Length of a function information block - */ - const FUNCTIONINFORMATION_LENGTH = 6; - + * File format version that this reader understands + */ + const FILE_FORMAT_VERSION = 7; + + /** + * Binary number format used. + * @see http://php.net/pack + */ + const NR_FORMAT = 'V'; + + /** + * Size, in bytes, of the above number format + */ + const NR_SIZE = 4; + + /** + * Length of a call information block + */ + const CALLINFORMATION_LENGTH = 4; + + /** + * Length of a function information block + */ + const FUNCTIONINFORMATION_LENGTH = 6; + + /** + * Address of the headers in the data file + * + * @var int + */ + private $headersPos; + + /** + * Array of addresses pointing to information about functions + * + * @var array + */ + private $functionPos; + + /** + * Array of headers + * + * @var array + */ + private $headers=null; + + /** + * Format to return costs in + * + * @var string + */ + private $costFormat; + + + /** + * Constructor + * @param string Data file to read + * @param string Format to return costs in + */ + function __construct($dataFile, $costFormat) { + $this->fp = @fopen($dataFile,'rb'); + if (!$this->fp) + throw new Exception('Error opening file!'); + + $this->costFormat = $costFormat; + $this->init(); + } + + /** + * Initializes the parser by reading initial information. + * + * Throws an exception if the file version does not match the readers version + * + * @return void + * @throws Exception + */ + private function init() { + list($version, $this->headersPos, $functionCount) = $this->read(3); + if ($version!=self::FILE_FORMAT_VERSION) + throw new Exception('Datafile not correct version. Found '.$version.' expected '.self::FILE_FORMAT_VERSION); + $this->functionPos = $this->read($functionCount); + } + + /** + * Returns number of functions + * @return int + */ + function getFunctionCount(){ + return count($this->functionPos); + } + /** - * Address of the headers in the data file - * - * @var int - */ - private $headersPos; - - /** - * Array of addresses pointing to information about functions - * - * @var array - */ - private $functionPos; - - /** - * Array of headers - * - * @var array - */ - private $headers=null; - - /** - * Format to return costs in - * - * @var string - */ - private $costFormat; - - - /** - * Constructor - * @param string Data file to read - * @param string Format to return costs in - **/ - function __construct($dataFile, $costFormat){ - $this->fp = @fopen($dataFile,'rb'); - if(!$this->fp) - throw new Exception('Error opening file!'); - - $this->costFormat = $costFormat; - $this->init(); - } - - /** - * Initializes the parser by reading initial information. - * - * Throws an exception if the file version does not match the readers version - * - * @return void - * @throws Exception - */ - private function init(){ - list($version, $this->headersPos, $functionCount) = $this->read(3); - if($version!=self::FILE_FORMAT_VERSION) - throw new Exception('Datafile not correct version. Found '.$version.' expected '.self::FILE_FORMAT_VERSION); - $this->functionPos = $this->read($functionCount); - } - - /** - * Returns number of functions - * @return int - */ - function getFunctionCount(){ - return count($this->functionPos); - } - - /** - * Returns information about function with nr $nr - * - * @param $nr int Function number - * @return array Function information - */ - function getFunctionInfo($nr){ - $this->seek($this->functionPos[$nr]); - - list($line, $summedSelfCost, $summedInclusiveCost, $invocationCount, $calledFromCount, $subCallCount) = $this->read(self::FUNCTIONINFORMATION_LENGTH); - - $this->seek(self::NR_SIZE*self::CALLINFORMATION_LENGTH*($calledFromCount+$subCallCount), SEEK_CUR); - $file = $this->readLine(); - $function = $this->readLine(); - - $result = array( - 'file'=>$file, - 'line'=>$line, - 'functionName'=>$function, - 'summedSelfCost'=>$summedSelfCost, - 'summedInclusiveCost'=>$summedInclusiveCost, - 'invocationCount'=>$invocationCount, - 'calledFromInfoCount'=>$calledFromCount, - 'subCallInfoCount'=>$subCallCount - ); + * Returns information about function with nr $nr + * + * @param $nr int Function number + * @return array Function information + */ + function getFunctionInfo($nr) { + $this->seek($this->functionPos[$nr]); + + list($line, $summedSelfCost, $summedInclusiveCost, $invocationCount, $calledFromCount, $subCallCount) = $this->read(self::FUNCTIONINFORMATION_LENGTH); + + $this->seek(self::NR_SIZE*self::CALLINFORMATION_LENGTH*($calledFromCount+$subCallCount), SEEK_CUR); + $file = $this->readLine(); + $function = $this->readLine(); + + $result = array( + 'file'=>$file, + 'line'=>$line, + 'functionName'=>$function, + 'summedSelfCost'=>$summedSelfCost, + 'summedInclusiveCost'=>$summedInclusiveCost, + 'invocationCount'=>$invocationCount, + 'calledFromInfoCount'=>$calledFromCount, + 'subCallInfoCount'=>$subCallCount + ); $result['summedSelfCost'] = $this->formatCost($result['summedSelfCost']); $result['summedInclusiveCost'] = $this->formatCost($result['summedInclusiveCost']); - return $result; - } - - /** - * Returns information about positions where a function has been called from - * - * @param $functionNr int Function number - * @param $calledFromNr int Called from position nr - * @return array Called from information - */ - function getCalledFromInfo($functionNr, $calledFromNr){ - $this->seek( - $this->functionPos[$functionNr] - + self::NR_SIZE - * (self::CALLINFORMATION_LENGTH * $calledFromNr + self::FUNCTIONINFORMATION_LENGTH) - ); - - $data = $this->read(self::CALLINFORMATION_LENGTH); - - $result = array( - 'functionNr'=>$data[0], - 'line'=>$data[1], - 'callCount'=>$data[2], - 'summedCallCost'=>$data[3] - ); - + return $result; + } + + /** + * Returns information about positions where a function has been called from + * + * @param $functionNr int Function number + * @param $calledFromNr int Called from position nr + * @return array Called from information + */ + function getCalledFromInfo($functionNr, $calledFromNr){ + $this->seek( + $this->functionPos[$functionNr] + + self::NR_SIZE + * (self::CALLINFORMATION_LENGTH * $calledFromNr + self::FUNCTIONINFORMATION_LENGTH) + ); + + $data = $this->read(self::CALLINFORMATION_LENGTH); + + $result = array( + 'functionNr'=>$data[0], + 'line'=>$data[1], + 'callCount'=>$data[2], + 'summedCallCost'=>$data[3] + ); + $result['summedCallCost'] = $this->formatCost($result['summedCallCost']); - return $result; - } - - /** - * Returns information about functions called by a function - * - * @param $functionNr int Function number - * @param $subCallNr int Sub call position nr - * @return array Sub call information - */ - function getSubCallInfo($functionNr, $subCallNr){ - // Sub call count is the second last number in the FUNCTION_INFORMATION block - $this->seek($this->functionPos[$functionNr] + self::NR_SIZE * (self::FUNCTIONINFORMATION_LENGTH - 2)); - $calledFromInfoCount = $this->read(); - $this->seek( ( ($calledFromInfoCount+$subCallNr) * self::CALLINFORMATION_LENGTH + 1 ) * self::NR_SIZE,SEEK_CUR); - $data = $this->read(self::CALLINFORMATION_LENGTH); - - $result = array( - 'functionNr'=>$data[0], - 'line'=>$data[1], - 'callCount'=>$data[2], - 'summedCallCost'=>$data[3] - ); - + return $result; + } + + /** + * Returns information about functions called by a function + * + * @param $functionNr int Function number + * @param $subCallNr int Sub call position nr + * @return array Sub call information + */ + function getSubCallInfo($functionNr, $subCallNr) { + // Sub call count is the second last number in the FUNCTION_INFORMATION block + $this->seek($this->functionPos[$functionNr] + self::NR_SIZE * (self::FUNCTIONINFORMATION_LENGTH - 2)); + $calledFromInfoCount = $this->read(); + $this->seek( ( ($calledFromInfoCount+$subCallNr) * self::CALLINFORMATION_LENGTH + 1 ) * self::NR_SIZE,SEEK_CUR); + $data = $this->read(self::CALLINFORMATION_LENGTH); + + $result = array( + 'functionNr'=>$data[0], + 'line'=>$data[1], + 'callCount'=>$data[2], + 'summedCallCost'=>$data[3] + ); + $result['summedCallCost'] = $this->formatCost($result['summedCallCost']); - return $result; - } - - /** - * Returns array of defined headers - * - * @return array Headers in format array('header name'=>'header value') - */ - function getHeaders(){ - if($this->headers==null){ // Cache headers - $this->seek($this->headersPos); - $this->headers['runs'] = 0; - while($line=$this->readLine()){ - $parts = explode(': ',$line); - if ($parts[0] == 'summary') { - $this->headers['runs']++; - if(isset($this->headers['summary'])) + return $result; + } + + /** + * Returns array of defined headers + * + * @return array Headers in format array('header name'=>'header value') + */ + function getHeaders() { + if ($this->headers==null) { // Cache headers + $this->seek($this->headersPos); + $this->headers['runs'] = 0; + while ($line=$this->readLine()) { + $parts = explode(': ',$line); + if ($parts[0] == 'summary') { + $this->headers['runs']++; + if (isset($this->headers['summary'])) $this->headers['summary'] += $parts[1]; else $this->headers['summary'] = $parts[1]; - } else { + } else { $this->headers[$parts[0]] = $parts[1]; } - } - } - return $this->headers; - } - - /** - * Returns value of a single header - * - * @return string Header value - */ - function getHeader($header){ - $headers = $this->getHeaders(); - return isset($headers[$header]) ? $headers[$header] : ''; - } - - /** - * Formats $cost using the format in $this->costFormat or optionally the format given as input - * - * @param int $cost Cost - * @param string $format 'percent', 'msec' or 'usec' - * @return int Formatted cost - */ - function formatCost($cost, $format=null) - { - if($format==null) - $format = $this->costFormat; - - if ($format == 'percent') { - $total = $this->getHeader('summary'); - $result = ($total==0) ? 0 : ($cost*100)/$total; - return number_format($result, 2, '.', ''); - } - - if ($format == 'msec') { - return round($cost/1000, 0); - } - - // Default usec - return $cost; - - } - - private function read($numbers=1){ - $values = unpack(self::NR_FORMAT.$numbers,fread($this->fp,self::NR_SIZE*$numbers)); - if($numbers==1) - return $values[1]; - else - return array_values($values); // reindex and return - } - - private function readLine(){ - $result = fgets($this->fp); - if($result) - return trim($result); - else - return $result; - } - - private function seek($offset, $whence=SEEK_SET){ - return fseek($this->fp, $offset, $whence); - } - + } + } + return $this->headers; + } + + /** + * Returns value of a single header + * + * @return string Header value + */ + function getHeader($header) { + $headers = $this->getHeaders(); + return isset($headers[$header]) ? $headers[$header] : ''; + } + + /** + * Formats $cost using the format in $this->costFormat or optionally the format given as input + * + * @param int $cost Cost + * @param string $format 'percent', 'msec' or 'usec' + * @return int Formatted cost + */ + function formatCost($cost, $format=null) { + if ($format==null) + $format = $this->costFormat; + + if ($format == 'percent') { + $total = $this->getHeader('summary'); + $result = ($total==0) ? 0 : ($cost*100)/$total; + return number_format($result, 2, '.', ''); + } + + if ($format == 'msec') { + return round($cost/1000, 0); + } + + // Default usec + return $cost; + } + + private function read($numbers=1) { + $values = unpack(self::NR_FORMAT.$numbers,fread($this->fp,self::NR_SIZE*$numbers)); + if ($numbers==1) + return $values[1]; + else + return array_values($values); // reindex and return + } + + private function readLine() { + $result = fgets($this->fp); + if ($result) + return trim($result); + else + return $result; + } + + private function seek($offset, $whence=SEEK_SET) { + return fseek($this->fp, $offset, $whence); + } + } diff --git a/library/gprof2dot.py b/library/gprof2dot.py index dc7bc8d..99a8c1b 100644 --- a/library/gprof2dot.py +++ b/library/gprof2dot.py @@ -79,7 +79,7 @@ def ratio(numerator, denominator): class UndefinedEvent(Exception): """Raised when attempting to get an event which is undefined.""" - + def __init__(self, event): Exception.__init__(self) self.event = event @@ -111,7 +111,7 @@ class Event(object): assert val1 is not None assert val2 is not None return self._aggregator(val1, val2) - + def format(self, val): """Format an event value.""" assert val is not None @@ -145,13 +145,13 @@ class Object(object): def __contains__(self, event): return event in self.events - + def __getitem__(self, event): try: return self.events[event] except KeyError: raise UndefinedEvent(event) - + def __setitem__(self, event, value): if value is None: if event in self.events: @@ -162,7 +162,7 @@ class Object(object): class Call(Object): """A call between functions. - + There should be at most one call object for every pair of functions. """ @@ -186,7 +186,7 @@ class Function(Object): self.called = None self.weight = None self.cycle = None - + def add_call(self, call): if call.callee_id in self.calls: sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id))) @@ -270,7 +270,7 @@ class Profile(Object): sys.stderr.write("Cycle:\n") for member in cycle.functions: sys.stderr.write("\tFunction %s\n" % member.name) - + def _tarjan(self, function, order, stack, orders, lowlinks, visited): """Tarjan's strongly connected components algorithm. @@ -349,7 +349,7 @@ class Profile(Object): if call.callee_id != function.id: assert call.ratio is not None - # Aggregate the input for each cycle + # Aggregate the input for each cycle for cycle in self.cycles: total = inevent.null() for function in self.functions.itervalues(): @@ -374,7 +374,7 @@ class Profile(Object): total += self._integrate_call(call, outevent, inevent) function[outevent] = total return function[outevent] - + def _integrate_call(self, call, outevent, inevent): assert outevent not in call assert call.ratio is not None @@ -396,7 +396,7 @@ class Profile(Object): subtotal += self._integrate_call(call, outevent, inevent) total += subtotal cycle[outevent] = total - + # Compute the time propagated to callers of this cycle callees = {} for function in self.functions.itervalues(): @@ -408,7 +408,7 @@ class Profile(Object): callees[callee] += call.ratio except KeyError: callees[callee] = call.ratio - + for member in cycle.functions: member[outevent] = outevent.null() @@ -509,11 +509,11 @@ class Profile(Object): if TOTAL_TIME_RATIO in call: # handle exact cases first - call.weight = call[TOTAL_TIME_RATIO] + call.weight = call[TOTAL_TIME_RATIO] else: try: # make a safe estimate - call.weight = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO]) + call.weight = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO]) except UndefinedEvent: pass @@ -530,7 +530,7 @@ class Profile(Object): call = function.calls[callee_id] if callee_id not in self.functions or call.weight is not None and call.weight < edge_thres: del function.calls[callee_id] - + def dump(self): for function in self.functions.itervalues(): sys.stderr.write('Function %s:\n' % (function.name,)) @@ -557,7 +557,7 @@ class Struct: if attrs is None: attrs = {} self.__dict__['_attrs'] = attrs - + def __getattr__(self, name): try: return self._attrs[name] @@ -572,7 +572,7 @@ class Struct: def __repr__(self): return repr(self._attrs) - + class ParseError(Exception): """Raised when parsing to signal mismatches.""" @@ -595,7 +595,7 @@ class Parser: def parse(self): raise NotImplementedError - + class LineParser(Parser): """Base class for parsers that read line-based formats.""" @@ -664,21 +664,21 @@ class XmlTokenizer: self.index = 0 self.final = False self.skip_ws = skip_ws - + self.character_pos = 0, 0 self.character_data = '' - + self.parser = xml.parsers.expat.ParserCreate() self.parser.StartElementHandler = self.handle_element_start self.parser.EndElementHandler = self.handle_element_end self.parser.CharacterDataHandler = self.handle_character_data - + def handle_element_start(self, name, attributes): self.finish_character_data() line, column = self.pos() token = XmlToken(XML_ELEMENT_START, name, attributes, line, column) self.tokens.append(token) - + def handle_element_end(self, name): self.finish_character_data() line, column = self.pos() @@ -689,15 +689,15 @@ class XmlTokenizer: if not self.character_data: self.character_pos = self.pos() self.character_data += data - + def finish_character_data(self): if self.character_data: - if not self.skip_ws or not self.character_data.isspace(): + if not self.skip_ws or not self.character_data.isspace(): line, column = self.character_pos token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column) self.tokens.append(token) self.character_data = '' - + def next(self): size = 16*1024 while self.index >= len(self.tokens) and not self.final: @@ -742,13 +742,13 @@ class XmlParser(Parser): Parser.__init__(self) self.tokenizer = XmlTokenizer(fp) self.consume() - + def consume(self): self.token = self.tokenizer.next() def match_element_start(self, name): return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name - + def match_element_end(self, name): return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name @@ -762,7 +762,7 @@ class XmlParser(Parser): attrs = self.token.attrs self.consume() return attrs - + def element_end(self, name): while self.token.type == XML_CHARACTER_DATA: self.consume() @@ -840,20 +840,20 @@ class GprofParser(Parser): ) _cg_primary_re = re.compile( - r'^\[(?P<index>\d+)\]?' + - r'\s+(?P<percentage_time>\d+\.\d+)' + - r'\s+(?P<self>\d+\.\d+)' + - r'\s+(?P<descendants>\d+\.\d+)' + - r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + + r'^\[(?P<index>\d+)\]?' + + r'\s+(?P<percentage_time>\d+\.\d+)' + + r'\s+(?P<self>\d+\.\d+)' + + r'\s+(?P<descendants>\d+\.\d+)' + + r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + r'\s+(?P<name>\S.*?)' + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' + r'\s\[(\d+)\]$' ) _cg_parent_re = re.compile( - r'^\s+(?P<self>\d+\.\d+)?' + - r'\s+(?P<descendants>\d+\.\d+)?' + - r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' + + r'^\s+(?P<self>\d+\.\d+)?' + + r'\s+(?P<descendants>\d+\.\d+)?' + + r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' + r'\s+(?P<name>\S.*?)' + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' + r'\s\[(?P<index>\d+)\]$' @@ -862,19 +862,19 @@ class GprofParser(Parser): _cg_child_re = _cg_parent_re _cg_cycle_header_re = re.compile( - r'^\[(?P<index>\d+)\]?' + - r'\s+(?P<percentage_time>\d+\.\d+)' + - r'\s+(?P<self>\d+\.\d+)' + - r'\s+(?P<descendants>\d+\.\d+)' + - r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + + r'^\[(?P<index>\d+)\]?' + + r'\s+(?P<percentage_time>\d+\.\d+)' + + r'\s+(?P<self>\d+\.\d+)' + + r'\s+(?P<descendants>\d+\.\d+)' + + r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' + r'\s+<cycle\s(?P<cycle>\d+)\sas\sa\swhole>' + r'\s\[(\d+)\]$' ) _cg_cycle_member_re = re.compile( - r'^\s+(?P<self>\d+\.\d+)?' + - r'\s+(?P<descendants>\d+\.\d+)?' + - r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' + + r'^\s+(?P<self>\d+\.\d+)?' + + r'\s+(?P<descendants>\d+\.\d+)?' + + r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' + r'\s+(?P<name>\S.*?)' + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' + r'\s\[(?P<index>\d+)\]$' @@ -892,7 +892,7 @@ class GprofParser(Parser): line = lines.pop(0) if line.startswith('['): break - + # read function parent line mo = self._cg_parent_re.match(line) if not mo: @@ -913,7 +913,7 @@ class GprofParser(Parser): while lines: line = lines.pop(0) - + # read function subroutine line mo = self._cg_child_re.match(line) if not mo: @@ -923,7 +923,7 @@ class GprofParser(Parser): else: child = self.translate(mo) children.append(child) - + function.parents = parents function.children = children @@ -948,7 +948,7 @@ class GprofParser(Parser): continue call = self.translate(mo) cycle.functions.append(call) - + self.cycles[cycle.cycle] = cycle def parse_cg_entry(self, lines): @@ -975,16 +975,16 @@ class GprofParser(Parser): self.parse_cg_entry(entry_lines) entry_lines = [] else: - entry_lines.append(line) + entry_lines.append(line) line = self.readline() - + def parse(self): self.parse_cg() self.fp.close() profile = Profile() profile[TIME] = 0.0 - + cycles = {} for index in self.cycles.iterkeys(): cycles[index] = Cycle() @@ -999,16 +999,16 @@ class GprofParser(Parser): call = Call(entry.index) call[CALLS] = entry.called_self function.called += entry.called_self - + # populate the function calls for child in entry.children: call = Call(child.index) - + assert child.called is not None call[CALLS] = child.called if child.index not in self.functions: - # NOTE: functions that were never called but were discovered by gprof's + # NOTE: functions that were never called but were discovered by gprof's # static call graph analysis dont have a call graph entry so we need # to add them here missing = Function(child.index, child.name) @@ -1024,7 +1024,7 @@ class GprofParser(Parser): try: cycle = cycles[entry.cycle] except KeyError: - sys.stderr.write('warning: <cycle %u as a whole> entry missing\n' % entry.cycle) + sys.stderr.write('warning: <cycle %u as a whole> entry missing\n' % entry.cycle) cycle = Cycle() cycles[entry.cycle] = cycle cycle.add_function(function) @@ -1046,7 +1046,7 @@ class GprofParser(Parser): class CallgrindParser(LineParser): """Parser for valgrind's callgrind tool. - + See also: - http://valgrind.org/docs/manual/cl-format.html """ @@ -1153,7 +1153,7 @@ class CallgrindParser(LineParser): self.parse_association_spec() __subpos_re = r'(0x[0-9a-fA-F]+|\d+|\+\d+|-\d+|\*)' - _cost_re = re.compile(r'^' + + _cost_re = re.compile(r'^' + __subpos_re + r'( +' + __subpos_re + r')*' + r'( +\d+)*' + '$') @@ -1188,12 +1188,12 @@ class CallgrindParser(LineParser): events = map(float, events) if calls is None: - function[SAMPLES] += events[0] + function[SAMPLES] += events[0] self.profile[SAMPLES] += events[0] else: callee = self.get_callee() callee.called += calls - + try: call = function.calls[callee.id] except KeyError: @@ -1255,7 +1255,7 @@ class CallgrindParser(LineParser): def parse_position_spec(self): line = self.lookahead() - + if line.startswith('jump=') or line.startswith('jcnd='): self.consume() return True @@ -1338,20 +1338,20 @@ class CallgrindParser(LineParser): def get_function(self): module = self.positions.get('ob', '') - filename = self.positions.get('fl', '') - function = self.positions.get('fn', '') + filename = self.positions.get('fl', '') + function = self.positions.get('fn', '') return self.make_function(module, filename, function) def get_callee(self): module = self.positions.get('cob', '') - filename = self.positions.get('cfi', '') - function = self.positions.get('cfn', '') + filename = self.positions.get('cfi', '') + function = self.positions.get('cfn', '') return self.make_function(module, filename, function) class OprofileParser(LineParser): """Parser for oprofile callgraph output. - + See also: - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph """ @@ -1380,7 +1380,7 @@ class OprofileParser(LineParser): self.update_subentries_dict(callers_total, callers) function_total.samples += function.samples self.update_subentries_dict(callees_total, callees) - + def update_subentries_dict(self, totals, partials): for partial in partials.itervalues(): try: @@ -1389,7 +1389,7 @@ class OprofileParser(LineParser): totals[partial.id] = partial else: total.samples += partial.samples - + def parse(self): # read lookahead self.readline() @@ -1401,7 +1401,7 @@ class OprofileParser(LineParser): profile = Profile() reverse_call_samples = {} - + # populate the profile profile[SAMPLES] = 0 for _callers, _function, _callees in self.entries.itervalues(): @@ -1424,7 +1424,7 @@ class OprofileParser(LineParser): call = Call(_callee.id) call[SAMPLES2] = _callee.samples function.add_call(call) - + # compute derived data profile.validate() profile.find_cycles() @@ -1510,7 +1510,7 @@ class OprofileParser(LineParser): def match_primary(self): line = self.lookahead() return not line[:1].isspace() - + def match_secondary(self): line = self.lookahead() return line[:1].isspace() @@ -1518,7 +1518,7 @@ class OprofileParser(LineParser): class HProfParser(LineParser): """Parser for java hprof output - + See also: - http://java.sun.com/developer/technicalArticles/Programming/HPROF.html """ @@ -1679,7 +1679,7 @@ class SysprofParser(XmlParser): def build_profile(self, objects, nodes): profile = Profile() - + profile[SAMPLES] = 0 for id, object in objects.iteritems(): # Ignore fake objects (process names, modules, "Everything", "kernel", etc.) @@ -1753,7 +1753,7 @@ class SharkParser(LineParser): else: function_total, callees_total = entry function_total.samples += function.samples - + def add_callee(self, function, callee): func, callees = self.entries[function.id] try: @@ -1762,7 +1762,7 @@ class SharkParser(LineParser): callees[callee.id] = callee else: entry.samples += callee.samples - + def parse(self): self.readline() self.readline() @@ -1800,9 +1800,9 @@ class SharkParser(LineParser): # if the callstack has had an entry, it's this functions caller if prefix > 0: self.add_callee(self.stack[prefix - 1], entry) - + self.add_entry(entry) - + profile = Profile() profile[SAMPLES] = 0 for _function, _callees in self.entries.itervalues(): @@ -1818,7 +1818,7 @@ class SharkParser(LineParser): call = Call(_callee.id) call[SAMPLES] = _callee.samples function.add_call(call) - + # compute derived data profile.validate() profile.find_cycles() @@ -1843,7 +1843,7 @@ class XPerfParser(Parser): def parse(self): import csv reader = csv.reader( - self.stream, + self.stream, delimiter = ',', quotechar = None, escapechar = None, @@ -1856,7 +1856,7 @@ class XPerfParser(Parser): self.parse_header(row) for row in it: self.parse_row(row) - + # compute derived data self.profile.validate() self.profile.find_cycles() @@ -1884,7 +1884,7 @@ class XPerfParser(Parser): else: break fields[name] = value - + process = fields['Process Name'] symbol = fields['Module'] + '!' + fields['Function'] weight = fields['Weight'] @@ -1950,12 +1950,12 @@ class SleepyParser(Parser): self.calls = {} self.profile = Profile() - + _symbol_re = re.compile( - r'^(?P<id>\w+)' + - r'\s+"(?P<module>[^"]*)"' + - r'\s+"(?P<procname>[^"]*)"' + - r'\s+"(?P<sourcefile>[^"]*)"' + + r'^(?P<id>\w+)' + + r'\s+"(?P<module>[^"]*)"' + + r'\s+"(?P<procname>[^"]*)"' + + r'\s+"(?P<sourcefile>[^"]*)"' + r'\s+(?P<sourceline>\d+)$' ) @@ -1965,7 +1965,7 @@ class SleepyParser(Parser): mo = self._symbol_re.match(line) if mo: symbol_id, module, procname, sourcefile, sourceline = mo.groups() - + function_id = ':'.join([module, procname]) try: @@ -1991,7 +1991,7 @@ class SleepyParser(Parser): callee[SAMPLES] += samples self.profile[SAMPLES] += samples - + for caller in callstack[1:]: try: call = caller.calls[callee.id] @@ -2059,7 +2059,7 @@ class AQtimeParser(XmlParser): self.parse_headers() results = self.parse_results() self.element_end('AQtime_Results') - return self.build_profile(results) + return self.build_profile(results) def parse_headers(self): self.element_start('HEADERS') @@ -2163,7 +2163,7 @@ class AQtimeParser(XmlParser): profile[TOTAL_TIME] = profile[TIME] profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) return profile - + def build_function(self, fields): function = Function(self.build_id(fields), self.build_name(fields)) function[TIME] = fields['Time'] @@ -2263,7 +2263,7 @@ class PstatsParser: class Theme: - def __init__(self, + def __init__(self, bgcolor = (0.0, 0.0, 1.0), mincolor = (0.0, 0.0, 0.0), maxcolor = (0.0, 0.0, 1.0), @@ -2320,10 +2320,10 @@ class Theme: def color(self, weight): weight = min(max(weight, 0.0), 1.0) - + hmin, smin, lmin = self.mincolor hmax, smax, lmax = self.maxcolor - + if self.skew < 0: raise ValueError("Skew must be greater than 0") elif self.skew == 1.0: @@ -2446,10 +2446,10 @@ class DotWriter: weight = 0.0 label = '\n'.join(labels) - self.node(function.id, - label = label, - color = self.color(theme.node_bgcolor(weight)), - fontcolor = self.color(theme.node_fgcolor(weight)), + self.node(function.id, + label = label, + color = self.color(theme.node_bgcolor(weight)), + fontcolor = self.color(theme.node_fgcolor(weight)), fontsize = "%.2f" % theme.node_fontsize(weight), ) @@ -2471,13 +2471,13 @@ class DotWriter: label = '\n'.join(labels) - self.edge(function.id, call.callee_id, - label = label, - color = self.color(theme.edge_color(weight)), + self.edge(function.id, call.callee_id, + label = label, + color = self.color(theme.edge_color(weight)), fontcolor = self.color(theme.edge_color(weight)), - fontsize = "%.2f" % theme.edge_fontsize(weight), - penwidth = "%.2f" % theme.edge_penwidth(weight), - labeldistance = "%.2f" % theme.edge_penwidth(weight), + fontsize = "%.2f" % theme.edge_fontsize(weight), + penwidth = "%.2f" % theme.edge_penwidth(weight), + labeldistance = "%.2f" % theme.edge_penwidth(weight), arrowsize = "%.2f" % theme.edge_arrowsize(weight), ) @@ -2621,7 +2621,7 @@ class Main: self.theme = self.themes[self.options.theme] except KeyError: parser.error('invalid colormap \'%s\'' % self.options.theme) - + # set skew on the theme now that it has been picked. if self.options.theme_skew: self.theme.skew = self.options.theme_skew @@ -2655,7 +2655,7 @@ class Main: fp = sys.stdin else: fp = open(self.args[0], 'rt') - parser = HProfParser(fp) + parser = HProfParser(fp) elif self.options.format == 'pstats': if not self.args: parser.error('at least a file must be specified for pstats input') @@ -2686,7 +2686,7 @@ class Main: parser.error('invalid format \'%s\'' % self.options.format) self.profile = parser.parse() - + if self.options.output is None: self.output = sys.stdout else: @@ -2760,4 +2760,4 @@ class Main: if __name__ == '__main__': - Main().main()
\ No newline at end of file + Main().main() |