summaryrefslogtreecommitdiffstats
path: root/library/Preprocessor.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Preprocessor.php')
-rw-r--r--library/Preprocessor.php374
1 files changed, 226 insertions, 148 deletions
diff --git a/library/Preprocessor.php b/library/Preprocessor.php
index b29581c..7b06aaf 100644
--- a/library/Preprocessor.php
+++ b/library/Preprocessor.php
@@ -1,161 +1,239 @@
<?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 https://github.com/jokkedk/webgrind/wiki/Preprocessed-Format
* @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),
- 'invocationCount' => 0,
- 'nr' => $nextFuncNr++,
- 'count' => 0,
- 'summedSelfCost' => 0,
- 'summedInclusiveCost' => 0,
+ /**
+ * 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.');
+
+ // If possible, use the binary preprocessor
+ if (self::binaryParse($inFile, $outFile)) {
+ return;
+ }
+
+ $proxyFunctions = array_flip(Webgrind_Config::$proxyFunctions);
+ $proxyQueue = array();
+ $nextFuncNr = 0;
+ $functionNames = array();
+ $functions = array();
+ $headers = array();
+
+ // Read information into memory
+ while (($line = fgets($in))) {
+ if (substr($line,0,3)==='fl=') {
+ // Found invocation of function. Read function name
+ fscanf($in,"fn=%[^\n\r]s",$function);
+ $function = self::getCompressedName($function, false);
+ // Special case for ENTRY_POINT - it contains summary header
+ if (self::ENTRY_POINT == $function) {
+ fgets($in);
+ $headers[] = fgets($in);
+ fgets($in);
+ }
+ // Cost line
+ fscanf($in,"%d %d",$lnr,$cost);
+
+ if (!isset($functionNames[$function])) {
+ $index = $nextFuncNr++;
+ $functionNames[$function] = $index;
+ if (isset($proxyFunctions[$function])) {
+ $proxyQueue[$index] = array();
+ }
+ $functions[$index] = array(
+ 'filename' => self::getCompressedName(substr(trim($line),3), true),
+ 'line' => $lnr,
+ 'invocationCount' => 1,
+ 'summedSelfCost' => $cost,
+ 'summedInclusiveCost' => $cost,
'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));
- }
-
- }
-
+ );
+ } else {
+ $index = $functionNames[$function];
+ $functions[$index]['invocationCount']++;
+ $functions[$index]['summedSelfCost'] += $cost;
+ $functions[$index]['summedInclusiveCost'] += $cost;
+ }
+ } else if (substr($line,0,4)==='cfn=') {
+ // Found call to function. ($function/$index should contain function call originates from)
+ $calledFunctionName = self::getCompressedName(substr(trim($line),4), false);
+ // Skip call line
+ fgets($in);
+ // Cost line
+ fscanf($in,"%d %d",$lnr,$cost);
+
+ // Current function is a proxy -> skip
+ if (isset($proxyQueue[$index])) {
+ $proxyQueue[$index][] = array(
+ 'calledIndex' => $functionNames[$calledFunctionName],
+ 'lnr' => $lnr,
+ 'cost' => $cost,
+ );
+ continue;
+ }
+
+ $calledIndex = $functionNames[$calledFunctionName];
+ // Called a proxy
+ if (isset($proxyQueue[$calledIndex])) {
+ $data = array_shift($proxyQueue[$calledIndex]);
+ $calledIndex = $data['calledIndex'];
+ $lnr = $data['lnr'];
+ $cost = $data['cost'];
+ }
+
+ $functions[$index]['summedInclusiveCost'] += $cost;
+
+ $key = $index.$lnr;
+ if (!isset($functions[$calledIndex]['calledFromInformation'][$key])) {
+ $functions[$calledIndex]['calledFromInformation'][$key] = array('functionNr'=>$index,'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0);
+ }
+
+ $functions[$calledIndex]['calledFromInformation'][$key]['callCount']++;
+ $functions[$calledIndex]['calledFromInformation'][$key]['summedCallCost'] += $cost;
+
+ $calledKey = $calledIndex.$lnr;
+ if (!isset($functions[$index]['subCallInformation'][$calledKey])) {
+ $functions[$index]['subCallInformation'][$calledKey] = array('functionNr'=>$calledIndex,'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0);
+ }
+
+ $functions[$index]['subCallInformation'][$calledKey]['callCount']++;
+ $functions[$index]['subCallInformation'][$calledKey]['summedCallCost'] += $cost;
+
+ } else if (strpos($line,': ')!==false) {
+ // Found header
+ $headers[] = $line;
+ }
+ }
+
+ $functionNames = array_flip($functionNames);
+
+ // 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 $index=>$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".$functionNames[$index]."\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));
+ }
+ }
+
+ /**
+ * Extract information from $inFile and store in preprocessed form in $outFile
+ *
+ * @param string $name String to parse (either a filename or function name line)
+ * @param int $isFile True if this is a filename line (since files and functions have their own symbol tables)
+ * @return void
+ **/
+ static function getCompressedName($name, $isFile)
+ {
+ global $compressedNames;
+ if (!preg_match("/\((\d+)\)(.+)?/", $name, $matches)) {
+ return $name;
+ }
+ $functionIndex = $matches[1];
+ if (isset($matches[2])) {
+ $compressedNames[$isFile][$functionIndex] = trim($matches[2]);
+ } else if (!isset($compressedNames[$isFile][$functionIndex])) {
+ return $name; // should not happen - is file valid?
+ }
+ return $compressedNames[$isFile][$functionIndex];
+ }
+
+ /**
+ * Extract information from $inFile and store in preprocessed form in $outFile
+ * using the (~20x) faster binary preprocessor
+ *
+ * @param string $inFile Callgrind file to read
+ * @param string $outFile File to write preprocessed data to
+ * @return bool True if binary preprocessor was executed
+ */
+ static function binaryParse($inFile, $outFile)
+ {
+ $preprocessor = Webgrind_Config::getBinaryPreprocessor();
+ if (!is_executable($preprocessor)) {
+ return false;
+ }
+
+ $cmd = escapeshellarg($preprocessor).' '.escapeshellarg($inFile).' '.escapeshellarg($outFile);
+ foreach (Webgrind_Config::$proxyFunctions as $function) {
+ $cmd .= ' '.escapeshellarg($function);
+ }
+ exec($cmd);
+ return true;
+ }
+
}