flameboyant 0.1.0

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.
@@ -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