summaryrefslogtreecommitdiffstats
path: root/lib/Webgrind/Preprocessor.php
blob: 3a9d033731653699fabf8a1f0c0c023ee017a06f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php

class Webgrind_Preprocessor{
	const FILE_FORMAT_VERSION = 5;

	const NR_FORMAT = 'V';
	const NR_SIZE = 4;

	const ENTRY_POINT = '{main}';

	private $in, $out;
	
	function __construct($inFile, $outFile){
		$this->in = fopen($inFile, 'rb');
		$this->out = fopen($outFile, 'w+b');
	}
	
	
	function parse(){
		// Make local for faster access
		$in = $this->in;
		$out = $this->out;
		
		$nextFuncNr = 0;
		$functions = array();
		$headers = array();
		
		
		// Read information into memory
		while(($line = fgets($in))){
			if(substr($line,0,3)==='fl='){
				list($function) = fscanf($in,"fn=%s");
				if(!isset($functions[$function])){
					$functions[$function] = array('filename'=>substr(trim($line),3), 'invocationCount'=>0,'nr'=>$nextFuncNr++,'count'=>0,'summedSelfCost'=>0,'summedInclusiveCost'=>0,'callInformation'=>array());
				} 
				$functions[$function]['invocationCount']++;
				// Special case for {main} - 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]['summedSelfCost'] += $cost;
				$functions[$function]['summedInclusiveCost'] += $cost;				
			} else if(substr($line,0,4)==='cfn=') {
				$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]['callInformation'][$function.':'.$lnr]))
					$functions[$calledFunctionName]['callInformation'][$function.':'.$lnr] = array('functionNr'=>$functions[$function]['nr'],'line'=>$lnr,'callCount'=>0,'summedCallCost'=>0);
				$functions[$calledFunctionName]['callInformation'][$function.':'.$lnr]['callCount']++;
				$functions[$calledFunctionName]['callInformation'][$function.':'.$lnr]['summedCallCost'] += $cost;
				
			} else if(strpos($line,': ')!==false){
				$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['callInformation']);
			fwrite($out, pack(self::NR_FORMAT.'*', $function['summedSelfCost'], $function['summedInclusiveCost'], $function['invocationCount'], $calledFromCount));
			// Write call information
			foreach($function['callInformation'] 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($this->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);
		foreach($functionAddresses as $address){
			fwrite($out, pack(self::NR_FORMAT, $address));			
		}
		
	}
	
}