flameboyant 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bfa186aaa2c0e8800643b18f17fc63c94a49d4e9
4
+ data.tar.gz: af5b86c5869e4d4198ec6dd752b9d26f0fa4a207
5
+ SHA512:
6
+ metadata.gz: 8b5e058e0ab44ef61882a7ca8d21f5babb5764662d8d4447fd8723b32c27dff60401c769ed14ab47495d57739e58a1566684a6754b58304cc077ab2518e39a80
7
+ data.tar.gz: 582544d45dbc969bd99acea64bbf2d08d607e891879c559151d9bc75f1280901b1817a4896cd8a7ea473a6f2fbf67f7ce4098ce11b249bf630fae1ebd2488629
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in flameboyant.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Scott Pierce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+ # Flameboyant
2
+
3
+ Generate an interactive SVG flame chart.
4
+
5
+ <img src='flameboyant.jpg' alt='example flame chart'/>
6
+
7
+ ## Thanks
8
+ - https://github.com/oozou/ruby-prof-flamegraph
9
+ - https://github.com/brendangregg/FlameGraph
10
+
11
+ ## Usage
12
+
13
+ ### Example basic ruby
14
+ ```ruby
15
+ Flameboyant.profile(name: 'foo', width: 1024) do
16
+ # interesting code here
17
+ end
18
+ ```
19
+
20
+ Files are written to current directory.
21
+
22
+ ### Example Rails Console
23
+
24
+ ```ruby
25
+ Flameboyant.profile do
26
+ app.get '/interesting/api/call'
27
+ end
28
+ ```
29
+
30
+ ## `profile` Options
31
+ + name (String, optional) :: The output file will be prefixed with this name.
32
+ + width (Integer, optional) :: defaults to 1920.
33
+
34
+ When running with Rails SVG files are written to `Rails.root/tmp/flames`
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ ```ruby
41
+ gem 'flameboyant'
42
+ ```
43
+
44
+ And then execute:
45
+
46
+ $ bundle
47
+
48
+ Or install it yourself as:
49
+
50
+ $ gem install flameboyant
51
+
52
+
53
+ ## Development
54
+
55
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
56
+
57
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
58
+
59
+ ## Contributing
60
+
61
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ddrscott/flameboyant.
62
+
63
+ ## License
64
+
65
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "flameboyant"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flameboyant/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'flameboyant'
8
+ spec.version = Flameboyant::VERSION
9
+ spec.authors = ['Scott Pierce']
10
+ spec.email = ['ddrscott@gmail.com']
11
+
12
+ spec.summary = 'Generate an interactive SVG flame chart'
13
+ spec.homepage = 'https://github.com/ddrscott/flameboyant'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.15'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+
27
+ spec.add_dependency 'ruby-prof'
28
+ spec.add_dependency 'ruby-prof-flamegraph'
29
+ end
Binary file
@@ -0,0 +1,61 @@
1
+ require 'flameboyant/version'
2
+ require 'ruby-prof-flamegraph'
3
+
4
+ # rubocop:disable all
5
+ module Flameboyant
6
+ module_function
7
+
8
+ # ensure directory exists
9
+ GRAPHER = File.join(__dir__, 'flamegraph.pl')
10
+
11
+ def profile(name: nil, width: 1920, &block)
12
+ name = "#{name}_#{timestamp}"
13
+
14
+ RubyProf::FlameGraphPrinter
15
+
16
+ block_result = nil
17
+ log 'starting profile'
18
+ result = RubyProf.profile do
19
+ block_result = block.call
20
+ end
21
+
22
+ # print a graph profile to text
23
+ printer = RubyProf::FlameGraphPrinter.new(result)
24
+
25
+ FileUtils.mkdir_p(dest_dir)
26
+
27
+ dst_data = dest_dir.join("#{name}.txt")
28
+ dst_svg = dest_dir.join("#{name}.svg")
29
+
30
+ log "writing: #{dst_data}"
31
+ File.open(dst_data, 'w') do |f|
32
+ printer.print(f, {})
33
+ end
34
+
35
+ log 'generating SVG'
36
+ if system("#{GRAPHER} --countname=ms --width=#{width} #{dst_data} > #{dst_svg}")
37
+ log "created: #{dst_svg}"
38
+ log "removing: #{dst_data}"
39
+ FileUtils.rm(dst_data)
40
+ end
41
+ block_result
42
+ end
43
+
44
+ def dest_dir
45
+ if defined? Rails
46
+ Rails.root.join('tmp', 'flames')
47
+ else
48
+ Dir.pwd
49
+ end
50
+ end
51
+
52
+ def timestamp
53
+ '%.04f' % Time.now.to_f
54
+ end
55
+
56
+ def log(msg)
57
+ full_msg = "[flame] #{msg}"
58
+ Rails.logger.info full_msg if defined? Rails
59
+ $stderr.puts full_msg
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ module Flameboyant
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,1111 @@
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
+ # Then open the resulting .svg in a web browser, for interactivity: mouse-over
14
+ # frames for info, click to zoom, and ctrl-F to search.
15
+ #
16
+ # Options are listed in the usage message (--help).
17
+ #
18
+ # The input is stack frames and sample counts formatted as single lines. Each
19
+ # frame in the stack is semicolon separated, with a space and count at the end
20
+ # of the line. These can be generated for Linux perf script output using
21
+ # stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools
22
+ # using the other stackcollapse programs. Example input:
23
+ #
24
+ # swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1
25
+ #
26
+ # An optional extra column of counts can be provided to generate a differential
27
+ # flame graph of the counts, colored red for more, and blue for less. This
28
+ # can be useful when using flame graphs for non-regression testing.
29
+ # See the header comment in the difffolded.pl program for instructions.
30
+ #
31
+ # The input functions can optionally have annotations at the end of each
32
+ # function name, following a precedent by some tools (Linux perf's _[k]):
33
+ # _[k] for kernel
34
+ # _[i] for inlined
35
+ # _[j] for jit
36
+ # _[w] for waker
37
+ # Some of the stackcollapse programs support adding these annotations, eg,
38
+ # stackcollapse-perf.pl --kernel --jit. They are used merely for colors by
39
+ # some palettes, eg, flamegraph.pl --color=java.
40
+ #
41
+ # The output flame graph shows relative presence of functions in stack samples.
42
+ # The ordering on the x-axis has no meaning; since the data is samples, time
43
+ # order of events is not known. The order used sorts function names
44
+ # alphabetically.
45
+ #
46
+ # While intended to process stack samples, this can also process stack traces.
47
+ # For example, tracing stacks for memory allocation, or resource usage. You
48
+ # can use --title to set the title to reflect the content, and --countname
49
+ # to change "samples" to "bytes" etc.
50
+ #
51
+ # There are a few different palettes, selectable using --color. By default,
52
+ # the colors are selected at random (except for differentials). Functions
53
+ # called "-" will be printed gray, which can be used for stack separators (eg,
54
+ # between user and kernel stacks).
55
+ #
56
+ # HISTORY
57
+ #
58
+ # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb
59
+ # program, which visualized function entry and return trace events. As Neel
60
+ # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which
61
+ # was in turn inspired by the work on vftrace by Jan Boerhout". See:
62
+ # https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and
63
+ #
64
+ # Copyright 2016 Netflix, Inc.
65
+ # Copyright 2011 Joyent, Inc. All rights reserved.
66
+ # Copyright 2011 Brendan Gregg. All rights reserved.
67
+ #
68
+ # CDDL HEADER START
69
+ #
70
+ # The contents of this file are subject to the terms of the
71
+ # Common Development and Distribution License (the "License").
72
+ # You may not use this file except in compliance with the License.
73
+ #
74
+ # You can obtain a copy of the license at docs/cddl1.txt or
75
+ # http://opensource.org/licenses/CDDL-1.0.
76
+ # See the License for the specific language governing permissions
77
+ # and limitations under the License.
78
+ #
79
+ # When distributing Covered Code, include this CDDL HEADER in each
80
+ # file and include the License file at docs/cddl1.txt.
81
+ # If applicable, add the following below this CDDL HEADER, with the
82
+ # fields enclosed by brackets "[]" replaced with your own identifying
83
+ # information: Portions Copyright [yyyy] [name of copyright owner]
84
+ #
85
+ # CDDL HEADER END
86
+ #
87
+ # 11-Oct-2014 Adrien Mahieux Added zoom.
88
+ # 21-Nov-2013 Shawn Sterling Added consistent palette file option
89
+ # 17-Mar-2013 Tim Bunce Added options and more tunables.
90
+ # 15-Dec-2011 Dave Pacheco Support for frames with whitespace.
91
+ # 10-Sep-2011 Brendan Gregg Created this.
92
+
93
+ use strict;
94
+
95
+ use Getopt::Long;
96
+
97
+ use open qw(:std :utf8);
98
+
99
+ # tunables
100
+ my $encoding;
101
+ my $fonttype = "Verdana";
102
+ my $imagewidth = 1200; # max width, pixels
103
+ my $frameheight = 16; # max height is dynamic
104
+ my $fontsize = 12; # base text size
105
+ my $fontwidth = 0.59; # avg width relative to fontsize
106
+ my $minwidth = 0.1; # min function width, pixels
107
+ my $nametype = "Function:"; # what are the names in the data?
108
+ my $countname = "samples"; # what are the counts in the data?
109
+ my $colors = "hot"; # color theme
110
+ my $bgcolor1 = "#eeeeee"; # background color gradient start
111
+ my $bgcolor2 = "#eeeeb0"; # background color gradient stop
112
+ my $nameattrfile; # file holding function attributes
113
+ my $timemax; # (override the) sum of the counts
114
+ my $factor = 1; # factor to scale counts by
115
+ my $hash = 0; # color by function name
116
+ my $palette = 0; # if we use consistent palettes (default off)
117
+ my %palette_map; # palette map hash
118
+ my $pal_file = "palette.map"; # palette map file name
119
+ my $stackreverse = 0; # reverse stack order, switching merge end
120
+ my $inverted = 0; # icicle graph
121
+ my $negate = 0; # switch differential hues
122
+ my $titletext = ""; # centered heading
123
+ my $titledefault = "Flame Graph"; # overwritten by --title
124
+ my $titleinverted = "Icicle Graph"; # " "
125
+ my $searchcolor = "rgb(230,0,230)"; # color for search highlighting
126
+ my $help = 0;
127
+
128
+ sub usage {
129
+ die <<USAGE_END;
130
+ USAGE: $0 [options] infile > outfile.svg\n
131
+ --title # change title text
132
+ --width # width of image (default 1200)
133
+ --height # height of each frame (default 16)
134
+ --minwidth # omit smaller functions (default 0.1 pixels)
135
+ --fonttype # font type (default "Verdana")
136
+ --fontsize # font size (default 12)
137
+ --countname # count type label (default "samples")
138
+ --nametype # name type label (default "Function:")
139
+ --colors # set color palette. choices are: hot (default), mem, io,
140
+ # wakeup, chain, java, js, perl, red, green, blue, aqua,
141
+ # yellow, purple, orange
142
+ --hash # colors are keyed by function name hash
143
+ --cp # use consistent palette (palette.map)
144
+ --reverse # generate stack-reversed flame graph
145
+ --inverted # icicle graph
146
+ --negate # switch differential hues (blue<->red)
147
+ --help # this message
148
+
149
+ eg,
150
+ $0 --title="Flame Graph: malloc()" trace.txt > graph.svg
151
+ USAGE_END
152
+ }
153
+
154
+ GetOptions(
155
+ 'fonttype=s' => \$fonttype,
156
+ 'width=i' => \$imagewidth,
157
+ 'height=i' => \$frameheight,
158
+ 'encoding=s' => \$encoding,
159
+ 'fontsize=f' => \$fontsize,
160
+ 'fontwidth=f' => \$fontwidth,
161
+ 'minwidth=f' => \$minwidth,
162
+ 'title=s' => \$titletext,
163
+ 'nametype=s' => \$nametype,
164
+ 'countname=s' => \$countname,
165
+ 'nameattr=s' => \$nameattrfile,
166
+ 'total=s' => \$timemax,
167
+ 'factor=f' => \$factor,
168
+ 'colors=s' => \$colors,
169
+ 'hash' => \$hash,
170
+ 'cp' => \$palette,
171
+ 'reverse' => \$stackreverse,
172
+ 'inverted' => \$inverted,
173
+ 'negate' => \$negate,
174
+ 'help' => \$help,
175
+ ) or usage();
176
+ $help && usage();
177
+
178
+ # internals
179
+ my $ypad1 = $fontsize * 4; # pad top, include title
180
+ my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels
181
+ my $xpad = 10; # pad lefm and right
182
+ my $framepad = 1; # vertical padding for frames
183
+ my $depthmax = 0;
184
+ my %Events;
185
+ my %nameattr;
186
+
187
+ if ($titletext eq "") {
188
+ unless ($inverted) {
189
+ $titletext = $titledefault;
190
+ } else {
191
+ $titletext = $titleinverted;
192
+ }
193
+ }
194
+
195
+ if ($nameattrfile) {
196
+ # The name-attribute file format is a function name followed by a tab then
197
+ # a sequence of tab separated name=value pairs.
198
+ open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n";
199
+ while (<$attrfh>) {
200
+ chomp;
201
+ my ($funcname, $attrstr) = split /\t/, $_, 2;
202
+ die "Invalid format in $nameattrfile" unless defined $attrstr;
203
+ $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr };
204
+ }
205
+ }
206
+
207
+ # background colors:
208
+ # - yellow gradient: default (hot, java, js, perl)
209
+ # - blue gradient: mem, chain
210
+ # - gray gradient: io, wakeup, flat colors (red, green, blue, ...)
211
+ if ($colors eq "mem" or $colors eq "chain") {
212
+ $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff";
213
+ }
214
+ if ($colors =~ /^(io|wakeup|red|green|blue|aqua|yellow|purple|orange)$/) {
215
+ $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8";
216
+ }
217
+
218
+ # SVG functions
219
+ { package SVG;
220
+ sub new {
221
+ my $class = shift;
222
+ my $self = {};
223
+ bless ($self, $class);
224
+ return $self;
225
+ }
226
+
227
+ sub header {
228
+ my ($self, $w, $h) = @_;
229
+ my $enc_attr = '';
230
+ if (defined $encoding) {
231
+ $enc_attr = qq{ encoding="$encoding"};
232
+ }
233
+ $self->{svg} .= <<SVG;
234
+ <?xml version="1.0"$enc_attr standalone="no"?>
235
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
236
+ <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">
237
+ <!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. -->
238
+ SVG
239
+ }
240
+
241
+ sub include {
242
+ my ($self, $content) = @_;
243
+ $self->{svg} .= $content;
244
+ }
245
+
246
+ sub colorAllocate {
247
+ my ($self, $r, $g, $b) = @_;
248
+ return "rgb($r,$g,$b)";
249
+ }
250
+
251
+ sub group_start {
252
+ my ($self, $attr) = @_;
253
+
254
+ my @g_attr = map {
255
+ exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : ()
256
+ } qw(class style onmouseover onmouseout onclick);
257
+ push @g_attr, $attr->{g_extra} if $attr->{g_extra};
258
+ $self->{svg} .= sprintf qq/<g %s>\n/, join(' ', @g_attr);
259
+
260
+ $self->{svg} .= sprintf qq/<title>%s<\/title>/, $attr->{title}
261
+ if $attr->{title}; # should be first element within g container
262
+
263
+ if ($attr->{href}) {
264
+ my @a_attr;
265
+ push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href};
266
+ # default target=_top else links will open within SVG <object>
267
+ push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top";
268
+ push @a_attr, $attr->{a_extra} if $attr->{a_extra};
269
+ $self->{svg} .= sprintf qq/<a %s>/, join(' ', @a_attr);
270
+ }
271
+ }
272
+
273
+ sub group_end {
274
+ my ($self, $attr) = @_;
275
+ $self->{svg} .= qq/<\/a>\n/ if $attr->{href};
276
+ $self->{svg} .= qq/<\/g>\n/;
277
+ }
278
+
279
+ sub filledRectangle {
280
+ my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
281
+ $x1 = sprintf "%0.1f", $x1;
282
+ $x2 = sprintf "%0.1f", $x2;
283
+ my $w = sprintf "%0.1f", $x2 - $x1;
284
+ my $h = sprintf "%0.1f", $y2 - $y1;
285
+ $extra = defined $extra ? $extra : "";
286
+ $self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
287
+ }
288
+
289
+ sub stringTTF {
290
+ my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
291
+ $x = sprintf "%0.2f", $x;
292
+ $loc = defined $loc ? $loc : "left";
293
+ $extra = defined $extra ? $extra : "";
294
+ $self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
295
+ }
296
+
297
+ sub svg {
298
+ my $self = shift;
299
+ return "$self->{svg}</svg>\n";
300
+ }
301
+ 1;
302
+ }
303
+
304
+ sub namehash {
305
+ # Generate a vector hash for the name string, weighting early over
306
+ # later characters. We want to pick the same colors for function
307
+ # names across different flame graphs.
308
+ my $name = shift;
309
+ my $vector = 0;
310
+ my $weight = 1;
311
+ my $max = 1;
312
+ my $mod = 10;
313
+ # if module name present, trunc to 1st char
314
+ $name =~ s/.(.*?)`//;
315
+ foreach my $c (split //, $name) {
316
+ my $i = (ord $c) % $mod;
317
+ $vector += ($i / ($mod++ - 1)) * $weight;
318
+ $max += 1 * $weight;
319
+ $weight *= 0.70;
320
+ last if $mod > 12;
321
+ }
322
+ return (1 - $vector / $max)
323
+ }
324
+
325
+ sub color {
326
+ my ($type, $hash, $name) = @_;
327
+ my ($v1, $v2, $v3);
328
+
329
+ if ($hash) {
330
+ $v1 = namehash($name);
331
+ $v2 = $v3 = namehash(scalar reverse $name);
332
+ } else {
333
+ $v1 = rand(1);
334
+ $v2 = rand(1);
335
+ $v3 = rand(1);
336
+ }
337
+
338
+ # theme palettes
339
+ if (defined $type and $type eq "hot") {
340
+ my $r = 205 + int(50 * $v3);
341
+ my $g = 0 + int(230 * $v1);
342
+ my $b = 0 + int(55 * $v2);
343
+ return "rgb($r,$g,$b)";
344
+ }
345
+ if (defined $type and $type eq "mem") {
346
+ my $r = 0;
347
+ my $g = 190 + int(50 * $v2);
348
+ my $b = 0 + int(210 * $v1);
349
+ return "rgb($r,$g,$b)";
350
+ }
351
+ if (defined $type and $type eq "io") {
352
+ my $r = 80 + int(60 * $v1);
353
+ my $g = $r;
354
+ my $b = 190 + int(55 * $v2);
355
+ return "rgb($r,$g,$b)";
356
+ }
357
+
358
+ # multi palettes
359
+ if (defined $type and $type eq "java") {
360
+ # Handle both annotations (_[j], _[i], ...; which are
361
+ # accurate), as well as input that lacks any annotations, as
362
+ # best as possible. Without annotations, we get a little hacky
363
+ # and match on java|org|com, etc.
364
+ if ($name =~ m:_\[j\]$:) { # jit annotation
365
+ $type = "green";
366
+ } elsif ($name =~ m:_\[i\]$:) { # inline annotation
367
+ $type = "aqua";
368
+ } elsif ($name =~ m:^L?(java|org|com|io|sun)/:) { # Java
369
+ $type = "green";
370
+ } elsif ($name =~ /::/) { # C++
371
+ $type = "yellow";
372
+ } elsif ($name =~ m:_\[k\]$:) { # kernel annotation
373
+ $type = "orange";
374
+ } else { # system
375
+ $type = "red";
376
+ }
377
+ # fall-through to color palettes
378
+ }
379
+ if (defined $type and $type eq "perl") {
380
+ if ($name =~ /::/) { # C++
381
+ $type = "yellow";
382
+ } elsif ($name =~ m:Perl: or $name =~ m:\.pl:) { # Perl
383
+ $type = "green";
384
+ } elsif ($name =~ m:_\[k\]$:) { # kernel
385
+ $type = "orange";
386
+ } else { # system
387
+ $type = "red";
388
+ }
389
+ # fall-through to color palettes
390
+ }
391
+ if (defined $type and $type eq "js") {
392
+ # Handle both annotations (_[j], _[i], ...; which are
393
+ # accurate), as well as input that lacks any annotations, as
394
+ # best as possible. Without annotations, we get a little hacky,
395
+ # and match on a "/" with a ".js", etc.
396
+ if ($name =~ m:_\[j\]$:) { # jit annotation
397
+ if ($name =~ m:/:) {
398
+ $type = "green"; # source
399
+ } else {
400
+ $type = "aqua"; # builtin
401
+ }
402
+ } elsif ($name =~ /::/) { # C++
403
+ $type = "yellow";
404
+ } elsif ($name =~ m:/.*\.js:) { # JavaScript (match "/" in path)
405
+ $type = "green";
406
+ } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin)
407
+ $type = "aqua";
408
+ } elsif ($name =~ m/^ $/) { # Missing symbol
409
+ $type = "green";
410
+ } elsif ($name =~ m:_\[k\]:) { # kernel
411
+ $type = "orange";
412
+ } else { # system
413
+ $type = "red";
414
+ }
415
+ # fall-through to color palettes
416
+ }
417
+ if (defined $type and $type eq "wakeup") {
418
+ $type = "aqua";
419
+ # fall-through to color palettes
420
+ }
421
+ if (defined $type and $type eq "chain") {
422
+ if ($name =~ m:_\[w\]:) { # waker
423
+ $type = "aqua"
424
+ } else { # off-CPU
425
+ $type = "blue";
426
+ }
427
+ # fall-through to color palettes
428
+ }
429
+
430
+ # color palettes
431
+ if (defined $type and $type eq "red") {
432
+ my $r = 200 + int(55 * $v1);
433
+ my $x = 50 + int(80 * $v1);
434
+ return "rgb($r,$x,$x)";
435
+ }
436
+ if (defined $type and $type eq "green") {
437
+ my $g = 200 + int(55 * $v1);
438
+ my $x = 50 + int(60 * $v1);
439
+ return "rgb($x,$g,$x)";
440
+ }
441
+ if (defined $type and $type eq "blue") {
442
+ my $b = 205 + int(50 * $v1);
443
+ my $x = 80 + int(60 * $v1);
444
+ return "rgb($x,$x,$b)";
445
+ }
446
+ if (defined $type and $type eq "yellow") {
447
+ my $x = 175 + int(55 * $v1);
448
+ my $b = 50 + int(20 * $v1);
449
+ return "rgb($x,$x,$b)";
450
+ }
451
+ if (defined $type and $type eq "purple") {
452
+ my $x = 190 + int(65 * $v1);
453
+ my $g = 80 + int(60 * $v1);
454
+ return "rgb($x,$g,$x)";
455
+ }
456
+ if (defined $type and $type eq "aqua") {
457
+ my $r = 50 + int(60 * $v1);
458
+ my $g = 165 + int(55 * $v1);
459
+ my $b = 165 + int(55 * $v1);
460
+ return "rgb($r,$g,$b)";
461
+ }
462
+ if (defined $type and $type eq "orange") {
463
+ my $r = 190 + int(65 * $v1);
464
+ my $g = 90 + int(65 * $v1);
465
+ return "rgb($r,$g,0)";
466
+ }
467
+
468
+ return "rgb(0,0,0)";
469
+ }
470
+
471
+ sub color_scale {
472
+ my ($value, $max) = @_;
473
+ my ($r, $g, $b) = (255, 255, 255);
474
+ $value = -$value if $negate;
475
+ if ($value > 0) {
476
+ $g = $b = int(210 * ($max - $value) / $max);
477
+ } elsif ($value < 0) {
478
+ $r = $g = int(210 * ($max + $value) / $max);
479
+ }
480
+ return "rgb($r,$g,$b)";
481
+ }
482
+
483
+ sub color_map {
484
+ my ($colors, $func) = @_;
485
+ if (exists $palette_map{$func}) {
486
+ return $palette_map{$func};
487
+ } else {
488
+ $palette_map{$func} = color($colors, $hash, $func);
489
+ return $palette_map{$func};
490
+ }
491
+ }
492
+
493
+ sub write_palette {
494
+ open(FILE, ">$pal_file");
495
+ foreach my $key (sort keys %palette_map) {
496
+ print FILE $key."->".$palette_map{$key}."\n";
497
+ }
498
+ close(FILE);
499
+ }
500
+
501
+ sub read_palette {
502
+ if (-e $pal_file) {
503
+ open(FILE, $pal_file) or die "can't open file $pal_file: $!";
504
+ while ( my $line = <FILE>) {
505
+ chomp($line);
506
+ (my $key, my $value) = split("->",$line);
507
+ $palette_map{$key}=$value;
508
+ }
509
+ close(FILE)
510
+ }
511
+ }
512
+
513
+ my %Node; # Hash of merged frame data
514
+ my %Tmp;
515
+
516
+ # flow() merges two stacks, storing the merged frames and value data in %Node.
517
+ sub flow {
518
+ my ($last, $this, $v, $d) = @_;
519
+
520
+ my $len_a = @$last - 1;
521
+ my $len_b = @$this - 1;
522
+
523
+ my $i = 0;
524
+ my $len_same;
525
+ for (; $i <= $len_a; $i++) {
526
+ last if $i > $len_b;
527
+ last if $last->[$i] ne $this->[$i];
528
+ }
529
+ $len_same = $i;
530
+
531
+ for ($i = $len_a; $i >= $len_same; $i--) {
532
+ my $k = "$last->[$i];$i";
533
+ # a unique ID is constructed from "func;depth;etime";
534
+ # func-depth isn't unique, it may be repeated later.
535
+ $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime};
536
+ if (defined $Tmp{$k}->{delta}) {
537
+ $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta};
538
+ }
539
+ delete $Tmp{$k};
540
+ }
541
+
542
+ for ($i = $len_same; $i <= $len_b; $i++) {
543
+ my $k = "$this->[$i];$i";
544
+ $Tmp{$k}->{stime} = $v;
545
+ if (defined $d) {
546
+ $Tmp{$k}->{delta} += $i == $len_b ? $d : 0;
547
+ }
548
+ }
549
+
550
+ return $this;
551
+ }
552
+
553
+ # parse input
554
+ my @Data;
555
+ my $last = [];
556
+ my $time = 0;
557
+ my $delta = undef;
558
+ my $ignored = 0;
559
+ my $line;
560
+ my $maxdelta = 1;
561
+
562
+ # reverse if needed
563
+ foreach (<>) {
564
+ chomp;
565
+ $line = $_;
566
+ if ($stackreverse) {
567
+ # there may be an extra samples column for differentials
568
+ # XXX todo: redo these REs as one. It's repeated below.
569
+ my($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/);
570
+ my $samples2 = undef;
571
+ if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) {
572
+ $samples2 = $samples;
573
+ ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/);
574
+ unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2";
575
+ } else {
576
+ unshift @Data, join(";", reverse split(";", $stack)) . " $samples";
577
+ }
578
+ } else {
579
+ unshift @Data, $line;
580
+ }
581
+ }
582
+
583
+ # process and merge frames
584
+ foreach (sort @Data) {
585
+ chomp;
586
+ # process: folded_stack count
587
+ # eg: func_a;func_b;func_c 31
588
+ my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/);
589
+ unless (defined $samples and defined $stack) {
590
+ ++$ignored;
591
+ next;
592
+ }
593
+
594
+ # there may be an extra samples column for differentials:
595
+ my $samples2 = undef;
596
+ if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) {
597
+ $samples2 = $samples;
598
+ ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/);
599
+ }
600
+ $delta = undef;
601
+ if (defined $samples2) {
602
+ $delta = $samples2 - $samples;
603
+ $maxdelta = abs($delta) if abs($delta) > $maxdelta;
604
+ }
605
+
606
+ # for chain graphs, annotate waker frames with "_[w]", for later
607
+ # coloring. This is a hack, but has a precedent ("_[k]" from perf).
608
+ if ($colors eq "chain") {
609
+ my @parts = split ";--;", $stack;
610
+ my @newparts = ();
611
+ $stack = shift @parts;
612
+ $stack .= ";--;";
613
+ foreach my $part (@parts) {
614
+ $part =~ s/;/_[w];/g;
615
+ $part .= "_[w]";
616
+ push @newparts, $part;
617
+ }
618
+ $stack .= join ";--;", @parts;
619
+ }
620
+
621
+ # merge frames and populate %Node:
622
+ $last = flow($last, [ '', split ";", $stack ], $time, $delta);
623
+
624
+ if (defined $samples2) {
625
+ $time += $samples2;
626
+ } else {
627
+ $time += $samples;
628
+ }
629
+ }
630
+ flow($last, [], $time, $delta);
631
+
632
+ warn "Ignored $ignored lines with invalid format\n" if $ignored;
633
+ unless ($time) {
634
+ warn "ERROR: No stack counts found\n";
635
+ my $im = SVG->new();
636
+ # emit an error message SVG, for tools automating flamegraph use
637
+ my $imageheight = $fontsize * 5;
638
+ $im->header($imagewidth, $imageheight);
639
+ $im->stringTTF($im->colorAllocate(0, 0, 0), $fonttype, $fontsize + 2,
640
+ 0.0, int($imagewidth / 2), $fontsize * 2,
641
+ "ERROR: No valid input provided to flamegraph.pl.", "middle");
642
+ print $im->svg;
643
+ exit 2;
644
+ }
645
+ if ($timemax and $timemax < $time) {
646
+ warn "Specified --total $timemax is less than actual total $time, so ignored\n"
647
+ if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc)
648
+ undef $timemax;
649
+ }
650
+ $timemax ||= $time;
651
+
652
+ my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
653
+ my $minwidth_time = $minwidth / $widthpertime;
654
+
655
+ # prune blocks that are too narrow and determine max depth
656
+ while (my ($id, $node) = each %Node) {
657
+ my ($func, $depth, $etime) = split ";", $id;
658
+ my $stime = $node->{stime};
659
+ die "missing start for $id" if not defined $stime;
660
+
661
+ if (($etime-$stime) < $minwidth_time) {
662
+ delete $Node{$id};
663
+ next;
664
+ }
665
+ $depthmax = $depth if $depth > $depthmax;
666
+ }
667
+
668
+ # draw canvas, and embed interactive JavaScript program
669
+ my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
670
+ my $im = SVG->new();
671
+ $im->header($imagewidth, $imageheight);
672
+ my $inc = <<INC;
673
+ <defs >
674
+ <linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
675
+ <stop stop-color="$bgcolor1" offset="5%" />
676
+ <stop stop-color="$bgcolor2" offset="95%" />
677
+ </linearGradient>
678
+ </defs>
679
+ <style type="text/css">
680
+ .func_g:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
681
+ </style>
682
+ <script type="text/ecmascript">
683
+ <![CDATA[
684
+ var details, searchbtn, matchedtxt, svg;
685
+ function init(evt) {
686
+ details = document.getElementById("details").firstChild;
687
+ searchbtn = document.getElementById("search");
688
+ matchedtxt = document.getElementById("matched");
689
+ svg = document.getElementsByTagName("svg")[0];
690
+ searching = 0;
691
+ }
692
+
693
+ // mouse-over for info
694
+ function s(node) { // show
695
+ info = g_to_text(node);
696
+ details.nodeValue = "$nametype " + info;
697
+ }
698
+ function c() { // clear
699
+ details.nodeValue = ' ';
700
+ }
701
+
702
+ // ctrl-F for search
703
+ window.addEventListener("keydown",function (e) {
704
+ if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
705
+ e.preventDefault();
706
+ search_prompt();
707
+ }
708
+ })
709
+
710
+ // functions
711
+ function find_child(parent, name, attr) {
712
+ var children = parent.childNodes;
713
+ for (var i=0; i<children.length;i++) {
714
+ if (children[i].tagName == name)
715
+ return (attr != undefined) ? children[i].attributes[attr].value : children[i];
716
+ }
717
+ return;
718
+ }
719
+ function orig_save(e, attr, val) {
720
+ if (e.attributes["_orig_"+attr] != undefined) return;
721
+ if (e.attributes[attr] == undefined) return;
722
+ if (val == undefined) val = e.attributes[attr].value;
723
+ e.setAttribute("_orig_"+attr, val);
724
+ }
725
+ function orig_load(e, attr) {
726
+ if (e.attributes["_orig_"+attr] == undefined) return;
727
+ e.attributes[attr].value = e.attributes["_orig_"+attr].value;
728
+ e.removeAttribute("_orig_"+attr);
729
+ }
730
+ function g_to_text(e) {
731
+ var text = find_child(e, "title").firstChild.nodeValue;
732
+ return (text)
733
+ }
734
+ function g_to_func(e) {
735
+ var func = g_to_text(e);
736
+ // if there's any manipulation we want to do to the function
737
+ // name before it's searched, do it here before returning.
738
+ return (func);
739
+ }
740
+ function update_text(e) {
741
+ var r = find_child(e, "rect");
742
+ var t = find_child(e, "text");
743
+ var w = parseFloat(r.attributes["width"].value) -3;
744
+ var txt = find_child(e, "title").textContent.replace(/\\([^(]*\\)\$/,"");
745
+ t.attributes["x"].value = parseFloat(r.attributes["x"].value) +3;
746
+
747
+ // Smaller than this size won't fit anything
748
+ if (w < 2*$fontsize*$fontwidth) {
749
+ t.textContent = "";
750
+ return;
751
+ }
752
+
753
+ t.textContent = txt;
754
+ // Fit in full text width
755
+ if (/^ *\$/.test(txt) || t.getSubStringLength(0, txt.length) < w)
756
+ return;
757
+
758
+ for (var x=txt.length-2; x>0; x--) {
759
+ if (t.getSubStringLength(0, x+2) <= w) {
760
+ t.textContent = txt.substring(0,x) + "..";
761
+ return;
762
+ }
763
+ }
764
+ t.textContent = "";
765
+ }
766
+
767
+ // zoom
768
+ function zoom_reset(e) {
769
+ if (e.attributes != undefined) {
770
+ orig_load(e, "x");
771
+ orig_load(e, "width");
772
+ }
773
+ if (e.childNodes == undefined) return;
774
+ for(var i=0, c=e.childNodes; i<c.length; i++) {
775
+ zoom_reset(c[i]);
776
+ }
777
+ }
778
+ function zoom_child(e, x, ratio) {
779
+ if (e.attributes != undefined) {
780
+ if (e.attributes["x"] != undefined) {
781
+ orig_save(e, "x");
782
+ e.attributes["x"].value = (parseFloat(e.attributes["x"].value) - x - $xpad) * ratio + $xpad;
783
+ if(e.tagName == "text") e.attributes["x"].value = find_child(e.parentNode, "rect", "x") + 3;
784
+ }
785
+ if (e.attributes["width"] != undefined) {
786
+ orig_save(e, "width");
787
+ e.attributes["width"].value = parseFloat(e.attributes["width"].value) * ratio;
788
+ }
789
+ }
790
+
791
+ if (e.childNodes == undefined) return;
792
+ for(var i=0, c=e.childNodes; i<c.length; i++) {
793
+ zoom_child(c[i], x-$xpad, ratio);
794
+ }
795
+ }
796
+ function zoom_parent(e) {
797
+ if (e.attributes) {
798
+ if (e.attributes["x"] != undefined) {
799
+ orig_save(e, "x");
800
+ e.attributes["x"].value = $xpad;
801
+ }
802
+ if (e.attributes["width"] != undefined) {
803
+ orig_save(e, "width");
804
+ e.attributes["width"].value = parseInt(svg.width.baseVal.value) - ($xpad*2);
805
+ }
806
+ }
807
+ if (e.childNodes == undefined) return;
808
+ for(var i=0, c=e.childNodes; i<c.length; i++) {
809
+ zoom_parent(c[i]);
810
+ }
811
+ }
812
+ function zoom(node) {
813
+ var attr = find_child(node, "rect").attributes;
814
+ var width = parseFloat(attr["width"].value);
815
+ var xmin = parseFloat(attr["x"].value);
816
+ var xmax = parseFloat(xmin + width);
817
+ var ymin = parseFloat(attr["y"].value);
818
+ var ratio = (svg.width.baseVal.value - 2*$xpad) / width;
819
+
820
+ // XXX: Workaround for JavaScript float issues (fix me)
821
+ var fudge = 0.0001;
822
+
823
+ var unzoombtn = document.getElementById("unzoom");
824
+ unzoombtn.style["opacity"] = "1.0";
825
+
826
+ var el = document.getElementsByTagName("g");
827
+ for(var i=0;i<el.length;i++){
828
+ var e = el[i];
829
+ var a = find_child(e, "rect").attributes;
830
+ var ex = parseFloat(a["x"].value);
831
+ var ew = parseFloat(a["width"].value);
832
+ // Is it an ancestor
833
+ if ($inverted == 0) {
834
+ var upstack = parseFloat(a["y"].value) > ymin;
835
+ } else {
836
+ var upstack = parseFloat(a["y"].value) < ymin;
837
+ }
838
+ if (upstack) {
839
+ // Direct ancestor
840
+ if (ex <= xmin && (ex+ew+fudge) >= xmax) {
841
+ e.style["opacity"] = "0.5";
842
+ zoom_parent(e);
843
+ e.onclick = function(e){unzoom(); zoom(this);};
844
+ update_text(e);
845
+ }
846
+ // not in current path
847
+ else
848
+ e.style["display"] = "none";
849
+ }
850
+ // Children maybe
851
+ else {
852
+ // no common path
853
+ if (ex < xmin || ex + fudge >= xmax) {
854
+ e.style["display"] = "none";
855
+ }
856
+ else {
857
+ zoom_child(e, xmin, ratio);
858
+ e.onclick = function(e){zoom(this);};
859
+ update_text(e);
860
+ }
861
+ }
862
+ }
863
+ }
864
+ function unzoom() {
865
+ var unzoombtn = document.getElementById("unzoom");
866
+ unzoombtn.style["opacity"] = "0.0";
867
+
868
+ var el = document.getElementsByTagName("g");
869
+ for(i=0;i<el.length;i++) {
870
+ el[i].style["display"] = "block";
871
+ el[i].style["opacity"] = "1";
872
+ zoom_reset(el[i]);
873
+ update_text(el[i]);
874
+ }
875
+ }
876
+
877
+ // search
878
+ function reset_search() {
879
+ var el = document.getElementsByTagName("rect");
880
+ for (var i=0; i < el.length; i++) {
881
+ orig_load(el[i], "fill")
882
+ }
883
+ }
884
+ function search_prompt() {
885
+ if (!searching) {
886
+ var term = prompt("Enter a search term (regexp " +
887
+ "allowed, eg: ^ext4_)", "");
888
+ if (term != null) {
889
+ search(term)
890
+ }
891
+ } else {
892
+ reset_search();
893
+ searching = 0;
894
+ searchbtn.style["opacity"] = "0.1";
895
+ searchbtn.firstChild.nodeValue = "Search"
896
+ matchedtxt.style["opacity"] = "0.0";
897
+ matchedtxt.firstChild.nodeValue = ""
898
+ }
899
+ }
900
+ function search(term) {
901
+ var re = new RegExp(term);
902
+ var el = document.getElementsByTagName("g");
903
+ var matches = new Object();
904
+ var maxwidth = 0;
905
+ for (var i = 0; i < el.length; i++) {
906
+ var e = el[i];
907
+ if (e.attributes["class"].value != "func_g")
908
+ continue;
909
+ var func = g_to_func(e);
910
+ var rect = find_child(e, "rect");
911
+ if (rect == null) {
912
+ // the rect might be wrapped in an anchor
913
+ // if nameattr href is being used
914
+ if (rect = find_child(e, "a")) {
915
+ rect = find_child(r, "rect");
916
+ }
917
+ }
918
+ if (func == null || rect == null)
919
+ continue;
920
+
921
+ // Save max width. Only works as we have a root frame
922
+ var w = parseFloat(rect.attributes["width"].value);
923
+ if (w > maxwidth)
924
+ maxwidth = w;
925
+
926
+ if (func.match(re)) {
927
+ // highlight
928
+ var x = parseFloat(rect.attributes["x"].value);
929
+ orig_save(rect, "fill");
930
+ rect.attributes["fill"].value =
931
+ "$searchcolor";
932
+
933
+ // remember matches
934
+ if (matches[x] == undefined) {
935
+ matches[x] = w;
936
+ } else {
937
+ if (w > matches[x]) {
938
+ // overwrite with parent
939
+ matches[x] = w;
940
+ }
941
+ }
942
+ searching = 1;
943
+ }
944
+ }
945
+ if (!searching)
946
+ return;
947
+
948
+ searchbtn.style["opacity"] = "1.0";
949
+ searchbtn.firstChild.nodeValue = "Reset Search"
950
+
951
+ // calculate percent matched, excluding vertical overlap
952
+ var count = 0;
953
+ var lastx = -1;
954
+ var lastw = 0;
955
+ var keys = Array();
956
+ for (k in matches) {
957
+ if (matches.hasOwnProperty(k))
958
+ keys.push(k);
959
+ }
960
+ // sort the matched frames by their x location
961
+ // ascending, then width descending
962
+ keys.sort(function(a, b){
963
+ return a - b;
964
+ if (a < b || a > b)
965
+ return a - b;
966
+ return matches[b] - matches[a];
967
+ });
968
+ // Step through frames saving only the biggest bottom-up frames
969
+ // thanks to the sort order. This relies on the tree property
970
+ // where children are always smaller than their parents.
971
+ for (var k in keys) {
972
+ var x = parseFloat(keys[k]);
973
+ var w = matches[keys[k]];
974
+ if (x >= lastx + lastw) {
975
+ count += w;
976
+ lastx = x;
977
+ lastw = w;
978
+ }
979
+ }
980
+ // display matched percent
981
+ matchedtxt.style["opacity"] = "1.0";
982
+ pct = 100 * count / maxwidth;
983
+ if (pct == 100)
984
+ pct = "100"
985
+ else
986
+ pct = pct.toFixed(1)
987
+ matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
988
+ }
989
+ function searchover(e) {
990
+ searchbtn.style["opacity"] = "1.0";
991
+ }
992
+ function searchout(e) {
993
+ if (searching) {
994
+ searchbtn.style["opacity"] = "1.0";
995
+ } else {
996
+ searchbtn.style["opacity"] = "0.1";
997
+ }
998
+ }
999
+ ]]>
1000
+ </script>
1001
+ INC
1002
+ $im->include($inc);
1003
+ $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
1004
+ my ($white, $black, $vvdgrey, $vdgrey, $dgrey) = (
1005
+ $im->colorAllocate(255, 255, 255),
1006
+ $im->colorAllocate(0, 0, 0),
1007
+ $im->colorAllocate(40, 40, 40),
1008
+ $im->colorAllocate(160, 160, 160),
1009
+ $im->colorAllocate(200, 200, 200),
1010
+ );
1011
+ $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle");
1012
+ $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
1013
+ $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $fontsize * 2,
1014
+ "Reset Zoom", "", 'id="unzoom" onclick="unzoom()" style="opacity:0.0;cursor:pointer"');
1015
+ $im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100,
1016
+ $fontsize * 2, "Search", "", 'id="search" onmouseover="searchover()" onmouseout="searchout()" onclick="search_prompt()" style="opacity:0.1;cursor:pointer"');
1017
+ $im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), " ", "", 'id="matched"');
1018
+
1019
+ if ($palette) {
1020
+ read_palette();
1021
+ }
1022
+
1023
+ # draw frames
1024
+ while (my ($id, $node) = each %Node) {
1025
+ my ($func, $depth, $etime) = split ";", $id;
1026
+ my $stime = $node->{stime};
1027
+ my $delta = $node->{delta};
1028
+
1029
+ $etime = $timemax if $func eq "" and $depth == 0;
1030
+
1031
+ my $x1 = $xpad + $stime * $widthpertime;
1032
+ my $x2 = $xpad + $etime * $widthpertime;
1033
+ my ($y1, $y2);
1034
+ unless ($inverted) {
1035
+ $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad;
1036
+ $y2 = $imageheight - $ypad2 - $depth * $frameheight;
1037
+ } else {
1038
+ $y1 = $ypad1 + $depth * $frameheight;
1039
+ $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad;
1040
+ }
1041
+
1042
+ my $samples = sprintf "%.0f", ($etime - $stime) * $factor;
1043
+ (my $samples_txt = $samples) # add commas per perlfaq5
1044
+ =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
1045
+
1046
+ my $info;
1047
+ if ($func eq "" and $depth == 0) {
1048
+ $info = "all ($samples_txt $countname, 100%)";
1049
+ } else {
1050
+ my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor));
1051
+ my $escaped_func = $func;
1052
+ # clean up SVG breaking characters:
1053
+ $escaped_func =~ s/&/&amp;/g;
1054
+ $escaped_func =~ s/</&lt;/g;
1055
+ $escaped_func =~ s/>/&gt;/g;
1056
+ $escaped_func =~ s/"/&quot;/g;
1057
+ $escaped_func =~ s/_\[[kwij]\]$//; # strip any annotation
1058
+ unless (defined $delta) {
1059
+ $info = "$escaped_func ($samples_txt $countname, $pct%)";
1060
+ } else {
1061
+ my $d = $negate ? -$delta : $delta;
1062
+ my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor));
1063
+ $deltapct = $d > 0 ? "+$deltapct" : $deltapct;
1064
+ $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)";
1065
+ }
1066
+ }
1067
+
1068
+ my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone
1069
+ $nameattr->{class} ||= "func_g";
1070
+ $nameattr->{onmouseover} ||= "s(this)";
1071
+ $nameattr->{onmouseout} ||= "c()";
1072
+ $nameattr->{onclick} ||= "zoom(this)";
1073
+ $nameattr->{title} ||= $info;
1074
+ $im->group_start($nameattr);
1075
+
1076
+ my $color;
1077
+ if ($func eq "--") {
1078
+ $color = $vdgrey;
1079
+ } elsif ($func eq "-") {
1080
+ $color = $dgrey;
1081
+ } elsif (defined $delta) {
1082
+ $color = color_scale($delta, $maxdelta);
1083
+ } elsif ($palette) {
1084
+ $color = color_map($colors, $func);
1085
+ } else {
1086
+ $color = color($colors, $hash, $func);
1087
+ }
1088
+ $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"');
1089
+
1090
+ my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));
1091
+ my $text = "";
1092
+ if ($chars >= 3) { # room for one char plus two dots
1093
+ $func =~ s/_\[[kwij]\]$//; # strip any annotation
1094
+ $text = substr $func, 0, $chars;
1095
+ substr($text, -2, 2) = ".." if $chars < length $func;
1096
+ $text =~ s/&/&amp;/g;
1097
+ $text =~ s/</&lt;/g;
1098
+ $text =~ s/>/&gt;/g;
1099
+ }
1100
+ $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "");
1101
+
1102
+ $im->group_end($nameattr);
1103
+ }
1104
+
1105
+ print $im->svg;
1106
+
1107
+ if ($palette) {
1108
+ write_palette();
1109
+ }
1110
+
1111
+ # vim: ts=8 sts=8 sw=8 noexpandtab