Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100755 264 lines (238 sloc) 8.0 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 #
a776fbb @davepacheco add support for frames with whitespace
davepacheco authored
50 # 15-Dec-2011 Dave Pacheco Support for frames with whitespace.
c536e6a setup
Brendan Gregg authored
51 # 10-Sep-2011 Brendan Gregg Created this.
52
53 use strict;
54
55 # tunables
56 my $fonttype = "Verdana";
57 my $imagewidth = 1200; # max width, pixels
58 my $frameheight = 16; # max height is dynamic
59 my $fontsize = 12; # base text size
60 my $minwidth = 0.1; # min function width, pixels
61
62 # internals
63 my $ypad1 = $fontsize * 4; # pad top, include title
64 my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels
65 my $xpad = 10; # pad lefm and right
66 my $timemax = 0;
67 my $depthmax = 0;
68 my %Events;
69
70 # SVG functions
71 { package SVG;
72 sub new {
73 my $class = shift;
74 my $self = {};
75 bless ($self, $class);
76 return $self;
77 }
78
79 sub header {
80 my ($self, $w, $h) = @_;
81 $self->{svg} .= <<SVG;
82 <?xml version="1.0" standalone="no"?>
83 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
84 <svg version="1.1" width="$w" height="$h" onload="init(evt)" viewBox="0 0 $w $h" xmlns="http://www.w3.org/2000/svg" >
85 SVG
86 }
87
88 sub include {
89 my ($self, $content) = @_;
90 $self->{svg} .= $content;
91 }
92
93 sub colorAllocate {
94 my ($self, $r, $g, $b) = @_;
95 return "rgb($r,$g,$b)";
96 }
97
98 sub filledRectangle {
99 my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
100 $x1 = sprintf "%0.1f", $x1;
101 $x2 = sprintf "%0.1f", $x2;
102 my $w = sprintf "%0.1f", $x2 - $x1;
103 my $h = sprintf "%0.1f", $y2 - $y1;
104 $extra = defined $extra ? $extra : "";
105 $self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
106 }
107
108 sub stringTTF {
109 my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
110 $loc = defined $loc ? $loc : "left";
111 $extra = defined $extra ? $extra : "";
112 $self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
113 }
114
115 sub svg {
116 my $self = shift;
117 return "$self->{svg}</svg>\n";
118 }
119 1;
120 }
121
122 sub color {
123 my $type = shift;
124 if (defined $type and $type eq "hot") {
125 my $r = 205 + int(rand(50));
126 my $g = 0 + int(rand(230));
127 my $b = 0 + int(rand(55));
128 return "rgb($r,$g,$b)";
129 }
130 return "rgb(0,0,0)";
131 }
132
133 my %Node;
134 my %Tmp;
135
136 sub flow {
137 my ($a, $b, $v) = @_;
02b07ad Use a semicolon to separate frames
Ryan Stone authored
138 my @A = split ";", $a;
139 my @B = split ";", $b;
c536e6a setup
Brendan Gregg authored
140
141 my $len_a = $#A;
142 my $len_b = $#B;
143 $depthmax = $len_b if $len_b > $depthmax;
144
145 my $i = 0;
146 my $len_same = 0;
147 for (; $i <= $len_a; $i++) {
148 last if $i > $len_b;
149 last if $A[$i] ne $B[$i];
150 }
151 $len_same = $i;
152
153 for ($i = $len_a; $i >= $len_same; $i--) {
154 my $k = "$A[$i]--$i";
155 # a unique ID is constructed from func--depth--etime;
156 # func-depth isn't unique, it may be repeated later.
157 $Node{"$k--$v"}->{stime} = $Tmp{$k}->{stime};
158 delete $Tmp{$k}->{stime};
159 delete $Tmp{$k};
160 }
161
162 for ($i = $len_same; $i <= $len_b; $i++) {
163 my $k = "$B[$i]--$i";
164 $Tmp{$k}->{stime} = $v;
165 }
166 }
167
168 # Parse input
169 my @Data = <>;
170 my $last = "";
171 my $time = 0;
172 foreach (sort @Data) {
173 chomp;
a776fbb @davepacheco add support for frames with whitespace
davepacheco authored
174 my ($stack, $samples) = (/^(.*)\s+(\d+)$/);
2bfbde7 @davepacheco escape special characters emitted by node helper
davepacheco authored
175 $stack =~ s/</(/g;
176 $stack =~ s/>/)/g;
c536e6a setup
Brendan Gregg authored
177 $stack = ",$stack";
178 next unless defined $samples;
179 flow($last, $stack, $time);
180 $time += $samples;
181 $last = $stack;
182 }
183 flow($last, "", $time);
184 $timemax = $time or die "ERROR: No stack counts found\n";
185
186 # Draw canvas
187 my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
188 my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
189 my $im = SVG->new();
190 $im->header($imagewidth, $imageheight);
191 my $inc = <<INC;
192 <defs >
193 <linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
194 <stop stop-color="#eeeeee" offset="5%" />
195 <stop stop-color="#eeeeb0" offset="95%" />
196 </linearGradient>
197 </defs>
198 <style type="text/css">
199 rect[rx]:hover { stroke:black; stroke-width:1; }
200 text:hover { stroke:black; stroke-width:1; stroke-opacity:0.35; }
201 </style>
202 <script type="text/ecmascript">
203 <![CDATA[
204 var details;
205 function init(evt) { details = document.getElementById("details").firstChild; }
206 function s(info) { details.nodeValue = info; }
207 function c() { details.nodeValue = ' '; }
208 ]]>
209 </script>
210 INC
211 $im->include($inc);
212 $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
213 my ($white, $black, $vvdgrey, $vdgrey) = (
214 $im->colorAllocate(255, 255, 255),
215 $im->colorAllocate(0, 0, 0),
216 $im->colorAllocate(40, 40, 40),
217 $im->colorAllocate(160, 160, 160),
218 );
219 $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, "Flame Graph", "middle");
220 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), 'Function:');
221 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad + 60, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
222
223 # Draw frames
224 foreach my $id (keys %Node) {
225 my ($func, $depth, $etime) = split "--", $id;
226 die "missing start for $id" if !defined $Node{$id}->{stime};
227 my $stime = $Node{$id}->{stime};
228 my $samples = $etime - $stime;
229
230 my $x1 = $xpad + $stime * $widthpertime;
231 my $x2 = $xpad + $etime * $widthpertime;
232 my $width = $x2 - $x1;
233 next if $width < $minwidth;
234
235 my $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + 1;
236 my $y2 = $imageheight - $ypad2 - $depth * $frameheight;
237
238 my $info;
239 if ($func eq "" and $depth == 0) {
240 $info = "all samples ($samples samples, 100%)";
241 } else {
242 my $pct = sprintf "%.2f", ((100 * $samples) / $timemax);
93f5b19 Escape strings after they have been truncated.
Ryan Stone authored
243 my $escaped_func = $func;
244 $escaped_func =~ s/&/&amp;/g;
245 $escaped_func =~ s/</&lt;/g;
246 $escaped_func =~ s/>/&gt;/g;
247 $info = "$escaped_func ($samples samples, $pct%)";
c536e6a setup
Brendan Gregg authored
248 }
249 $im->filledRectangle($x1, $y1, $x2, $y2, color("hot"), 'rx="2" ry="2" onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
250
251 if ($width > 50) {
252 my $chars = int($width / (0.7 * $fontsize));
253 my $text = substr $func, 0, $chars;
254 $text .= ".." if $chars < length $func;
93f5b19 Escape strings after they have been truncated.
Ryan Stone authored
255 $text =~ s/&/&amp;/g;
256 $text =~ s/</&lt;/g;
257 $text =~ s/>/&gt;/g;
c536e6a setup
Brendan Gregg authored
258 $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "",
259 'onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
260 }
261 }
262
263 print $im->svg;
Something went wrong with that request. Please try again.