Skip to content
Newer
Older
100755 254 lines (228 sloc) 7.51 KB
c536e6a setup
Brendan Gregg authored
1 #!/usr/bin/perl -w
2 #
3 # flamegraph.pl flame stack grapher.
4 #
5 # This takes stack samples and renders a call graph, allowing hot functions
6 # and codepaths to be quickly identified.
7 #
8 # USAGE: ./flamegraph.pl input.txt > graph.svg
9 #
10 # grep funcA input.txt | ./flamegraph.pl > graph.svg
11 #
12 # The input is stack frames and sample counts formatted as single lines. Each
13 # frame in the stack is comma separated, with a space and count at the end of
14 # the line. These can be generated using DTrace with stackcollapse.pl.
15 #
16 # The output graph shows relative presense of functions in stack samples. The
17 # ordering on the x-axis has no meaning; since the data is samples, time order
18 # of events is not known. The order used sorts function names alphabeticly.
19 #
20 # HISTORY
21 #
22 # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb
23 # program, which visualized function entry and return trace events. As Neel
24 # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which
25 # was in turn inspired by the work on vftrace by Jan Boerhout". See:
26 # http://blogs.sun.com/realneel/entry/visualizing_callstacks_via_dtrace_and
27 #
28 # Copyright 2011 Joyent, Inc. All rights reserved.
29 # Copyright 2011 Brendan Gregg. All rights reserved.
30 #
31 # CDDL HEADER START
32 #
33 # The contents of this file are subject to the terms of the
34 # Common Development and Distribution License (the "License").
35 # You may not use this file except in compliance with the License.
36 #
37 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
38 # or http://www.opensolaris.org/os/licensing.
39 # See the License for the specific language governing permissions
40 # and limitations under the License.
41 #
42 # When distributing Covered Code, include this CDDL HEADER in each
43 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
44 # If applicable, add the following below this CDDL HEADER, with the
45 # fields enclosed by brackets "[]" replaced with your own identifying
46 # information: Portions Copyright [yyyy] [name of copyright owner]
47 #
48 # CDDL HEADER END
49 #
50 # 10-Sep-2011 Brendan Gregg Created this.
51
52 use strict;
53
54 # tunables
55 my $fonttype = "Verdana";
56 my $imagewidth = 1200; # max width, pixels
57 my $frameheight = 16; # max height is dynamic
58 my $fontsize = 12; # base text size
59 my $minwidth = 0.1; # min function width, pixels
60
61 # internals
62 my $ypad1 = $fontsize * 4; # pad top, include title
63 my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels
64 my $xpad = 10; # pad lefm and right
65 my $timemax = 0;
66 my $depthmax = 0;
67 my %Events;
68
69 # SVG functions
70 { package SVG;
71 sub new {
72 my $class = shift;
73 my $self = {};
74 bless ($self, $class);
75 return $self;
76 }
77
78 sub header {
79 my ($self, $w, $h) = @_;
80 $self->{svg} .= <<SVG;
81 <?xml version="1.0" standalone="no"?>
82 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
83 <svg version="1.1" width="$w" height="$h" onload="init(evt)" viewBox="0 0 $w $h" xmlns="http://www.w3.org/2000/svg" >
84 SVG
85 }
86
87 sub include {
88 my ($self, $content) = @_;
89 $self->{svg} .= $content;
90 }
91
92 sub colorAllocate {
93 my ($self, $r, $g, $b) = @_;
94 return "rgb($r,$g,$b)";
95 }
96
97 sub filledRectangle {
98 my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
99 $x1 = sprintf "%0.1f", $x1;
100 $x2 = sprintf "%0.1f", $x2;
101 my $w = sprintf "%0.1f", $x2 - $x1;
102 my $h = sprintf "%0.1f", $y2 - $y1;
103 $extra = defined $extra ? $extra : "";
104 $self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
105 }
106
107 sub stringTTF {
108 my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
109 $loc = defined $loc ? $loc : "left";
110 $extra = defined $extra ? $extra : "";
111 $self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
112 }
113
114 sub svg {
115 my $self = shift;
116 return "$self->{svg}</svg>\n";
117 }
118 1;
119 }
120
121 sub color {
122 my $type = shift;
123 if (defined $type and $type eq "hot") {
124 my $r = 205 + int(rand(50));
125 my $g = 0 + int(rand(230));
126 my $b = 0 + int(rand(55));
127 return "rgb($r,$g,$b)";
128 }
129 return "rgb(0,0,0)";
130 }
131
132 my %Node;
133 my %Tmp;
134
135 sub flow {
136 my ($a, $b, $v) = @_;
137 my @A = split ",", $a;
138 my @B = split ",", $b;
139
140 my $len_a = $#A;
141 my $len_b = $#B;
142 $depthmax = $len_b if $len_b > $depthmax;
143
144 my $i = 0;
145 my $len_same = 0;
146 for (; $i <= $len_a; $i++) {
147 last if $i > $len_b;
148 last if $A[$i] ne $B[$i];
149 }
150 $len_same = $i;
151
152 for ($i = $len_a; $i >= $len_same; $i--) {
153 my $k = "$A[$i]--$i";
154 # a unique ID is constructed from func--depth--etime;
155 # func-depth isn't unique, it may be repeated later.
156 $Node{"$k--$v"}->{stime} = $Tmp{$k}->{stime};
157 delete $Tmp{$k}->{stime};
158 delete $Tmp{$k};
159 }
160
161 for ($i = $len_same; $i <= $len_b; $i++) {
162 my $k = "$B[$i]--$i";
163 $Tmp{$k}->{stime} = $v;
164 }
165 }
166
167 # Parse input
168 my @Data = <>;
169 my $last = "";
170 my $time = 0;
171 foreach (sort @Data) {
172 chomp;
173 my ($stack, $samples) = split ' ';
174 $stack = ",$stack";
175 next unless defined $samples;
176 flow($last, $stack, $time);
177 $time += $samples;
178 $last = $stack;
179 }
180 flow($last, "", $time);
181 $timemax = $time or die "ERROR: No stack counts found\n";
182
183 # Draw canvas
184 my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
185 my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
186 my $im = SVG->new();
187 $im->header($imagewidth, $imageheight);
188 my $inc = <<INC;
189 <defs >
190 <linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
191 <stop stop-color="#eeeeee" offset="5%" />
192 <stop stop-color="#eeeeb0" offset="95%" />
193 </linearGradient>
194 </defs>
195 <style type="text/css">
196 rect[rx]:hover { stroke:black; stroke-width:1; }
197 text:hover { stroke:black; stroke-width:1; stroke-opacity:0.35; }
198 </style>
199 <script type="text/ecmascript">
200 <![CDATA[
201 var details;
202 function init(evt) { details = document.getElementById("details").firstChild; }
203 function s(info) { details.nodeValue = info; }
204 function c() { details.nodeValue = ' '; }
205 ]]>
206 </script>
207 INC
208 $im->include($inc);
209 $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
210 my ($white, $black, $vvdgrey, $vdgrey) = (
211 $im->colorAllocate(255, 255, 255),
212 $im->colorAllocate(0, 0, 0),
213 $im->colorAllocate(40, 40, 40),
214 $im->colorAllocate(160, 160, 160),
215 );
216 $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, "Flame Graph", "middle");
217 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), 'Function:');
218 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad + 60, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
219
220 # Draw frames
221 foreach my $id (keys %Node) {
222 my ($func, $depth, $etime) = split "--", $id;
223 die "missing start for $id" if !defined $Node{$id}->{stime};
224 my $stime = $Node{$id}->{stime};
225 my $samples = $etime - $stime;
226
227 my $x1 = $xpad + $stime * $widthpertime;
228 my $x2 = $xpad + $etime * $widthpertime;
229 my $width = $x2 - $x1;
230 next if $width < $minwidth;
231
232 my $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + 1;
233 my $y2 = $imageheight - $ypad2 - $depth * $frameheight;
234
235 my $info;
236 if ($func eq "" and $depth == 0) {
237 $info = "all samples ($samples samples, 100%)";
238 } else {
239 my $pct = sprintf "%.2f", ((100 * $samples) / $timemax);
240 $info = "$func ($samples samples, $pct%)";
241 }
242 $im->filledRectangle($x1, $y1, $x2, $y2, color("hot"), 'rx="2" ry="2" onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
243
244 if ($width > 50) {
245 my $chars = int($width / (0.7 * $fontsize));
246 my $text = substr $func, 0, $chars;
247 $text .= ".." if $chars < length $func;
248 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "",
249 'onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
250 }
251 }
252
253 print $im->svg;
Something went wrong with that request. Please try again.