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