#!/usr/bin/php
<?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.
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-xdebug.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-xdebug.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";
}