123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- #!/usr/bin/php
- #
- # Copyright 2018 Miriam Lauter (lauter.miriam@gmail.com). All rights reserved.
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software Foundation,
- # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- #
- # (http://www.gnu.org/copyleft/gpl.html)
- #
- # 13-Apr-2018 Miriam Lauter Created this.
- <?php
- ini_set('error_log', null);
- $optind = null;
- $args = getopt("htc", ["help"], $optind);
- if (isset($args['h']) || isset($args['help'])) {
- usage();
- }
- function usage($exit = 0) {
- echo <<<EOT
- stackcollapse-php.php collapse php function traces into single lines.
- Parses php samples generated by xdebug with xdebug.trace_format = 1
- and outputs stacks as single lines, with methods separated by semicolons,
- and then a space and an occurrence count. For use with flamegraph.pl.
- See https://github.com/brendangregg/FlameGraph.
- USAGE: ./stackcollapse-php.php [OPTIONS] infile > outfile
- -h --help Show this message
- -t Weight stack counts by duration using the time index in the trace (default)
- -c Invocation counts only. Simply count stacks in the trace and sum duplicates, don't weight by duration.
- Example input:
- For more info on xdebug and generating traces see
- https://xdebug.org/docs/execution_trace.
- Version: 2.0.0RC4-dev
- TRACE START [2007-05-06 18:29:01]
- 1 0 0 0.010870 114112 {main} 1 ../trace.php 0
- 2 1 0 0.032009 114272 str_split 0 ../trace.php 8
- 2 1 1 0.032073 116632
- 2 2 0 0.033505 117424 ret_ord 1 ../trace.php 10
- 3 3 0 0.033531 117584 ord 0 ../trace.php 5
- 3 3 1 0.033551 117584
- ...
- TRACE END [2007-05-06 18:29:01]
- Example output:
- - c
- {main};str_split 1
- {main};ret_ord;ord 6
- -t
- {main} 23381
- {main};str_split 64
- {main};ret_ord 215
- {main};ret_ord;ord 106
- EOT;
- exit($exit);
- }
- function collapseStack(array $stack, string $func_name_key): string {
- return implode(';', array_column($stack, $func_name_key));
- }
- function addCurrentStackToStacks(array $stack, float $dur, array &$stacks) {
- $collapsed = implode(';', $stack);
- $duration = SCALE_FACTOR * $dur;
- if (array_key_exists($collapsed, $stacks)) {
- $stacks[$collapsed] += $duration;
- } else {
- $stacks[$collapsed] = $duration;
- }
- }
- function isEOTrace(string $l) {
- $pattern = "/^(\\t|TRACE END)/";
- return preg_match($pattern, $l);
- }
- $filename = $argv[$optind] ?? null;
- if ($filename === null) {
- usage(1);
- }
- $do_time = !isset($args['c']);
- // First make sure our file is consistently formatted with only one \t delimiting each field
- $out = [];
- $retval = null;
- exec("sed -in 's/\t\+/\t/g' " . escapeshellarg($filename), $out, $retval);
- if ($retval !== 0) {
- usage(1);
- }
- $handle = fopen($filename, 'r');
- if ($handle === false) {
- echo "Unable to open $filename \n\n";
- usage(1);
- }
- // Loop till we find TRACE START
- while ($l = fgets($handle)) {
- if (strpos($l, "TRACE START") === 0) {
- break;
- }
- }
- const SCALE_FACTOR = 1000000;
- $stacks = [];
- $current_stack = [];
- $was_exit = false;
- $prev_start_time = 0;
- if ($do_time) {
- // Weight counts by duration
- // Xdebug trace time indices have 6 sigfigs of precision
- // We have a perfect trace, but let's instead pretend that
- // this was collected by sampling at 10^6 Hz
- // then each millionth of a second this stack took to execute is 1 count
- while ($l = fgets($handle)) {
- if (isEOTrace($l)) {
- break;
- }
- $parts = explode("\t", $l);
- list($level, $fn_no, $is_exit, $time) = $parts;
- if ($is_exit) {
- if (empty($current_stack)) {
- echo "[WARNING] Found function exit without corresponding entrance. Discarding line. Check your input.\n";
- continue;
- }
- addCurrentStackToStacks($current_stack, $time - $prev_start_time, $stacks);
- array_pop($current_stack);
- } else {
- $func_name = $parts[5];
- if (!empty($current_stack)) {
- addCurrentStackToStacks($current_stack, $time - $prev_start_time, $stacks);
- }
- $current_stack[] = $func_name;
- }
- $prev_start_time = $time;
- }
- } else {
- // Counts only
- while ($l = fgets($handle)) {
- if (isEOTrace($l)) {
- break;
- }
- $parts = explode("\t", $l);
- list($level, $fn_no, $is_exit) = $parts;
- if ($is_exit === "1") {
- if (!$was_exit) {
- $collapsed = implode(";", $current_stack);
- if (array_key_exists($collapsed, $stacks)) {
- $stacks[$collapsed]++;
- } else {
- $stacks[$collapsed] = 1;
- }
- }
- array_pop($current_stack);
- $was_exit = true;
- } else {
- $func_name = $parts[5];
- $current_stack[] = $func_name;
- $was_exit = false;
- }
- }
- }
- foreach ($stacks as $stack => $count) {
- echo "$stack $count\n";
- }
|