stackprof 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/bin/stackprof +15 -4
- data/bin/stackprof-flamegraph +2 -0
- data/bin/stackprof-gprof2dot +2 -0
- data/ext/stackprof.c +77 -14
- data/lib/stackprof/middleware.rb +4 -3
- data/lib/stackprof/report.rb +26 -1
- data/stackprof.gemspec +3 -1
- data/test/test_stackprof.rb +26 -0
- data/vendor/FlameGraph/README +134 -0
- data/vendor/FlameGraph/flamegraph.pl +494 -0
- data/vendor/gprof2dot/gprof2dot.py +3266 -0
- data/vendor/gprof2dot/hotshotmain.py +70 -0
- metadata +10 -2
@@ -0,0 +1,494 @@
|
|
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. Stack samples can be generated using
|
7
|
+
# tools such as DTrace, perf, SystemTap, and Instruments.
|
8
|
+
#
|
9
|
+
# USAGE: ./flamegraph.pl [options] input.txt > graph.svg
|
10
|
+
#
|
11
|
+
# grep funcA input.txt | ./flamegraph.pl [options] > graph.svg
|
12
|
+
#
|
13
|
+
# Options are listed in the usage message (--help).
|
14
|
+
#
|
15
|
+
# The input is stack frames and sample counts formatted as single lines. Each
|
16
|
+
# frame in the stack is semicolon separated, with a space and count at the end
|
17
|
+
# of the line. These can be generated using DTrace with stackcollapse.pl,
|
18
|
+
# and other tools using the stackcollapse variants.
|
19
|
+
#
|
20
|
+
# The output graph shows relative presence of functions in stack samples. The
|
21
|
+
# ordering on the x-axis has no meaning; since the data is samples, time order
|
22
|
+
# of events is not known. The order used sorts function names alphabetically.
|
23
|
+
#
|
24
|
+
# While intended to process stack samples, this can also process stack traces.
|
25
|
+
# For example, tracing stacks for memory allocation, or resource usage. You
|
26
|
+
# can use --title to set the title to reflect the content, and --countname
|
27
|
+
# to change "samples" to "bytes" etc.
|
28
|
+
#
|
29
|
+
# There are a few different palettes, selectable using --color. Functions
|
30
|
+
# called "-" will be printed gray, which can be used for stack separators (eg,
|
31
|
+
# between user and kernel stacks).
|
32
|
+
#
|
33
|
+
# HISTORY
|
34
|
+
#
|
35
|
+
# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb
|
36
|
+
# program, which visualized function entry and return trace events. As Neel
|
37
|
+
# wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which
|
38
|
+
# was in turn inspired by the work on vftrace by Jan Boerhout". See:
|
39
|
+
# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and
|
40
|
+
#
|
41
|
+
# Copyright 2011 Joyent, Inc. All rights reserved.
|
42
|
+
# Copyright 2011 Brendan Gregg. All rights reserved.
|
43
|
+
#
|
44
|
+
# CDDL HEADER START
|
45
|
+
#
|
46
|
+
# The contents of this file are subject to the terms of the
|
47
|
+
# Common Development and Distribution License (the "License").
|
48
|
+
# You may not use this file except in compliance with the License.
|
49
|
+
#
|
50
|
+
# You can obtain a copy of the license at docs/cddl1.txt or
|
51
|
+
# http://opensource.org/licenses/CDDL-1.0.
|
52
|
+
# See the License for the specific language governing permissions
|
53
|
+
# and limitations under the License.
|
54
|
+
#
|
55
|
+
# When distributing Covered Code, include this CDDL HEADER in each
|
56
|
+
# file and include the License file at docs/cddl1.txt.
|
57
|
+
# If applicable, add the following below this CDDL HEADER, with the
|
58
|
+
# fields enclosed by brackets "[]" replaced with your own identifying
|
59
|
+
# information: Portions Copyright [yyyy] [name of copyright owner]
|
60
|
+
#
|
61
|
+
# CDDL HEADER END
|
62
|
+
#
|
63
|
+
# 21-Nov-2013 Shawn Sterling Added consistent palette file option
|
64
|
+
# 17-Mar-2013 Tim Bunce Added options and more tunables.
|
65
|
+
# 15-Dec-2011 Dave Pacheco Support for frames with whitespace.
|
66
|
+
# 10-Sep-2011 Brendan Gregg Created this.
|
67
|
+
|
68
|
+
use strict;
|
69
|
+
|
70
|
+
use Getopt::Long;
|
71
|
+
|
72
|
+
# tunables
|
73
|
+
my $encoding;
|
74
|
+
my $fonttype = "Verdana";
|
75
|
+
my $imagewidth = 1200; # max width, pixels
|
76
|
+
my $frameheight = 16; # max height is dynamic
|
77
|
+
my $fontsize = 12; # base text size
|
78
|
+
my $fontwidth = 0.59; # avg width relative to fontsize
|
79
|
+
my $minwidth = 0.1; # min function width, pixels
|
80
|
+
my $titletext = "Flame Graph"; # centered heading
|
81
|
+
my $nametype = "Function:"; # what are the names in the data?
|
82
|
+
my $countname = "samples"; # what are the counts in the data?
|
83
|
+
my $colors = "hot"; # color theme
|
84
|
+
my $bgcolor1 = "#eeeeee"; # background color gradient start
|
85
|
+
my $bgcolor2 = "#eeeeb0"; # background color gradient stop
|
86
|
+
my $nameattrfile; # file holding function attributes
|
87
|
+
my $timemax; # (override the) sum of the counts
|
88
|
+
my $factor = 1; # factor to scale counts by
|
89
|
+
my $hash = 0; # color by function name
|
90
|
+
my $palette = 0; # if we use consistent palettes (default off)
|
91
|
+
my %palette_map; # palette map hash
|
92
|
+
my $pal_file = "palette.map"; # palette map file name
|
93
|
+
|
94
|
+
GetOptions(
|
95
|
+
'fonttype=s' => \$fonttype,
|
96
|
+
'width=i' => \$imagewidth,
|
97
|
+
'height=i' => \$frameheight,
|
98
|
+
'encoding=s' => \$encoding,
|
99
|
+
'fontsize=f' => \$fontsize,
|
100
|
+
'fontwidth=f' => \$fontwidth,
|
101
|
+
'minwidth=f' => \$minwidth,
|
102
|
+
'title=s' => \$titletext,
|
103
|
+
'nametype=s' => \$nametype,
|
104
|
+
'countname=s' => \$countname,
|
105
|
+
'nameattr=s' => \$nameattrfile,
|
106
|
+
'total=s' => \$timemax,
|
107
|
+
'factor=f' => \$factor,
|
108
|
+
'colors=s' => \$colors,
|
109
|
+
'hash' => \$hash,
|
110
|
+
'cp' => \$palette,
|
111
|
+
) or die <<USAGE_END;
|
112
|
+
USAGE: $0 [options] infile > outfile.svg\n
|
113
|
+
--title # change title text
|
114
|
+
--width # width of image (default 1200)
|
115
|
+
--height # height of each frame (default 16)
|
116
|
+
--minwidth # omit smaller functions (default 0.1 pixels)
|
117
|
+
--fonttype # font type (default "Verdana")
|
118
|
+
--fontsize # font size (default 12)
|
119
|
+
--countname # count type label (default "samples")
|
120
|
+
--nametype # name type label (default "Function:")
|
121
|
+
--colors # "hot", "mem", "io" palette (default "hot")
|
122
|
+
--hash # colors are keyed by function name hash
|
123
|
+
--cp # use consistent palette (palette.map)
|
124
|
+
|
125
|
+
eg,
|
126
|
+
$0 --title="Flame Graph: malloc()" trace.txt > graph.svg
|
127
|
+
USAGE_END
|
128
|
+
|
129
|
+
# internals
|
130
|
+
my $ypad1 = $fontsize * 4; # pad top, include title
|
131
|
+
my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels
|
132
|
+
my $xpad = 10; # pad lefm and right
|
133
|
+
my $depthmax = 0;
|
134
|
+
my %Events;
|
135
|
+
my %nameattr;
|
136
|
+
|
137
|
+
if ($nameattrfile) {
|
138
|
+
# The name-attribute file format is a function name followed by a tab then
|
139
|
+
# a sequence of tab separated name=value pairs.
|
140
|
+
open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n";
|
141
|
+
while (<$attrfh>) {
|
142
|
+
chomp;
|
143
|
+
my ($funcname, $attrstr) = split /\t/, $_, 2;
|
144
|
+
die "Invalid format in $nameattrfile" unless defined $attrstr;
|
145
|
+
$nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr };
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
if ($colors eq "mem") { $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; }
|
150
|
+
if ($colors eq "io") { $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; }
|
151
|
+
|
152
|
+
# SVG functions
|
153
|
+
{ package SVG;
|
154
|
+
sub new {
|
155
|
+
my $class = shift;
|
156
|
+
my $self = {};
|
157
|
+
bless ($self, $class);
|
158
|
+
return $self;
|
159
|
+
}
|
160
|
+
|
161
|
+
sub header {
|
162
|
+
my ($self, $w, $h) = @_;
|
163
|
+
my $enc_attr = '';
|
164
|
+
if (defined $encoding) {
|
165
|
+
$enc_attr = qq{ encoding="$encoding"};
|
166
|
+
}
|
167
|
+
$self->{svg} .= <<SVG;
|
168
|
+
<?xml version="1.0"$enc_attr standalone="no"?>
|
169
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
170
|
+
<svg version="1.1" width="$w" height="$h" onload="init(evt)" viewBox="0 0 $w $h" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
171
|
+
SVG
|
172
|
+
}
|
173
|
+
|
174
|
+
sub include {
|
175
|
+
my ($self, $content) = @_;
|
176
|
+
$self->{svg} .= $content;
|
177
|
+
}
|
178
|
+
|
179
|
+
sub colorAllocate {
|
180
|
+
my ($self, $r, $g, $b) = @_;
|
181
|
+
return "rgb($r,$g,$b)";
|
182
|
+
}
|
183
|
+
|
184
|
+
sub group_start {
|
185
|
+
my ($self, $attr) = @_;
|
186
|
+
|
187
|
+
my @g_attr = map {
|
188
|
+
exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : ()
|
189
|
+
} qw(class style onmouseover onmouseout);
|
190
|
+
push @g_attr, $attr->{g_extra} if $attr->{g_extra};
|
191
|
+
$self->{svg} .= sprintf qq/<g %s>\n/, join(' ', @g_attr);
|
192
|
+
|
193
|
+
$self->{svg} .= sprintf qq/<title>%s<\/title>/, $attr->{title}
|
194
|
+
if $attr->{title}; # should be first element within g container
|
195
|
+
|
196
|
+
if ($attr->{href}) {
|
197
|
+
my @a_attr;
|
198
|
+
push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href};
|
199
|
+
# default target=_top else links will open within SVG <object>
|
200
|
+
push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top";
|
201
|
+
push @a_attr, $attr->{a_extra} if $attr->{a_extra};
|
202
|
+
$self->{svg} .= sprintf qq/<a %s>/, join(' ', @a_attr);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
sub group_end {
|
207
|
+
my ($self, $attr) = @_;
|
208
|
+
$self->{svg} .= qq/<\/a>\n/ if $attr->{href};
|
209
|
+
$self->{svg} .= qq/<\/g>\n/;
|
210
|
+
}
|
211
|
+
|
212
|
+
sub filledRectangle {
|
213
|
+
my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
|
214
|
+
$x1 = sprintf "%0.1f", $x1;
|
215
|
+
$x2 = sprintf "%0.1f", $x2;
|
216
|
+
my $w = sprintf "%0.1f", $x2 - $x1;
|
217
|
+
my $h = sprintf "%0.1f", $y2 - $y1;
|
218
|
+
$extra = defined $extra ? $extra : "";
|
219
|
+
$self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
|
220
|
+
}
|
221
|
+
|
222
|
+
sub stringTTF {
|
223
|
+
my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
|
224
|
+
$loc = defined $loc ? $loc : "left";
|
225
|
+
$extra = defined $extra ? $extra : "";
|
226
|
+
$self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
|
227
|
+
}
|
228
|
+
|
229
|
+
sub svg {
|
230
|
+
my $self = shift;
|
231
|
+
return "$self->{svg}</svg>\n";
|
232
|
+
}
|
233
|
+
1;
|
234
|
+
}
|
235
|
+
|
236
|
+
sub namehash {
|
237
|
+
# Generate a vector hash for the name string, weighting early over
|
238
|
+
# later characters. We want to pick the same colors for function
|
239
|
+
# names across different flame graphs.
|
240
|
+
my $name = shift;
|
241
|
+
my $vector = 0;
|
242
|
+
my $weight = 1;
|
243
|
+
my $max = 1;
|
244
|
+
my $mod = 10;
|
245
|
+
# if module name present, trunc to 1st char
|
246
|
+
$name =~ s/.(.*?)`//;
|
247
|
+
foreach my $c (split //, $name) {
|
248
|
+
my $i = (ord $c) % $mod;
|
249
|
+
$vector += ($i / ($mod++ - 1)) * $weight;
|
250
|
+
$max += 1 * $weight;
|
251
|
+
$weight *= 0.70;
|
252
|
+
last if $mod > 12;
|
253
|
+
}
|
254
|
+
return (1 - $vector / $max)
|
255
|
+
}
|
256
|
+
|
257
|
+
sub color {
|
258
|
+
my ($type, $hash, $name) = @_;
|
259
|
+
my ($v1, $v2, $v3);
|
260
|
+
if ($hash) {
|
261
|
+
$v1 = namehash($name);
|
262
|
+
$v2 = $v3 = namehash(scalar reverse $name);
|
263
|
+
} else {
|
264
|
+
$v1 = rand(1);
|
265
|
+
$v2 = rand(1);
|
266
|
+
$v3 = rand(1);
|
267
|
+
}
|
268
|
+
if (defined $type and $type eq "hot") {
|
269
|
+
my $r = 205 + int(50 * $v3);
|
270
|
+
my $g = 0 + int(230 * $v1);
|
271
|
+
my $b = 0 + int(55 * $v2);
|
272
|
+
return "rgb($r,$g,$b)";
|
273
|
+
}
|
274
|
+
if (defined $type and $type eq "mem") {
|
275
|
+
my $r = 0;
|
276
|
+
my $g = 190 + int(50 * $v2);
|
277
|
+
my $b = 0 + int(210 * $v1);
|
278
|
+
return "rgb($r,$g,$b)";
|
279
|
+
}
|
280
|
+
if (defined $type and $type eq "io") {
|
281
|
+
my $r = 80 + int(60 * $v1);
|
282
|
+
my $g = $r;
|
283
|
+
my $b = 190 + int(55 * $v2);
|
284
|
+
return "rgb($r,$g,$b)";
|
285
|
+
}
|
286
|
+
return "rgb(0,0,0)";
|
287
|
+
}
|
288
|
+
|
289
|
+
sub color_map {
|
290
|
+
my ($colors, $func) = @_;
|
291
|
+
if (exists $palette_map{$func}) {
|
292
|
+
return $palette_map{$func};
|
293
|
+
} else {
|
294
|
+
$palette_map{$func} = color($colors);
|
295
|
+
return $palette_map{$func};
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
sub write_palette {
|
300
|
+
open(FILE, ">$pal_file");
|
301
|
+
foreach my $key (sort keys %palette_map) {
|
302
|
+
print FILE $key."->".$palette_map{$key}."\n";
|
303
|
+
}
|
304
|
+
close(FILE);
|
305
|
+
}
|
306
|
+
|
307
|
+
sub read_palette {
|
308
|
+
if (-e $pal_file) {
|
309
|
+
open(FILE, $pal_file) or die "can't open file $pal_file: $!";
|
310
|
+
while ( my $line = <FILE>) {
|
311
|
+
chomp($line);
|
312
|
+
(my $key, my $value) = split("->",$line);
|
313
|
+
$palette_map{$key}=$value;
|
314
|
+
}
|
315
|
+
close(FILE)
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
my %Node;
|
320
|
+
my %Tmp;
|
321
|
+
|
322
|
+
sub flow {
|
323
|
+
my ($last, $this, $v) = @_;
|
324
|
+
|
325
|
+
my $len_a = @$last - 1;
|
326
|
+
my $len_b = @$this - 1;
|
327
|
+
|
328
|
+
my $i = 0;
|
329
|
+
my $len_same;
|
330
|
+
for (; $i <= $len_a; $i++) {
|
331
|
+
last if $i > $len_b;
|
332
|
+
last if $last->[$i] ne $this->[$i];
|
333
|
+
}
|
334
|
+
$len_same = $i;
|
335
|
+
|
336
|
+
for ($i = $len_a; $i >= $len_same; $i--) {
|
337
|
+
my $k = "$last->[$i];$i";
|
338
|
+
# a unique ID is constructed from "func;depth;etime";
|
339
|
+
# func-depth isn't unique, it may be repeated later.
|
340
|
+
$Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime};
|
341
|
+
delete $Tmp{$k};
|
342
|
+
}
|
343
|
+
|
344
|
+
for ($i = $len_same; $i <= $len_b; $i++) {
|
345
|
+
my $k = "$this->[$i];$i";
|
346
|
+
$Tmp{$k}->{stime} = $v;
|
347
|
+
}
|
348
|
+
|
349
|
+
return $this;
|
350
|
+
}
|
351
|
+
|
352
|
+
# Parse input
|
353
|
+
my @Data = <>;
|
354
|
+
my $last = [];
|
355
|
+
my $time = 0;
|
356
|
+
my $ignored = 0;
|
357
|
+
foreach (sort @Data) {
|
358
|
+
chomp;
|
359
|
+
my ($stack, $samples) = (/^(.*)\s+(\d+(?:\.\d*)?)$/);
|
360
|
+
unless (defined $samples) {
|
361
|
+
++$ignored;
|
362
|
+
next;
|
363
|
+
}
|
364
|
+
$stack =~ tr/<>/()/;
|
365
|
+
$last = flow($last, [ '', split ";", $stack ], $time);
|
366
|
+
$time += $samples;
|
367
|
+
}
|
368
|
+
flow($last, [], $time);
|
369
|
+
warn "Ignored $ignored lines with invalid format\n" if $ignored;
|
370
|
+
die "ERROR: No stack counts found\n" unless $time;
|
371
|
+
|
372
|
+
if ($timemax and $timemax < $time) {
|
373
|
+
warn "Specified --total $timemax is less than actual total $time, so ignored\n"
|
374
|
+
if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc)
|
375
|
+
undef $timemax;
|
376
|
+
}
|
377
|
+
$timemax ||= $time;
|
378
|
+
|
379
|
+
my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
|
380
|
+
my $minwidth_time = $minwidth / $widthpertime;
|
381
|
+
|
382
|
+
# prune blocks that are too narrow and determine max depth
|
383
|
+
while (my ($id, $node) = each %Node) {
|
384
|
+
my ($func, $depth, $etime) = split ";", $id;
|
385
|
+
my $stime = $node->{stime};
|
386
|
+
die "missing start for $id" if not defined $stime;
|
387
|
+
|
388
|
+
if (($etime-$stime) < $minwidth_time) {
|
389
|
+
delete $Node{$id};
|
390
|
+
next;
|
391
|
+
}
|
392
|
+
$depthmax = $depth if $depth > $depthmax;
|
393
|
+
}
|
394
|
+
|
395
|
+
# Draw canvas
|
396
|
+
my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
|
397
|
+
my $im = SVG->new();
|
398
|
+
$im->header($imagewidth, $imageheight);
|
399
|
+
my $inc = <<INC;
|
400
|
+
<defs >
|
401
|
+
<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
|
402
|
+
<stop stop-color="$bgcolor1" offset="5%" />
|
403
|
+
<stop stop-color="$bgcolor2" offset="95%" />
|
404
|
+
</linearGradient>
|
405
|
+
</defs>
|
406
|
+
<style type="text/css">
|
407
|
+
.func_g:hover { stroke:black; stroke-width:0.5; }
|
408
|
+
</style>
|
409
|
+
<script type="text/ecmascript">
|
410
|
+
<![CDATA[
|
411
|
+
var details;
|
412
|
+
function init(evt) { details = document.getElementById("details").firstChild; }
|
413
|
+
function s(info) { details.nodeValue = "$nametype " + info; }
|
414
|
+
function c() { details.nodeValue = ' '; }
|
415
|
+
]]>
|
416
|
+
</script>
|
417
|
+
INC
|
418
|
+
$im->include($inc);
|
419
|
+
$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
|
420
|
+
my ($white, $black, $vvdgrey, $vdgrey) = (
|
421
|
+
$im->colorAllocate(255, 255, 255),
|
422
|
+
$im->colorAllocate(0, 0, 0),
|
423
|
+
$im->colorAllocate(40, 40, 40),
|
424
|
+
$im->colorAllocate(160, 160, 160),
|
425
|
+
);
|
426
|
+
$im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle");
|
427
|
+
$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
|
428
|
+
|
429
|
+
if ($palette) {
|
430
|
+
read_palette();
|
431
|
+
}
|
432
|
+
# Draw frames
|
433
|
+
|
434
|
+
while (my ($id, $node) = each %Node) {
|
435
|
+
my ($func, $depth, $etime) = split ";", $id;
|
436
|
+
my $stime = $node->{stime};
|
437
|
+
|
438
|
+
$etime = $timemax if $func eq "" and $depth == 0;
|
439
|
+
|
440
|
+
my $x1 = $xpad + $stime * $widthpertime;
|
441
|
+
my $x2 = $xpad + $etime * $widthpertime;
|
442
|
+
my $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + 1;
|
443
|
+
my $y2 = $imageheight - $ypad2 - $depth * $frameheight;
|
444
|
+
|
445
|
+
my $samples = sprintf "%.0f", ($etime - $stime) * $factor;
|
446
|
+
(my $samples_txt = $samples) # add commas per perlfaq5
|
447
|
+
=~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
|
448
|
+
|
449
|
+
my $info;
|
450
|
+
if ($func eq "" and $depth == 0) {
|
451
|
+
$info = "all ($samples_txt $countname, 100%)";
|
452
|
+
} else {
|
453
|
+
my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor));
|
454
|
+
my $escaped_func = $func;
|
455
|
+
$escaped_func =~ s/&/&/g;
|
456
|
+
$escaped_func =~ s/</</g;
|
457
|
+
$escaped_func =~ s/>/>/g;
|
458
|
+
$info = "$escaped_func ($samples_txt $countname, $pct%)";
|
459
|
+
}
|
460
|
+
|
461
|
+
my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone
|
462
|
+
$nameattr->{class} ||= "func_g";
|
463
|
+
$nameattr->{onmouseover} ||= "s('".$info."')";
|
464
|
+
$nameattr->{onmouseout} ||= "c()";
|
465
|
+
$nameattr->{title} ||= $info;
|
466
|
+
$im->group_start($nameattr);
|
467
|
+
|
468
|
+
if ($palette) {
|
469
|
+
$im->filledRectangle($x1, $y1, $x2, $y2, color_map($colors, $func), 'rx="2" ry="2"');
|
470
|
+
} else {
|
471
|
+
my $color = $func eq "-" ? $vdgrey : color($colors, $hash, $func);
|
472
|
+
$im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"');
|
473
|
+
}
|
474
|
+
|
475
|
+
my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));
|
476
|
+
if ($chars >= 3) { # room for one char plus two dots
|
477
|
+
my $text = substr $func, 0, $chars;
|
478
|
+
substr($text, -2, 2) = ".." if $chars < length $func;
|
479
|
+
$text =~ s/&/&/g;
|
480
|
+
$text =~ s/</</g;
|
481
|
+
$text =~ s/>/>/g;
|
482
|
+
$im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "");
|
483
|
+
}
|
484
|
+
|
485
|
+
$im->group_end($nameattr);
|
486
|
+
}
|
487
|
+
|
488
|
+
print $im->svg;
|
489
|
+
|
490
|
+
if ($palette) {
|
491
|
+
write_palette();
|
492
|
+
}
|
493
|
+
|
494
|
+
# vim: ts=8 sts=8 sw=8 noexpandtab
|