PageRenderTime 29ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Reader.php

http://github.com/jokkedk/webgrind
PHP | 266 lines | 134 code | 37 blank | 95 comment | 15 complexity | 523b89d660366ae85f715453b4d5b41c MD5 | raw file
  1. <?php
  2. /**
  3. * Class for reading datafiles generated by Webgrind_Preprocessor
  4. *
  5. * @package Webgrind
  6. * @author Jacob Oettinger
  7. */
  8. class Webgrind_Reader
  9. {
  10. /**
  11. * File format version that this reader understands
  12. */
  13. const FILE_FORMAT_VERSION = 7;
  14. /**
  15. * Binary number format used.
  16. * @see http://php.net/pack
  17. */
  18. const NR_FORMAT = 'V';
  19. /**
  20. * Size, in bytes, of the above number format
  21. */
  22. const NR_SIZE = 4;
  23. /**
  24. * Length of a call information block
  25. */
  26. const CALLINFORMATION_LENGTH = 4;
  27. /**
  28. * Length of a function information block
  29. */
  30. const FUNCTIONINFORMATION_LENGTH = 6;
  31. /**
  32. * Address of the headers in the data file
  33. *
  34. * @var int
  35. */
  36. private $headersPos;
  37. /**
  38. * Array of addresses pointing to information about functions
  39. *
  40. * @var array
  41. */
  42. private $functionPos;
  43. /**
  44. * Array of headers
  45. *
  46. * @var array
  47. */
  48. private $headers=null;
  49. /**
  50. * Format to return costs in
  51. *
  52. * @var string
  53. */
  54. private $costFormat;
  55. /**
  56. * Constructor
  57. * @param string Data file to read
  58. * @param string Format to return costs in
  59. */
  60. function __construct($dataFile, $costFormat) {
  61. $this->fp = @fopen($dataFile,'rb');
  62. if (!$this->fp)
  63. throw new Exception('Error opening file!');
  64. $this->costFormat = $costFormat;
  65. $this->init();
  66. }
  67. /**
  68. * Initializes the parser by reading initial information.
  69. *
  70. * Throws an exception if the file version does not match the readers version
  71. *
  72. * @return void
  73. * @throws Exception
  74. */
  75. private function init() {
  76. list($version, $this->headersPos, $functionCount) = $this->read(3);
  77. if ($version!=self::FILE_FORMAT_VERSION)
  78. throw new Exception('Datafile not correct version. Found '.$version.' expected '.self::FILE_FORMAT_VERSION);
  79. $this->functionPos = $this->read($functionCount);
  80. if (!is_array($this->functionPos))
  81. $this->functionPos = [$this->functionPos];
  82. }
  83. /**
  84. * Returns number of functions
  85. * @return int
  86. */
  87. function getFunctionCount() {
  88. return count($this->functionPos);
  89. }
  90. /**
  91. * Returns information about function with nr $nr
  92. *
  93. * @param $nr int Function number
  94. * @return array Function information
  95. */
  96. function getFunctionInfo($nr) {
  97. $this->seek($this->functionPos[$nr]);
  98. list($line, $summedSelfCost, $summedInclusiveCost, $invocationCount, $calledFromCount, $subCallCount) = $this->read(self::FUNCTIONINFORMATION_LENGTH);
  99. $this->seek(self::NR_SIZE*self::CALLINFORMATION_LENGTH*($calledFromCount+$subCallCount), SEEK_CUR);
  100. $file = $this->readLine();
  101. $function = $this->readLine();
  102. $result = array(
  103. 'file' => $file,
  104. 'line' => $line,
  105. 'functionName' => $function,
  106. 'summedSelfCost' => $summedSelfCost,
  107. 'summedInclusiveCost' => $summedInclusiveCost,
  108. 'invocationCount' => $invocationCount,
  109. 'calledFromInfoCount' => $calledFromCount,
  110. 'subCallInfoCount' => $subCallCount
  111. );
  112. $result['summedSelfCostRaw'] = $result['summedSelfCost'];
  113. $result['summedSelfCost'] = $this->formatCost($result['summedSelfCost']);
  114. $result['summedInclusiveCost'] = $this->formatCost($result['summedInclusiveCost']);
  115. return $result;
  116. }
  117. /**
  118. * Returns information about positions where a function has been called from
  119. *
  120. * @param $functionNr int Function number
  121. * @param $calledFromNr int Called from position nr
  122. * @return array Called from information
  123. */
  124. function getCalledFromInfo($functionNr, $calledFromNr) {
  125. $this->seek(
  126. $this->functionPos[$functionNr]
  127. + self::NR_SIZE
  128. * (self::CALLINFORMATION_LENGTH * $calledFromNr + self::FUNCTIONINFORMATION_LENGTH)
  129. );
  130. $data = $this->read(self::CALLINFORMATION_LENGTH);
  131. $result = array(
  132. 'functionNr' => $data[0],
  133. 'line' => $data[1],
  134. 'callCount' => $data[2],
  135. 'summedCallCost' => $data[3]
  136. );
  137. $result['summedCallCost'] = $this->formatCost($result['summedCallCost']);
  138. return $result;
  139. }
  140. /**
  141. * Returns information about functions called by a function
  142. *
  143. * @param $functionNr int Function number
  144. * @param $subCallNr int Sub call position nr
  145. * @return array Sub call information
  146. */
  147. function getSubCallInfo($functionNr, $subCallNr) {
  148. // Sub call count is the second last number in the FUNCTION_INFORMATION block
  149. $this->seek($this->functionPos[$functionNr] + self::NR_SIZE * (self::FUNCTIONINFORMATION_LENGTH - 2));
  150. $calledFromInfoCount = $this->read();
  151. $this->seek( ( ($calledFromInfoCount+$subCallNr) * self::CALLINFORMATION_LENGTH + 1 ) * self::NR_SIZE,SEEK_CUR);
  152. $data = $this->read(self::CALLINFORMATION_LENGTH);
  153. $result = array(
  154. 'functionNr' => $data[0],
  155. 'line' => $data[1],
  156. 'callCount' => $data[2],
  157. 'summedCallCost' => $data[3]
  158. );
  159. $result['summedCallCost'] = $this->formatCost($result['summedCallCost']);
  160. return $result;
  161. }
  162. /**
  163. * Returns value of a single header
  164. *
  165. * @return string Header value
  166. */
  167. function getHeader($header) {
  168. if ($this->headers==null) { // Cache headers
  169. $this->seek($this->headersPos);
  170. $this->headers = array(
  171. 'runs' => 0,
  172. 'summary' => 0,
  173. 'cmd' => '',
  174. 'creator' => '',
  175. );
  176. while ($line=$this->readLine()) {
  177. $parts = explode(': ',$line);
  178. if ($parts[0] == 'summary') {
  179. // According to https://github.com/xdebug/xdebug/commit/926808a6e0204f5835a617caa3581b45f6d82a6c#diff-1a570e993c4d7f2e341ba24905b8b2cdR355
  180. // summary now includes time + memory usage, webgrind only tracks the time from the summary
  181. $subParts = explode(' ', $parts[1]);
  182. $this->headers['runs']++;
  183. $this->headers['summary'] += (double) $subParts[0];
  184. } else {
  185. $this->headers[$parts[0]] = $parts[1];
  186. }
  187. }
  188. }
  189. return $this->headers[$header];
  190. }
  191. /**
  192. * Formats $cost using the format in $this->costFormat or optionally the format given as input
  193. *
  194. * @param int $cost Cost
  195. * @param string $format 'percent', 'msec' or 'usec'
  196. * @return int Formatted cost
  197. */
  198. function formatCost($cost, $format=null) {
  199. if ($format==null)
  200. $format = $this->costFormat;
  201. if ($format == 'percent') {
  202. $total = $this->getHeader('summary');
  203. $result = ($total==0) ? 0 : ($cost*100)/$total;
  204. return number_format($result, 2, '.', '');
  205. }
  206. if ($format == 'msec') {
  207. return round($cost/1000, 0);
  208. }
  209. // Default usec
  210. return $cost;
  211. }
  212. private function read($numbers=1) {
  213. $values = unpack(self::NR_FORMAT.$numbers,fread($this->fp,self::NR_SIZE*$numbers));
  214. if ($numbers==1)
  215. return $values[1];
  216. else
  217. return array_values($values); // reindex and return
  218. }
  219. private function readLine() {
  220. $result = fgets($this->fp);
  221. if ($result)
  222. return trim($result);
  223. else
  224. return $result;
  225. }
  226. private function seek($offset, $whence=SEEK_SET) {
  227. return fseek($this->fp, $offset, $whence);
  228. }
  229. }