stackcollapse-xdebug.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/php
  2. #
  3. # Copyright 2018 Miriam Lauter (lauter.miriam@gmail.com). All rights reserved.
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software Foundation,
  17. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  18. #
  19. # (http://www.gnu.org/copyleft/gpl.html)
  20. #
  21. # 13-Apr-2018 Miriam Lauter Created this.
  22. <?php
  23. ini_set('error_log', null);
  24. $optind = null;
  25. $args = getopt("htc", ["help"], $optind);
  26. if (isset($args['h']) || isset($args['help'])) {
  27. usage();
  28. }
  29. function usage($exit = 0) {
  30. echo <<<EOT
  31. stackcollapse-php.php collapse php function traces into single lines.
  32. Parses php samples generated by xdebug with xdebug.trace_format = 1
  33. and outputs stacks as single lines, with methods separated by semicolons,
  34. and then a space and an occurrence count. For use with flamegraph.pl.
  35. See https://github.com/brendangregg/FlameGraph.
  36. USAGE: ./stackcollapse-php.php [OPTIONS] infile > outfile
  37. -h --help Show this message
  38. -t Weight stack counts by duration using the time index in the trace (default)
  39. -c Invocation counts only. Simply count stacks in the trace and sum duplicates, don't weight by duration.
  40. Example input:
  41. For more info on xdebug and generating traces see
  42. https://xdebug.org/docs/execution_trace.
  43. Version: 2.0.0RC4-dev
  44. TRACE START [2007-05-06 18:29:01]
  45. 1 0 0 0.010870 114112 {main} 1 ../trace.php 0
  46. 2 1 0 0.032009 114272 str_split 0 ../trace.php 8
  47. 2 1 1 0.032073 116632
  48. 2 2 0 0.033505 117424 ret_ord 1 ../trace.php 10
  49. 3 3 0 0.033531 117584 ord 0 ../trace.php 5
  50. 3 3 1 0.033551 117584
  51. ...
  52. TRACE END [2007-05-06 18:29:01]
  53. Example output:
  54. - c
  55. {main};str_split 1
  56. {main};ret_ord;ord 6
  57. -t
  58. {main} 23381
  59. {main};str_split 64
  60. {main};ret_ord 215
  61. {main};ret_ord;ord 106
  62. EOT;
  63. exit($exit);
  64. }
  65. function collapseStack(array $stack, string $func_name_key): string {
  66. return implode(';', array_column($stack, $func_name_key));
  67. }
  68. function addCurrentStackToStacks(array $stack, float $dur, array &$stacks) {
  69. $collapsed = implode(';', $stack);
  70. $duration = SCALE_FACTOR * $dur;
  71. if (array_key_exists($collapsed, $stacks)) {
  72. $stacks[$collapsed] += $duration;
  73. } else {
  74. $stacks[$collapsed] = $duration;
  75. }
  76. }
  77. function isEOTrace(string $l) {
  78. $pattern = "/^(\\t|TRACE END)/";
  79. return preg_match($pattern, $l);
  80. }
  81. $filename = $argv[$optind] ?? null;
  82. if ($filename === null) {
  83. usage(1);
  84. }
  85. $do_time = !isset($args['c']);
  86. // First make sure our file is consistently formatted with only one \t delimiting each field
  87. $out = [];
  88. $retval = null;
  89. exec("sed -in 's/\t\+/\t/g' " . escapeshellarg($filename), $out, $retval);
  90. if ($retval !== 0) {
  91. usage(1);
  92. }
  93. $handle = fopen($filename, 'r');
  94. if ($handle === false) {
  95. echo "Unable to open $filename \n\n";
  96. usage(1);
  97. }
  98. // Loop till we find TRACE START
  99. while ($l = fgets($handle)) {
  100. if (strpos($l, "TRACE START") === 0) {
  101. break;
  102. }
  103. }
  104. const SCALE_FACTOR = 1000000;
  105. $stacks = [];
  106. $current_stack = [];
  107. $was_exit = false;
  108. $prev_start_time = 0;
  109. if ($do_time) {
  110. // Weight counts by duration
  111. // Xdebug trace time indices have 6 sigfigs of precision
  112. // We have a perfect trace, but let's instead pretend that
  113. // this was collected by sampling at 10^6 Hz
  114. // then each millionth of a second this stack took to execute is 1 count
  115. while ($l = fgets($handle)) {
  116. if (isEOTrace($l)) {
  117. break;
  118. }
  119. $parts = explode("\t", $l);
  120. list($level, $fn_no, $is_exit, $time) = $parts;
  121. if ($is_exit) {
  122. if (empty($current_stack)) {
  123. echo "[WARNING] Found function exit without corresponding entrance. Discarding line. Check your input.\n";
  124. continue;
  125. }
  126. addCurrentStackToStacks($current_stack, $time - $prev_start_time, $stacks);
  127. array_pop($current_stack);
  128. } else {
  129. $func_name = $parts[5];
  130. if (!empty($current_stack)) {
  131. addCurrentStackToStacks($current_stack, $time - $prev_start_time, $stacks);
  132. }
  133. $current_stack[] = $func_name;
  134. }
  135. $prev_start_time = $time;
  136. }
  137. } else {
  138. // Counts only
  139. while ($l = fgets($handle)) {
  140. if (isEOTrace($l)) {
  141. break;
  142. }
  143. $parts = explode("\t", $l);
  144. list($level, $fn_no, $is_exit) = $parts;
  145. if ($is_exit === "1") {
  146. if (!$was_exit) {
  147. $collapsed = implode(";", $current_stack);
  148. if (array_key_exists($collapsed, $stacks)) {
  149. $stacks[$collapsed]++;
  150. } else {
  151. $stacks[$collapsed] = 1;
  152. }
  153. }
  154. array_pop($current_stack);
  155. $was_exit = true;
  156. } else {
  157. $func_name = $parts[5];
  158. $current_stack[] = $func_name;
  159. $was_exit = false;
  160. }
  161. }
  162. }
  163. foreach ($stacks as $stack => $count) {
  164. echo "$stack $count\n";
  165. }