.gear/flamegraph.spec | 64 +++++++++++++ .gear/rules | 3 + .gear/tags/list | 1 + dev/hcstackcollapse.pl | 2 +- flamegraph.pl | 21 ++++- stackcollapse-bpftrace.pl | 66 +++++++++++++ stackcollapse-go.pl | 2 +- stackcollapse-java-exceptions.pl | 72 ++++++++++++++ stackcollapse-jstack.pl | 2 +- stackcollapse-ljp.awk | 2 +- stackcollapse-perf.pl | 2 +- stackcollapse-stap.pl | 2 +- stackcollapse-xdebug.php | 197 +++++++++++++++++++++++++++++++++++++++ stackcollapse.pl | 2 +- 14 files changed, 428 insertions(+), 10 deletions(-) diff --git a/.gear/flamegraph.spec b/.gear/flamegraph.spec new file mode 100644 index 0000000..17538d0 --- /dev/null +++ b/.gear/flamegraph.spec @@ -0,0 +1,64 @@ +Name: flamegraph +Version: 1.0 +Release: alt2 +Epoch: 1 + +Summary: Flame Graphs visualize profiled code-paths +License: CDDL1.0/GPLv2+ +Group: Monitoring +URL: http://www.brendangregg.com/flamegraphs.html + +BuildArch: noarch + +Source0: %name-%version.tar +Patch0: %name-%version-alt.patch + +Conflicts: perl-Devel-NYTProf + + +%description +Flame Graphs visualize profiled code-paths. + +%prep +%setup +%patch0 -p1 + +%install +mkdir -p %buildroot%_bindir +for file in *.pl *.awk dev/*.pl ;do +install -p -m755 $file %buildroot%_bindir/ +done + +%files +%_bindir/* +%doc README.md example* demos dev/README + +%changelog +* Fri Jan 11 2019 Terechkov Evgenii 1:1.0-alt2 +- v1.0-13-gf857ebc +- Add Conflicts: to perl-Devel-NYTProf (thanks, repocop) + +* Thu Sep 27 2018 Terechkov Evgenii 1:1.0-alt1 +- v1.0-5-g18c3dea +- Epoch tag added to proper package upgrade + +* Wed Aug 2 2017 Terechkov Evgenii 20170801-alt1 +- git-20170801 (99972c0) + +* Wed Feb 8 2017 Terechkov Evgenii 20170208-alt1 +- git-20170208 (54b5f97) + +* Thu Jan 28 2016 Terechkov Evgenii 20160128-alt1 +- git-20160128 + +* Sat Nov 14 2015 Terechkov Evgenii 20151114-alt1 +- git-20151114 + +* Tue Sep 15 2015 Terechkov Evgenii 20150915-alt1 +- git-20150915 + +* Sun Aug 16 2015 Terechkov Evgenii 20150816-alt1 +- git-20150816 + +* Mon Jul 13 2015 Terechkov Evgenii 20150713-alt1 +- git-20150713 diff --git a/.gear/rules b/.gear/rules new file mode 100644 index 0000000..c93dd65 --- /dev/null +++ b/.gear/rules @@ -0,0 +1,3 @@ +tar: v@version@:. name=@name@-@version@ +diff: v@version@:. . name=@name@-@version@-alt.patch +spec: .gear/flamegraph.spec diff --git a/.gear/tags/list b/.gear/tags/list new file mode 100644 index 0000000..5d71e48 --- /dev/null +++ b/.gear/tags/list @@ -0,0 +1 @@ +a8d807a11c0f22871134324bda709618ca482b58 v1.0 diff --git a/dev/hcstackcollapse.pl b/dev/hcstackcollapse.pl index cc249d0..9e56282 100755 --- a/dev/hcstackcollapse.pl +++ b/dev/hcstackcollapse.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# hcstackcolllapse.pl collapse hot/cold multiline stacks into single lines. +# hcstackcollapse.pl collapse hot/cold multiline stacks into single lines. # # EXPERIMENTAL: This is a work in progress, and may not work properly. # diff --git a/flamegraph.pl b/flamegraph.pl index 4723421..410700d 100755 --- a/flamegraph.pl +++ b/flamegraph.pl @@ -118,6 +118,7 @@ my %palette_map; # palette map hash my $pal_file = "palette.map"; # palette map file name my $stackreverse = 0; # reverse stack order, switching merge end my $inverted = 0; # icicle graph +my $flamechart = 0; # produce a flame chart (sort by time, do not merge stacks) my $negate = 0; # switch differential hues my $titletext = ""; # centered heading my $titledefault = "Flame Graph"; # overwritten by --title @@ -146,6 +147,7 @@ USAGE: $0 [options] infile > outfile.svg\n --cp # use consistent palette (palette.map) --reverse # generate stack-reversed flame graph --inverted # icicle graph + --flamechart # produce a flame chart (sort by time, do not merge stacks) --negate # switch differential hues (blue<->red) --notes TEXT # add notes comment in SVG (for debugging) --help # this message @@ -175,6 +177,7 @@ GetOptions( 'cp' => \$palette, 'reverse' => \$stackreverse, 'inverted' => \$inverted, + 'flamechart' => \$flamechart, 'negate' => \$negate, 'notes=s' => \$notestext, 'help' => \$help, @@ -191,6 +194,10 @@ my $depthmax = 0; my %Events; my %nameattr; +if ($flamechart && $titletext eq "") { + $titletext = "Flame Chart"; +} + if ($titletext eq "") { unless ($inverted) { $titletext = $titledefault; @@ -379,10 +386,10 @@ sub color { $type = "aqua"; } elsif ($name =~ m:^L?(java|org|com|io|sun)/:) { # Java $type = "green"; - } elsif ($name =~ /::/) { # C++ - $type = "yellow"; } elsif ($name =~ m:_\[k\]$:) { # kernel annotation $type = "orange"; + } elsif ($name =~ /::/) { # C++ + $type = "yellow"; } else { # system $type = "red"; } @@ -564,6 +571,7 @@ sub flow { # parse input my @Data; +my @SortedData; my $last = []; my $time = 0; my $delta = undef; @@ -592,8 +600,15 @@ foreach (<>) { } } +if ($flamechart) { + # In flame chart mode, just reverse the data so time moves from left to right. + @SortedData = reverse @Data; +} else { + @SortedData = sort @Data; +} + # process and merge frames -foreach (sort @Data) { +foreach (@SortedData) { chomp; # process: folded_stack count # eg: func_a;func_b;func_c 31 diff --git a/stackcollapse-bpftrace.pl b/stackcollapse-bpftrace.pl new file mode 100755 index 0000000..e5d32a5 --- /dev/null +++ b/stackcollapse-bpftrace.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl -w +# +# stackcollapse-bpftrace.pl collapse bpftrace samples into single lines. +# +# USAGE ./stackcollapse-bpftrace.pl infile > outfile +# +# Example input: +# +# @[ +# _raw_spin_lock_bh+0 +# tcp_recvmsg+808 +# inet_recvmsg+81 +# sock_recvmsg+67 +# sock_read_iter+144 +# new_sync_read+228 +# __vfs_read+41 +# vfs_read+142 +# sys_read+85 +# do_syscall_64+115 +# entry_SYSCALL_64_after_hwframe+61 +# ]: 3 +# +# Example output: +# +# entry_SYSCALL_64_after_hwframe+61;do_syscall_64+115;sys_read+85;vfs_read+142;__vfs_read+41;new_sync_read+228;sock_read_iter+144;sock_recvmsg+67;inet_recvmsg+81;tcp_recvmsg+808;_raw_spin_lock_bh+0 3 +# +# Copyright 2018 Peter Sanford. 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) +# + +use strict; + +my @stack; +my $in_stack = 0; + +foreach (<>) { + chomp; + if (!$in_stack) { + if (/^@\[/) { + $in_stack = 1; + } + } else { + if (m/^\]: (\d+)/) { + print join(';', reverse(@stack)) . " $1\n"; + $in_stack = 0; + @stack = (); + } else { + push(@stack, $_); + } + } +} diff --git a/stackcollapse-go.pl b/stackcollapse-go.pl index 986b7d5..3b2ce3c 100755 --- a/stackcollapse-go.pl +++ b/stackcollapse-go.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# stackcolllapse-go.pl collapse golang samples into single lines. +# stackcollapse-go.pl collapse golang samples into single lines. # # Parses golang smaples generated by "go tool pprof" and outputs stacks as # single lines, with methods separated by semicolons, and then a space and an diff --git a/stackcollapse-java-exceptions.pl b/stackcollapse-java-exceptions.pl new file mode 100755 index 0000000..19badbc --- /dev/null +++ b/stackcollapse-java-exceptions.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl -w +# +# stackcolllapse-java-exceptions.pl collapse java exceptions (found in logs) into single lines. +# +# Parses Java error stacks found in a log file and outputs them as +# single lines, with methods separated by semicolons, and then a space and an +# occurrence count. Inspired by stackcollapse-jstack.pl except that it does +# not act as a performance profiler. +# +# It can be useful if a Java process dumps a lot of different stacks in its logs +# and you want to quickly identify the biggest culprits. +# +# USAGE: ./stackcollapse-java-exceptions.pl infile > outfile +# +# Copyright 2018 Paul de Verdiere. All rights reserved. + +use strict; +use Getopt::Long; + +# tunables +my $shorten_pkgs = 0; # shorten package names +my $no_pkgs = 0; # really shorten package names!! +my $help = 0; + +sub usage { + die < outfile\n + --shorten-pkgs : shorten package names + --no-pkgs : suppress package names (makes SVG much more readable) + +USAGE_END +} + +GetOptions( + 'shorten-pkgs!' => \$shorten_pkgs, + 'no-pkgs!' => \$no_pkgs, + 'help' => \$help, +) or usage(); +$help && usage(); + +my %collapsed; + +sub remember_stack { + my ($stack, $count) = @_; + $collapsed{$stack} += $count; +} + +my @stack; + +foreach (<>) { + chomp; + + if (/^\s*at ([^\(]*)/) { + my $func = $1; + if ($shorten_pkgs || $no_pkgs) { + my ($pkgs, $clsFunc) = ( $func =~ m/(.*\.)([^.]+\.[^.]+)$/ ); + $pkgs =~ s/(\w)\w*/$1/g; + $func = $no_pkgs ? $clsFunc: $pkgs . $clsFunc; + } + unshift @stack, $func; + } elsif (@stack ) { + next if m/.*waiting on .*/; + remember_stack(join(";", @stack), 1) if @stack; + undef @stack; + } +} + +remember_stack(join(";", @stack), 1) if @stack; + +foreach my $k (sort { $a cmp $b } keys %collapsed) { + print "$k $collapsed{$k}\n"; +} diff --git a/stackcollapse-jstack.pl b/stackcollapse-jstack.pl index f4aa6a1..24541d0 100755 --- a/stackcollapse-jstack.pl +++ b/stackcollapse-jstack.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# stackcolllapse-jstack.pl collapse jstack samples into single lines. +# stackcollapse-jstack.pl collapse jstack samples into single lines. # # Parses Java stacks generated by jstack(1) and outputs RUNNABLE stacks as # single lines, with methods separated by semicolons, and then a space and an diff --git a/stackcollapse-ljp.awk b/stackcollapse-ljp.awk index d1465ba..59aaae3 100755 --- a/stackcollapse-ljp.awk +++ b/stackcollapse-ljp.awk @@ -1,6 +1,6 @@ #!/usr/bin/awk -f # -# stackcolllapse-ljp.awk collapse lightweight java profile reports +# stackcollapse-ljp.awk collapse lightweight java profile reports # into single lines stacks. # # Parses a list of multiline stacks generated by: diff --git a/stackcollapse-perf.pl b/stackcollapse-perf.pl index e91f7de..5cefa82 100755 --- a/stackcollapse-perf.pl +++ b/stackcollapse-perf.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# stackcolllapse-perf.pl collapse perf samples into single lines. +# stackcollapse-perf.pl collapse perf samples into single lines. # # Parses a list of multiline stacks generated by "perf script", and # outputs a semicolon separated stack followed by a space and a count. diff --git a/stackcollapse-stap.pl b/stackcollapse-stap.pl index b04aac4..bca4046 100755 --- a/stackcollapse-stap.pl +++ b/stackcollapse-stap.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# stackcolllapse-stap.pl collapse multiline SystemTap stacks +# stackcollapse-stap.pl collapse multiline SystemTap stacks # into single lines. # # Parses a multiline stack followed by a number on a separate line, and diff --git a/stackcollapse-xdebug.php b/stackcollapse-xdebug.php new file mode 100755 index 0000000..6548903 --- /dev/null +++ b/stackcollapse-xdebug.php @@ -0,0 +1,197 @@ +#!/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. + + 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"; +} diff --git a/stackcollapse.pl b/stackcollapse.pl index 17144bb..1e00c52 100755 --- a/stackcollapse.pl +++ b/stackcollapse.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# stackcolllapse.pl collapse multiline stacks into single lines. +# stackcollapse.pl collapse multiline stacks into single lines. # # Parses a multiline stack followed by a number on a separate line, and # outputs a semicolon separated stack followed by a space and the number.