// Copyright 2016 Netflix, Inc.
// Copyright 2011 Joyent, Inc.  All rights reserved.
// Copyright 2011 Brendan Gregg.  All rights reserved.
//
// CDDL HEADER START
//
// The contents of this file are subject to the terms of the
// Common Development and Distribution License (the "License").
// You may not use this file except in compliance with the License.
//
// You can obtain a copy of the license at docs/cddl1.txt or
// http://opensource.org/licenses/CDDL-1.0.
// See the License for the specific language governing permissions
// and limitations under the License.
//
// When distributing Covered Code, include this CDDL HEADER in each
// file and include the License file at docs/cddl1.txt.
// If applicable, add the following below this CDDL HEADER, with the
// fields enclosed by brackets "[]" replaced with your own identifying
// information: Portions Copyright [yyyy] [name of copyright owner]
//
// CDDL HEADER END
//
// 11-Oct-2014	Adrien Mahieux	Added zoom.
// 21-Nov-2013   Shawn Sterling  Added consistent palette file option
// 17-Mar-2013   Tim Bunce       Added options and more tunables.
// 15-Dec-2011	Dave Pacheco	Support for frames with whitespace.
// 10-Sep-2011	Brendan Gregg	Created this.

#include "brpc/builtin/flamegraph_perl.h"

namespace brpc {

const char* flamegraph_perl() {
    return "#!/usr/bin/perl -w\n"
        "#\n"
        "# flamegraph.pl		flame stack grapher.\n"
        "#\n"
        "# This takes stack samples and renders a call graph, allowing hot functions\n"
        "# and codepaths to be quickly identified.  Stack samples can be generated using\n"
        "# tools such as DTrace, perf, SystemTap, and Instruments.\n"
        "#\n"
        "# USAGE: ./flamegraph.pl [options] input.txt > graph.svg\n"
        "#\n"
        "#        grep funcA input.txt | ./flamegraph.pl [options] > graph.svg\n"
        "#\n"
        "# Then open the resulting .svg in a web browser, for interactivity: mouse-over\n"
        "# frames for info, click to zoom, and ctrl-F to search.\n"
        "#\n"
        "# Options are listed in the usage message (--help).\n"
        "#\n"
        "# The input is stack frames and sample counts formatted as single lines.  Each\n"
        "# frame in the stack is semicolon separated, with a space and count at the end\n"
        "# of the line.  These can be generated for Linux perf script output using\n"
        "# stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools\n"
        "# using the other stackcollapse programs.  Example input:\n"
        "#\n"
        "#  swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1\n"
        "#\n"
        "# An optional extra column of counts can be provided to generate a differential\n"
        "# flame graph of the counts, colored red for more, and blue for less.  This\n"
        "# can be useful when using flame graphs for non-regression testing.\n"
        "# See the header comment in the difffolded.pl program for instructions.\n"
        "#\n"
        "# The input functions can optionally have annotations at the end of each\n"
        "# function name, following a precedent by some tools (Linux perf's _[k]):\n"
        "# 	_[k] for kernel\n"
        "#	_[i] for inlined\n"
        "#	_[j] for jit\n"
        "#	_[w] for waker\n"
        "# Some of the stackcollapse programs support adding these annotations, eg,\n"
        "# stackcollapse-perf.pl --kernel --jit. They are used merely for colors by\n"
        "# some palettes, eg, flamegraph.pl --color=java.\n"
        "#\n"
        "# The output flame graph shows relative presence of functions in stack samples.\n"
        "# The ordering on the x-axis has no meaning; since the data is samples, time\n"
        "# order of events is not known.  The order used sorts function names\n"
        "# alphabetically.\n"
        "#\n"
        "# While intended to process stack samples, this can also process stack traces.\n"
        "# For example, tracing stacks for memory allocation, or resource usage.  You\n"
        "# can use --title to set the title to reflect the content, and --countname\n"
        "# to change \"samples\" to \"bytes\" etc.\n"
        "#\n"
        "# There are a few different palettes, selectable using --color.  By default,\n"
        "# the colors are selected at random (except for differentials).  Functions\n"
        "# called \"-\" will be printed gray, which can be used for stack separators (eg,\n"
        "# between user and kernel stacks).\n"
        "#\n"
        "# HISTORY\n"
        "#\n"
        "# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb\n"
        "# program, which visualized function entry and return trace events.  As Neel\n"
        "# wrote: \"The output displayed is inspired by Roch's CallStackAnalyzer which\n"
        "# was in turn inspired by the work on vftrace by Jan Boerhout\".  See:\n"
        "# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and\n"
        "#\n"
        "# Copyright 2016 Netflix, Inc.\n"
        "# Copyright 2011 Joyent, Inc.  All rights reserved.\n"
        "# Copyright 2011 Brendan Gregg.  All rights reserved.\n"
        "#\n"
        "# CDDL HEADER START\n"
        "#\n"
        "# The contents of this file are subject to the terms of the\n"
        "# Common Development and Distribution License (the \"License\").\n"
        "# You may not use this file except in compliance with the License.\n"
        "#\n"
        "# You can obtain a copy of the license at docs/cddl1.txt or\n"
        "# http://opensource.org/licenses/CDDL-1.0.\n"
        "# See the License for the specific language governing permissions\n"
        "# and limitations under the License.\n"
        "#\n"
        "# When distributing Covered Code, include this CDDL HEADER in each\n"
        "# file and include the License file at docs/cddl1.txt.\n"
        "# If applicable, add the following below this CDDL HEADER, with the\n"
        "# fields enclosed by brackets \"[]\" replaced with your own identifying\n"
        "# information: Portions Copyright [yyyy] [name of copyright owner]\n"
        "#\n"
        "# CDDL HEADER END\n"
        "#\n"
        "# 11-Oct-2014	Adrien Mahieux	Added zoom.\n"
        "# 21-Nov-2013   Shawn Sterling  Added consistent palette file option\n"
        "# 17-Mar-2013   Tim Bunce       Added options and more tunables.\n"
        "# 15-Dec-2011	Dave Pacheco	Support for frames with whitespace.\n"
        "# 10-Sep-2011	Brendan Gregg	Created this.\n"
        "\n"
        "use strict;\n"
        "\n"
        "use Getopt::Long;\n"
        "\n"
        "use open qw(:std :utf8);\n"
        "\n"
        "# tunables\n"
        "my $encoding;\n"
        "my $fonttype = \"Verdana\";\n"
        "my $imagewidth = 1200;          # max width, pixels\n"
        "my $frameheight = 16;           # max height is dynamic\n"
        "my $fontsize = 12;              # base text size\n"
        "my $fontwidth = 0.59;           # avg width relative to fontsize\n"
        "my $minwidth = 0.1;             # min function width, pixels\n"
        "my $nametype = \"Function:\";     # what are the names in the data?\n"
        "my $countname = \"samples\";      # what are the counts in the data?\n"
        "my $colors = \"hot\";             # color theme\n"
        "my $bgcolor1 = \"#eeeeee\";       # background color gradient start\n"
        "my $bgcolor2 = \"#eeeeb0\";       # background color gradient stop\n"
        "my $nameattrfile;               # file holding function attributes\n"
        "my $timemax;                    # (override the) sum of the counts\n"
        "my $factor = 1;                 # factor to scale counts by\n"
        "my $hash = 0;                   # color by function name\n"
        "my $palette = 0;                # if we use consistent palettes (default off)\n"
        "my %palette_map;                # palette map hash\n"
        "my $pal_file = \"palette.map\";   # palette map file name\n"
        "my $stackreverse = 0;           # reverse stack order, switching merge end\n"
        "my $inverted = 0;               # icicle graph\n"
        "my $negate = 0;                 # switch differential hues\n"
        "my $titletext = \"\";             # centered heading\n"
        "my $titledefault = \"Flame Graph\";	# overwritten by --title\n"
        "my $titleinverted = \"Icicle Graph\";	#   \"    \"\n"
        "my $searchcolor = \"rgb(230,0,230)\";	# color for search highlighting\n"
        "my $notestext = \"\";		# embedded notes in SVG\n"
        "my $subtitletext = \"\";		# second level title (optional)\n"
        "my $help = 0;\n"
        "\n"
        "sub usage {\n"
        "	die <<USAGE_END;\n"
        "USAGE: $0 [options] infile > outfile.svg\\n\n"
        "	--title TEXT     # change title text\n"
        "	--subtitle TEXT  # second level title (optional)\n"
        "	--width NUM      # width of image (default 1200)\n"
        "	--height NUM     # height of each frame (default 16)\n"
        "	--minwidth NUM   # omit smaller functions (default 0.1 pixels)\n"
        "	--fonttype FONT  # font type (default \"Verdana\")\n"
        "	--fontsize NUM   # font size (default 12)\n"
        "	--countname TEXT # count type label (default \"samples\")\n"
        "	--nametype TEXT  # name type label (default \"Function:\")\n"
        "	--colors PALETTE # set color palette. choices are: hot (default), mem,\n"
        "	                 # io, wakeup, chain, java, js, perl, red, green, blue,\n"
        "	                 # aqua, yellow, purple, orange\n"
        "	--hash           # colors are keyed by function name hash\n"
        "	--cp             # use consistent palette (palette.map)\n"
        "	--reverse        # generate stack-reversed flame graph\n"
        "	--inverted       # icicle graph\n"
        "	--negate         # switch differential hues (blue<->red)\n"
        "	--notes TEXT     # add notes comment in SVG (for debugging)\n"
        "	--help           # this message\n"
        "\n"
        "	eg,\n"
        "	$0 --title=\"Flame Graph: malloc()\" trace.txt > graph.svg\n"
        "USAGE_END\n"
        "}\n"
        "\n"
        "GetOptions(\n"
        "	'fonttype=s'  => \\$fonttype,\n"
        "	'width=i'     => \\$imagewidth,\n"
        "	'height=i'    => \\$frameheight,\n"
        "	'encoding=s'  => \\$encoding,\n"
        "	'fontsize=f'  => \\$fontsize,\n"
        "	'fontwidth=f' => \\$fontwidth,\n"
        "	'minwidth=f'  => \\$minwidth,\n"
        "	'title=s'     => \\$titletext,\n"
        "	'subtitle=s'  => \\$subtitletext,\n"
        "	'nametype=s'  => \\$nametype,\n"
        "	'countname=s' => \\$countname,\n"
        "	'nameattr=s'  => \\$nameattrfile,\n"
        "	'total=s'     => \\$timemax,\n"
        "	'factor=f'    => \\$factor,\n"
        "	'colors=s'    => \\$colors,\n"
        "	'hash'        => \\$hash,\n"
        "	'cp'          => \\$palette,\n"
        "	'reverse'     => \\$stackreverse,\n"
        "	'inverted'    => \\$inverted,\n"
        "	'negate'      => \\$negate,\n"
        "	'notes=s'     => \\$notestext,\n"
        "	'help'        => \\$help,\n"
        ") or usage();\n"
        "$help && usage();\n"
        "\n"
        "# internals\n"
        "my $ypad1 = $fontsize * 3;      # pad top, include title\n"
        "my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels\n"
        "my $ypad3 = $fontsize * 2;      # pad top, include subtitle (optional)\n"
        "my $xpad = 10;                  # pad lefm and right\n"
        "my $framepad = 1;		# vertical padding for frames\n"
        "my $depthmax = 0;\n"
        "my %Events;\n"
        "my %nameattr;\n"
        "\n"
        "if ($titletext eq \"\") {\n"
        "	unless ($inverted) {\n"
        "		$titletext = $titledefault;\n"
        "	} else {\n"
        "		$titletext = $titleinverted;\n"
        "	}\n"
        "}\n"
        "\n"
        "if ($nameattrfile) {\n"
        "	# The name-attribute file format is a function name followed by a tab then\n"
        "	# a sequence of tab separated name=value pairs.\n"
        "	open my $attrfh, $nameattrfile or die \"Can't read $nameattrfile: $!\\n\";\n"
        "	while (<$attrfh>) {\n"
        "		chomp;\n"
        "		my ($funcname, $attrstr) = split /\\t/, $_, 2;\n"
        "		die \"Invalid format in $nameattrfile\" unless defined $attrstr;\n"
        "		$nameattr{$funcname} = { map { split /=/, $_, 2 } split /\\t/, $attrstr "
        "};\n"
        "	}\n"
        "}\n"
        "\n"
        "if ($notestext =~ /[<>]/) {\n"
        "	die \"Notes string can't contain < or >\"\n"
        "}\n"
        "\n"
        "# background colors:\n"
        "# - yellow gradient: default (hot, java, js, perl)\n"
        "# - blue gradient: mem, chain\n"
        "# - gray gradient: io, wakeup, flat colors (red, green, blue, ...)\n"
        "if ($colors eq \"mem\" or $colors eq \"chain\") {\n"
        "	$bgcolor1 = \"#eeeeee\"; $bgcolor2 = \"#e0e0ff\";\n"
        "}\n"
        "if ($colors =~ /^(io|wakeup|red|green|blue|aqua|yellow|purple|orange)$/) {\n"
        "	$bgcolor1 = \"#f8f8f8\"; $bgcolor2 = \"#e8e8e8\";\n"
        "}\n"
        "\n"
        "# SVG functions\n"
        "{ package SVG;\n"
        "	sub new {\n"
        "		my $class = shift;\n"
        "		my $self = {};\n"
        "		bless ($self, $class);\n"
        "		return $self;\n"
        "	}\n"
        "\n"
        "	sub header {\n"
        "		my ($self, $w, $h) = @_;\n"
        "		my $enc_attr = '';\n"
        "		if (defined $encoding) {\n"
        "			$enc_attr = qq{ encoding=\"$encoding\"};\n"
        "		}\n"
        "		$self->{svg} .= <<SVG;\n"
        "<?xml version=\"1.0\"$enc_attr standalone=\"no\"?>\n"
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
        "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
        "<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\">\n"
        "<!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph "
        "for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. "
        "-->\n"
        "<!-- NOTES: $notestext -->\n"
        "SVG\n"
        "	}\n"
        "\n"
        "	sub include {\n"
        "		my ($self, $content) = @_;\n"
        "		$self->{svg} .= $content;\n"
        "	}\n"
        "\n"
        "	sub colorAllocate {\n"
        "		my ($self, $r, $g, $b) = @_;\n"
        "		return \"rgb($r,$g,$b)\";\n"
        "	}\n"
        "\n"
        "	sub group_start {\n"
        "		my ($self, $attr) = @_;\n"
        "\n"
        "		my @g_attr = map {\n"
        "			exists $attr->{$_} ? sprintf(qq/$_=\"%s\"/, $attr->{$_}) : ()\n"
        "		} qw(class style onmouseover onmouseout onclick);\n"
        "		push @g_attr, $attr->{g_extra} if $attr->{g_extra};\n"
        "		$self->{svg} .= sprintf qq/<g %s>\\n/, join(' ', @g_attr);\n"
        "\n"
        "		$self->{svg} .= sprintf qq/<title>%s<\\/title>/, $attr->{title}\n"
        "			if $attr->{title}; # should be first element within g container\n"
        "\n"
        "		if ($attr->{href}) {\n"
        "			my @a_attr;\n"
        "			push @a_attr, sprintf qq/xlink:href=\"%s\"/, $attr->{href} if "
        "$attr->{href};\n"
        "			# default target=_top else links will open within SVG <object>\n"
        "			push @a_attr, sprintf qq/target=\"%s\"/, $attr->{target} || "
        "\"_top\";\n"
        "			push @a_attr, $attr->{a_extra}                           if "
        "$attr->{a_extra};\n"
        "			$self->{svg} .= sprintf qq/<a %s>/, join(' ', @a_attr);\n"
        "		}\n"
        "	}\n"
        "\n"
        "	sub group_end {\n"
        "		my ($self, $attr) = @_;\n"
        "		$self->{svg} .= qq/<\\/a>\\n/ if $attr->{href};\n"
        "		$self->{svg} .= qq/<\\/g>\\n/;\n"
        "	}\n"
        "\n"
        "	sub filledRectangle {\n"
        "		my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;\n"
        "		$x1 = sprintf \"%0.1f\", $x1;\n"
        "		$x2 = sprintf \"%0.1f\", $x2;\n"
        "		my $w = sprintf \"%0.1f\", $x2 - $x1;\n"
        "		my $h = sprintf \"%0.1f\", $y2 - $y1;\n"
        "		$extra = defined $extra ? $extra : \"\";\n"
        "		$self->{svg} .= qq/<rect x=\"$x1\" y=\"$y1\" width=\"$w\" height=\"$h\" "
        "fill=\"$fill\" $extra \\/>\\n/;\n"
        "	}\n"
        "\n"
        "	sub stringTTF {\n"
        "		my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = "
        "@_;\n"
        "		$x = sprintf \"%0.2f\", $x;\n"
        "		$loc = defined $loc ? $loc : \"left\";\n"
        "		$extra = defined $extra ? $extra : \"\";\n"
        "		$self->{svg} .= qq/<text text-anchor=\"$loc\" x=\"$x\" y=\"$y\" "
        "font-size=\"$size\" font-family=\"$font\" fill=\"$color\" $extra >$str<\\/text>\\n/;\n"
        "	}\n"
        "\n"
        "	sub svg {\n"
        "		my $self = shift;\n"
        "		return \"$self->{svg}</svg>\\n\";\n"
        "	}\n"
        "	1;\n"
        "}\n"
        "\n"
        "sub namehash {\n"
        "	# Generate a vector hash for the name string, weighting early over\n"
        "	# later characters. We want to pick the same colors for function\n"
        "	# names across different flame graphs.\n"
        "	my $name = shift;\n"
        "	my $vector = 0;\n"
        "	my $weight = 1;\n"
        "	my $max = 1;\n"
        "	my $mod = 10;\n"
        "	# if module name present, trunc to 1st char\n"
        "	$name =~ s/.(.*?)`//;\n"
        "	foreach my $c (split //, $name) {\n"
        "		my $i = (ord $c) % $mod;\n"
        "		$vector += ($i / ($mod++ - 1)) * $weight;\n"
        "		$max += 1 * $weight;\n"
        "		$weight *= 0.70;\n"
        "		last if $mod > 12;\n"
        "	}\n"
        "	return (1 - $vector / $max)\n"
        "}\n"
        "\n"
        "sub color {\n"
        "	my ($type, $hash, $name) = @_;\n"
        "	my ($v1, $v2, $v3);\n"
        "\n"
        "	if ($hash) {\n"
        "		$v1 = namehash($name);\n"
        "		$v2 = $v3 = namehash(scalar reverse $name);\n"
        "	} else {\n"
        "		$v1 = rand(1);\n"
        "		$v2 = rand(1);\n"
        "		$v3 = rand(1);\n"
        "	}\n"
        "\n"
        "	# theme palettes\n"
        "	if (defined $type and $type eq \"hot\") {\n"
        "		my $r = 205 + int(50 * $v3);\n"
        "		my $g = 0 + int(230 * $v1);\n"
        "		my $b = 0 + int(55 * $v2);\n"
        "		return \"rgb($r,$g,$b)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"mem\") {\n"
        "		my $r = 0;\n"
        "		my $g = 190 + int(50 * $v2);\n"
        "		my $b = 0 + int(210 * $v1);\n"
        "		return \"rgb($r,$g,$b)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"io\") {\n"
        "		my $r = 80 + int(60 * $v1);\n"
        "		my $g = $r;\n"
        "		my $b = 190 + int(55 * $v2);\n"
        "		return \"rgb($r,$g,$b)\";\n"
        "	}\n"
        "\n"
        "	# multi palettes\n"
        "	if (defined $type and $type eq \"java\") {\n"
        "		# Handle both annotations (_[j], _[i], ...; which are\n"
        "		# accurate), as well as input that lacks any annotations, as\n"
        "		# best as possible. Without annotations, we get a little hacky\n"
        "		# and match on java|org|com, etc.\n"
        "		if ($name =~ m:_\\[j\\]$:) {	# jit annotation\n"
        "			$type = \"green\";\n"
        "		} elsif ($name =~ m:_\\[i\\]$:) {	# inline annotation\n"
        "			$type = \"aqua\";\n"
        "		} elsif ($name =~ m:^L?(java|org|com|io|sun)/:) {	# Java\n"
        "			$type = \"green\";\n"
        "		} elsif ($name =~ /::/) {	# C++\n"
        "			$type = \"yellow\";\n"
        "		} elsif ($name =~ m:_\\[k\\]$:) {	# kernel annotation\n"
        "			$type = \"orange\";\n"
        "		} else {			# system\n"
        "			$type = \"red\";\n"
        "		}\n"
        "		# fall-through to color palettes\n"
        "	}\n"
        "	if (defined $type and $type eq \"perl\") {\n"
        "		if ($name =~ /::/) {		# C++\n"
        "			$type = \"yellow\";\n"
        "		} elsif ($name =~ m:Perl: or $name =~ m:\\.pl:) {	# Perl\n"
        "			$type = \"green\";\n"
        "		} elsif ($name =~ m:_\\[k\\]$:) {	# kernel\n"
        "			$type = \"orange\";\n"
        "		} else {			# system\n"
        "			$type = \"red\";\n"
        "		}\n"
        "		# fall-through to color palettes\n"
        "	}\n"
        "	if (defined $type and $type eq \"js\") {\n"
        "		# Handle both annotations (_[j], _[i], ...; which are\n"
        "		# accurate), as well as input that lacks any annotations, as\n"
        "		# best as possible. Without annotations, we get a little hacky,\n"
        "		# and match on a \"/\" with a \".js\", etc.\n"
        "		if ($name =~ m:_\\[j\\]$:) {	# jit annotation\n"
        "			if ($name =~ m:/:) {\n"
        "				$type = \"green\";	# source\n"
        "			} else {\n"
        "				$type = \"aqua\";		# builtin\n"
        "			}\n"
        "		} elsif ($name =~ /::/) {	# C++\n"
        "			$type = \"yellow\";\n"
        "		} elsif ($name =~ m:/.*\\.js:) {	# JavaScript (match \"/\" in "
        "path)\n"
        "			$type = \"green\";\n"
        "		} elsif ($name =~ m/:/) {	# JavaScript (match \":\" in builtin)\n"
        "			$type = \"aqua\";\n"
        "		} elsif ($name =~ m/^ $/) {	# Missing symbol\n"
        "			$type = \"green\";\n"
        "		} elsif ($name =~ m:_\\[k\\]:) {	# kernel\n"
        "			$type = \"orange\";\n"
        "		} else {			# system\n"
        "			$type = \"red\";\n"
        "		}\n"
        "		# fall-through to color palettes\n"
        "	}\n"
        "	if (defined $type and $type eq \"wakeup\") {\n"
        "		$type = \"aqua\";\n"
        "		# fall-through to color palettes\n"
        "	}\n"
        "	if (defined $type and $type eq \"chain\") {\n"
        "		if ($name =~ m:_\\[w\\]:) {	# waker\n"
        "			$type = \"aqua\"\n"
        "		} else {			# off-CPU\n"
        "			$type = \"blue\";\n"
        "		}\n"
        "		# fall-through to color palettes\n"
        "	}\n"
        "\n"
        "	# color palettes\n"
        "	if (defined $type and $type eq \"red\") {\n"
        "		my $r = 200 + int(55 * $v1);\n"
        "		my $x = 50 + int(80 * $v1);\n"
        "		return \"rgb($r,$x,$x)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"green\") {\n"
        "		my $g = 200 + int(55 * $v1);\n"
        "		my $x = 50 + int(60 * $v1);\n"
        "		return \"rgb($x,$g,$x)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"blue\") {\n"
        "		my $b = 205 + int(50 * $v1);\n"
        "		my $x = 80 + int(60 * $v1);\n"
        "		return \"rgb($x,$x,$b)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"yellow\") {\n"
        "		my $x = 175 + int(55 * $v1);\n"
        "		my $b = 50 + int(20 * $v1);\n"
        "		return \"rgb($x,$x,$b)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"purple\") {\n"
        "		my $x = 190 + int(65 * $v1);\n"
        "		my $g = 80 + int(60 * $v1);\n"
        "		return \"rgb($x,$g,$x)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"aqua\") {\n"
        "		my $r = 50 + int(60 * $v1);\n"
        "		my $g = 165 + int(55 * $v1);\n"
        "		my $b = 165 + int(55 * $v1);\n"
        "		return \"rgb($r,$g,$b)\";\n"
        "	}\n"
        "	if (defined $type and $type eq \"orange\") {\n"
        "		my $r = 190 + int(65 * $v1);\n"
        "		my $g = 90 + int(65 * $v1);\n"
        "		return \"rgb($r,$g,0)\";\n"
        "	}\n"
        "\n"
        "	return \"rgb(0,0,0)\";\n"
        "}\n"
        "\n"
        "sub color_scale {\n"
        "	my ($value, $max) = @_;\n"
        "	my ($r, $g, $b) = (255, 255, 255);\n"
        "	$value = -$value if $negate;\n"
        "	if ($value > 0) {\n"
        "		$g = $b = int(210 * ($max - $value) / $max);\n"
        "	} elsif ($value < 0) {\n"
        "		$r = $g = int(210 * ($max + $value) / $max);\n"
        "	}\n"
        "	return \"rgb($r,$g,$b)\";\n"
        "}\n"
        "\n"
        "sub color_map {\n"
        "	my ($colors, $func) = @_;\n"
        "	if (exists $palette_map{$func}) {\n"
        "		return $palette_map{$func};\n"
        "	} else {\n"
        "		$palette_map{$func} = color($colors, $hash, $func);\n"
        "		return $palette_map{$func};\n"
        "	}\n"
        "}\n"
        "\n"
        "sub write_palette {\n"
        "	open(FILE, \">$pal_file\");\n"
        "	foreach my $key (sort keys %palette_map) {\n"
        "		print FILE $key.\"->\".$palette_map{$key}.\"\\n\";\n"
        "	}\n"
        "	close(FILE);\n"
        "}\n"
        "\n"
        "sub read_palette {\n"
        "	if (-e $pal_file) {\n"
        "	open(FILE, $pal_file) or die \"can't open file $pal_file: $!\";\n"
        "	while ( my $line = <FILE>) {\n"
        "		chomp($line);\n"
        "		(my $key, my $value) = split(\"->\",$line);\n"
        "		$palette_map{$key}=$value;\n"
        "	}\n"
        "	close(FILE)\n"
        "	}\n"
        "}\n"
        "\n"
        "my %Node;	# Hash of merged frame data\n"
        "my %Tmp;\n"
        "\n"
        "# flow() merges two stacks, storing the merged frames and value data in %Node.\n"
        "sub flow {\n"
        "	my ($last, $this, $v, $d) = @_;\n"
        "\n"
        "	my $len_a = @$last - 1;\n"
        "	my $len_b = @$this - 1;\n"
        "\n"
        "	my $i = 0;\n"
        "	my $len_same;\n"
        "	for (; $i <= $len_a; $i++) {\n"
        "		last if $i > $len_b;\n"
        "		last if $last->[$i] ne $this->[$i];\n"
        "	}\n"
        "	$len_same = $i;\n"
        "\n"
        "	for ($i = $len_a; $i >= $len_same; $i--) {\n"
        "		my $k = \"$last->[$i];$i\";\n"
        "		# a unique ID is constructed from \"func;depth;etime\";\n"
        "		# func-depth isn't unique, it may be repeated later.\n"
        "		$Node{\"$k;$v\"}->{stime} = delete $Tmp{$k}->{stime};\n"
        "		if (defined $Tmp{$k}->{delta}) {\n"
        "			$Node{\"$k;$v\"}->{delta} = delete $Tmp{$k}->{delta};\n"
        "		}\n"
        "		delete $Tmp{$k};\n"
        "	}\n"
        "\n"
        "	for ($i = $len_same; $i <= $len_b; $i++) {\n"
        "		my $k = \"$this->[$i];$i\";\n"
        "		$Tmp{$k}->{stime} = $v;\n"
        "		if (defined $d) {\n"
        "			$Tmp{$k}->{delta} += $i == $len_b ? $d : 0;\n"
        "		}\n"
        "	}\n"
        "\n"
        "        return $this;\n"
        "}\n"
        "\n"
        "# parse input\n"
        "my @Data;\n"
        "my $last = [];\n"
        "my $time = 0;\n"
        "my $delta = undef;\n"
        "my $ignored = 0;\n"
        "my $line;\n"
        "my $maxdelta = 1;\n"
        "\n"
        "# reverse if needed\n"
        "foreach (<>) {\n"
        "	chomp;\n"
        "	$line = $_;\n"
        "	if ($stackreverse) {\n"
        "		# there may be an extra samples column for differentials\n"
        "		# XXX todo: redo these REs as one. It's repeated below.\n"
        "		my($stack, $samples) = (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n"
        "		my $samples2 = undef;\n"
        "		if ($stack =~ /^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/) {\n"
        "			$samples2 = $samples;\n"
        "			($stack, $samples) = $stack =~ (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n"
        "			unshift @Data, join(\";\", reverse split(\";\", $stack)) . \" "
        "$samples $samples2\";\n"
        "		} else {\n"
        "			unshift @Data, join(\";\", reverse split(\";\", $stack)) . \" "
        "$samples\";\n"
        "		}\n"
        "	} else {\n"
        "		unshift @Data, $line;\n"
        "	}\n"
        "}\n"
        "\n"
        "# process and merge frames\n"
        "foreach (sort @Data) {\n"
        "	chomp;\n"
        "	# process: folded_stack count\n"
        "	# eg: func_a;func_b;func_c 31\n"
        "	my ($stack, $samples) = (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n"
        "	unless (defined $samples and defined $stack) {\n"
        "		++$ignored;\n"
        "		next;\n"
        "	}\n"
        "\n"
        "	# there may be an extra samples column for differentials:\n"
        "	my $samples2 = undef;\n"
        "	if ($stack =~ /^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/) {\n"
        "		$samples2 = $samples;\n"
        "		($stack, $samples) = $stack =~ (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n"
        "	}\n"
        "	$delta = undef;\n"
        "	if (defined $samples2) {\n"
        "		$delta = $samples2 - $samples;\n"
        "		$maxdelta = abs($delta) if abs($delta) > $maxdelta;\n"
        "	}\n"
        "\n"
        "	# for chain graphs, annotate waker frames with \"_[w]\", for later\n"
        "	# coloring. This is a hack, but has a precedent (\"_[k]\" from perf).\n"
        "	if ($colors eq \"chain\") {\n"
        "		my @parts = split \";--;\", $stack;\n"
        "		my @newparts = ();\n"
        "		$stack = shift @parts;\n"
        "		$stack .= \";--;\";\n"
        "		foreach my $part (@parts) {\n"
        "			$part =~ s/;/_[w];/g;\n"
        "			$part .= \"_[w]\";\n"
        "			push @newparts, $part;\n"
        "		}\n"
        "		$stack .= join \";--;\", @parts;\n"
        "	}\n"
        "\n"
        "	# merge frames and populate %Node:\n"
        "	$last = flow($last, [ '', split \";\", $stack ], $time, $delta);\n"
        "\n"
        "	if (defined $samples2) {\n"
        "		$time += $samples2;\n"
        "	} else {\n"
        "		$time += $samples;\n"
        "	}\n"
        "}\n"
        "flow($last, [], $time, $delta);\n"
        "\n"
        "warn \"Ignored $ignored lines with invalid format\\n\" if $ignored;\n"
        "unless ($time) {\n"
        "	warn \"ERROR: No stack counts found\\n\";\n"
        "	my $im = SVG->new();\n"
        "	# emit an error message SVG, for tools automating flamegraph use\n"
        "	my $imageheight = $fontsize * 5;\n"
        "	$im->header($imagewidth, $imageheight);\n"
        "	$im->stringTTF($im->colorAllocate(0, 0, 0), $fonttype, $fontsize + 2,\n"
        "	    0.0, int($imagewidth / 2), $fontsize * 2,\n"
        "	    \"ERROR: No valid input provided to flamegraph.pl.\", \"middle\");\n"
        "	print $im->svg;\n"
        "	exit 2;\n"
        "}\n"
        "if ($timemax and $timemax < $time) {\n"
        "	warn \"Specified --total $timemax is less than actual total $time, so "
        "ignored\\n\"\n"
        "	if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc)\n"
        "	undef $timemax;\n"
        "}\n"
        "$timemax ||= $time;\n"
        "\n"
        "my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;\n"
        "my $minwidth_time = $minwidth / $widthpertime;\n"
        "\n"
        "# prune blocks that are too narrow and determine max depth\n"
        "while (my ($id, $node) = each %Node) {\n"
        "	my ($func, $depth, $etime) = split \";\", $id;\n"
        "	my $stime = $node->{stime};\n"
        "	die \"missing start for $id\" if not defined $stime;\n"
        "\n"
        "	if (($etime-$stime) < $minwidth_time) {\n"
        "		delete $Node{$id};\n"
        "		next;\n"
        "	}\n"
        "	$depthmax = $depth if $depth > $depthmax;\n"
        "}\n"
        "\n"
        "# draw canvas, and embed interactive JavaScript program\n"
        "my $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2;\n"
        "$imageheight += $ypad3 if $subtitletext ne \"\";\n"
        "my $im = SVG->new();\n"
        "$im->header($imagewidth, $imageheight);\n"
        "my $inc = <<INC;\n"
        "<defs >\n"
        "	<linearGradient id=\"background\" y1=\"0\" y2=\"1\" x1=\"0\" x2=\"0\" >\n"
        "		<stop stop-color=\"$bgcolor1\" offset=\"5%\" />\n"
        "		<stop stop-color=\"$bgcolor2\" offset=\"95%\" />\n"
        "	</linearGradient>\n"
        "</defs>\n"
        "<style type=\"text/css\">\n"
        "	.func_g:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n"
        "</style>\n"
        "<script type=\"text/ecmascript\">\n"
        "<![CDATA[\n"
        "	var details, searchbtn, matchedtxt, svg;\n"
        "	function init(evt) {\n"
        "		details = document.getElementById(\"details\").firstChild;\n"
        "		searchbtn = document.getElementById(\"search\");\n"
        "		matchedtxt = document.getElementById(\"matched\");\n"
        "		svg = document.getElementsByTagName(\"svg\")[0];\n"
        "		searching = 0;\n"
        "	}\n"
        "\n"
        "	// mouse-over for info\n"
        "	function s(node) {		// show\n"
        "		info = g_to_text(node);\n"
        "		details.nodeValue = \"$nametype \" + info;\n"
        "	}\n"
        "	function c() {			// clear\n"
        "		details.nodeValue = ' ';\n"
        "	}\n"
        "\n"
        "	// ctrl-F for search\n"
        "	window.addEventListener(\"keydown\",function (e) {\n"
        "		if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n"
        "			e.preventDefault();\n"
        "			search_prompt();\n"
        "		}\n"
        "	})\n"
        "\n"
        "	// functions\n"
        "	function find_child(parent, name, attr) {\n"
        "		var children = parent.childNodes;\n"
        "		for (var i=0; i<children.length;i++) {\n"
        "			if (children[i].tagName == name)\n"
        "				return (attr != undefined) ? "
        "children[i].attributes[attr].value : children[i];\n"
        "		}\n"
        "		return;\n"
        "	}\n"
        "	function orig_save(e, attr, val) {\n"
        "		if (e.attributes[\"_orig_\"+attr] != undefined) return;\n"
        "		if (e.attributes[attr] == undefined) return;\n"
        "		if (val == undefined) val = e.attributes[attr].value;\n"
        "		e.setAttribute(\"_orig_\"+attr, val);\n"
        "	}\n"
        "	function orig_load(e, attr) {\n"
        "		if (e.attributes[\"_orig_\"+attr] == undefined) return;\n"
        "		e.attributes[attr].value = e.attributes[\"_orig_\"+attr].value;\n"
        "		e.removeAttribute(\"_orig_\"+attr);\n"
        "	}\n"
        "	function g_to_text(e) {\n"
        "		var text = find_child(e, \"title\").firstChild.nodeValue;\n"
        "		return (text)\n"
        "	}\n"
        "	function g_to_func(e) {\n"
        "		var func = g_to_text(e);\n"
        "		// if there's any manipulation we want to do to the function\n"
        "		// name before it's searched, do it here before returning.\n"
        "		return (func);\n"
        "	}\n"
        "	function update_text(e) {\n"
        "		var r = find_child(e, \"rect\");\n"
        "		var t = find_child(e, \"text\");\n"
        "		var w = parseFloat(r.attributes[\"width\"].value) -3;\n"
        "		var txt = find_child(e, "
        "\"title\").textContent.replace(/\\\\([^(]*\\\\)\\$/,\"\");\n"
        "		t.attributes[\"x\"].value = parseFloat(r.attributes[\"x\"].value) +3;\n"
        "\n"
        "		// Smaller than this size won't fit anything\n"
        "		if (w < 2*$fontsize*$fontwidth) {\n"
        "			t.textContent = \"\";\n"
        "			return;\n"
        "		}\n"
        "\n"
        "		t.textContent = txt;\n"
        "		// Fit in full text width\n"
        "		if (/^ *\\$/.test(txt) || t.getSubStringLength(0, txt.length) < w)\n"
        "			return;\n"
        "\n"
        "		for (var x=txt.length-2; x>0; x--) {\n"
        "			if (t.getSubStringLength(0, x+2) <= w) {\n"
        "				t.textContent = txt.substring(0,x) + \"..\";\n"
        "				return;\n"
        "			}\n"
        "		}\n"
        "		t.textContent = \"\";\n"
        "	}\n"
        "\n"
        "	// zoom\n"
        "	function zoom_reset(e) {\n"
        "		if (e.attributes != undefined) {\n"
        "			orig_load(e, \"x\");\n"
        "			orig_load(e, \"width\");\n"
        "		}\n"
        "		if (e.childNodes == undefined) return;\n"
        "		for(var i=0, c=e.childNodes; i<c.length; i++) {\n"
        "			zoom_reset(c[i]);\n"
        "		}\n"
        "	}\n"
        "	function zoom_child(e, x, ratio) {\n"
        "		if (e.attributes != undefined) {\n"
        "			if (e.attributes[\"x\"] != undefined) {\n"
        "				orig_save(e, \"x\");\n"
        "				e.attributes[\"x\"].value = "
        "(parseFloat(e.attributes[\"x\"].value) - x - $xpad) * ratio + $xpad;\n"
        "				if(e.tagName == \"text\") e.attributes[\"x\"].value = "
        "find_child(e.parentNode, \"rect\", \"x\") + 3;\n"
        "			}\n"
        "			if (e.attributes[\"width\"] != undefined) {\n"
        "				orig_save(e, \"width\");\n"
        "				e.attributes[\"width\"].value = "
        "parseFloat(e.attributes[\"width\"].value) * ratio;\n"
        "			}\n"
        "		}\n"
        "\n"
        "		if (e.childNodes == undefined) return;\n"
        "		for(var i=0, c=e.childNodes; i<c.length; i++) {\n"
        "			zoom_child(c[i], x-$xpad, ratio);\n"
        "		}\n"
        "	}\n"
        "	function zoom_parent(e) {\n"
        "		if (e.attributes) {\n"
        "			if (e.attributes[\"x\"] != undefined) {\n"
        "				orig_save(e, \"x\");\n"
        "				e.attributes[\"x\"].value = $xpad;\n"
        "			}\n"
        "			if (e.attributes[\"width\"] != undefined) {\n"
        "				orig_save(e, \"width\");\n"
        "				e.attributes[\"width\"].value = "
        "parseInt(svg.width.baseVal.value) - ($xpad*2);\n"
        "			}\n"
        "		}\n"
        "		if (e.childNodes == undefined) return;\n"
        "		for(var i=0, c=e.childNodes; i<c.length; i++) {\n"
        "			zoom_parent(c[i]);\n"
        "		}\n"
        "	}\n"
        "	function zoom(node) {\n"
        "		var attr = find_child(node, \"rect\").attributes;\n"
        "		var width = parseFloat(attr[\"width\"].value);\n"
        "		var xmin = parseFloat(attr[\"x\"].value);\n"
        "		var xmax = parseFloat(xmin + width);\n"
        "		var ymin = parseFloat(attr[\"y\"].value);\n"
        "		var ratio = (svg.width.baseVal.value - 2*$xpad) / width;\n"
        "\n"
        "		// XXX: Workaround for JavaScript float issues (fix me)\n"
        "		var fudge = 0.0001;\n"
        "\n"
        "		var unzoombtn = document.getElementById(\"unzoom\");\n"
        "		unzoombtn.style[\"opacity\"] = \"1.0\";\n"
        "\n"
        "		var el = document.getElementsByTagName(\"g\");\n"
        "		for(var i=0;i<el.length;i++){\n"
        "			var e = el[i];\n"
        "			var a = find_child(e, \"rect\").attributes;\n"
        "			var ex = parseFloat(a[\"x\"].value);\n"
        "			var ew = parseFloat(a[\"width\"].value);\n"
        "			// Is it an ancestor\n"
        "			if ($inverted == 0) {\n"
        "				var upstack = parseFloat(a[\"y\"].value) > ymin;\n"
        "			} else {\n"
        "				var upstack = parseFloat(a[\"y\"].value) < ymin;\n"
        "			}\n"
        "			if (upstack) {\n"
        "				// Direct ancestor\n"
        "				if (ex <= xmin && (ex+ew+fudge) >= xmax) {\n"
        "					e.style[\"opacity\"] = \"0.5\";\n"
        "					zoom_parent(e);\n"
        "					e.onclick = function(e){unzoom(); zoom(this);};\n"
        "					update_text(e);\n"
        "				}\n"
        "				// not in current path\n"
        "				else\n"
        "					e.style[\"display\"] = \"none\";\n"
        "			}\n"
        "			// Children maybe\n"
        "			else {\n"
        "				// no common path\n"
        "				if (ex < xmin || ex + fudge >= xmax) {\n"
        "					e.style[\"display\"] = \"none\";\n"
        "				}\n"
        "				else {\n"
        "					zoom_child(e, xmin, ratio);\n"
        "					e.onclick = function(e){zoom(this);};\n"
        "					update_text(e);\n"
        "				}\n"
        "			}\n"
        "		}\n"
        "	}\n"
        "	function unzoom() {\n"
        "		var unzoombtn = document.getElementById(\"unzoom\");\n"
        "		unzoombtn.style[\"opacity\"] = \"0.0\";\n"
        "\n"
        "		var el = document.getElementsByTagName(\"g\");\n"
        "		for(i=0;i<el.length;i++) {\n"
        "			el[i].style[\"display\"] = \"block\";\n"
        "			el[i].style[\"opacity\"] = \"1\";\n"
        "			zoom_reset(el[i]);\n"
        "			update_text(el[i]);\n"
        "		}\n"
        "	}\n"
        "\n"
        "	// search\n"
        "	function reset_search() {\n"
        "		var el = document.getElementsByTagName(\"rect\");\n"
        "		for (var i=0; i < el.length; i++) {\n"
        "			orig_load(el[i], \"fill\")\n"
        "		}\n"
        "	}\n"
        "	function search_prompt() {\n"
        "		if (!searching) {\n"
        "			var term = prompt(\"Enter a search term (regexp \" +\n"
        "			    \"allowed, eg: ^ext4_)\", \"\");\n"
        "			if (term != null) {\n"
        "				search(term)\n"
        "			}\n"
        "		} else {\n"
        "			reset_search();\n"
        "			searching = 0;\n"
        "			searchbtn.style[\"opacity\"] = \"0.1\";\n"
        "			searchbtn.firstChild.nodeValue = \"Search\"\n"
        "			matchedtxt.style[\"opacity\"] = \"0.0\";\n"
        "			matchedtxt.firstChild.nodeValue = \"\"\n"
        "		}\n"
        "	}\n"
        "	function search(term) {\n"
        "		var re = new RegExp(term);\n"
        "		var el = document.getElementsByTagName(\"g\");\n"
        "		var matches = new Object();\n"
        "		var maxwidth = 0;\n"
        "		for (var i = 0; i < el.length; i++) {\n"
        "			var e = el[i];\n"
        "			if (e.attributes[\"class\"].value != \"func_g\")\n"
        "				continue;\n"
        "			var func = g_to_func(e);\n"
        "			var rect = find_child(e, \"rect\");\n"
        "			if (rect == null) {\n"
        "				// the rect might be wrapped in an anchor\n"
        "				// if nameattr href is being used\n"
        "				if (rect = find_child(e, \"a\")) {\n"
        "				    rect = find_child(r, \"rect\");\n"
        "				}\n"
        "			}\n"
        "			if (func == null || rect == null)\n"
        "				continue;\n"
        "\n"
        "			// Save max width. Only works as we have a root frame\n"
        "			var w = parseFloat(rect.attributes[\"width\"].value);\n"
        "			if (w > maxwidth)\n"
        "				maxwidth = w;\n"
        "\n"
        "			if (func.match(re)) {\n"
        "				// highlight\n"
        "				var x = parseFloat(rect.attributes[\"x\"].value);\n"
        "				orig_save(rect, \"fill\");\n"
        "				rect.attributes[\"fill\"].value =\n"
        "				    \"$searchcolor\";\n"
        "\n"
        "				// remember matches\n"
        "				if (matches[x] == undefined) {\n"
        "					matches[x] = w;\n"
        "				} else {\n"
        "					if (w > matches[x]) {\n"
        "						// overwrite with parent\n"
        "						matches[x] = w;\n"
        "					}\n"
        "				}\n"
        "				searching = 1;\n"
        "			}\n"
        "		}\n"
        "		if (!searching)\n"
        "			return;\n"
        "\n"
        "		searchbtn.style[\"opacity\"] = \"1.0\";\n"
        "		searchbtn.firstChild.nodeValue = \"Reset Search\"\n"
        "\n"
        "		// calculate percent matched, excluding vertical overlap\n"
        "		var count = 0;\n"
        "		var lastx = -1;\n"
        "		var lastw = 0;\n"
        "		var keys = Array();\n"
        "		for (k in matches) {\n"
        "			if (matches.hasOwnProperty(k))\n"
        "				keys.push(k);\n"
        "		}\n"
        "		// sort the matched frames by their x location\n"
        "		// ascending, then width descending\n"
        "		keys.sort(function(a, b){\n"
        "			return a - b;\n"
        "		});\n"
        "		// Step through frames saving only the biggest bottom-up frames\n"
        "		// thanks to the sort order. This relies on the tree property\n"
        "		// where children are always smaller than their parents.\n"
        "		var fudge = 0.0001;	// JavaScript floating point\n"
        "		for (var k in keys) {\n"
        "			var x = parseFloat(keys[k]);\n"
        "			var w = matches[keys[k]];\n"
        "			if (x >= lastx + lastw - fudge) {\n"
        "				count += w;\n"
        "				lastx = x;\n"
        "				lastw = w;\n"
        "			}\n"
        "		}\n"
        "		// display matched percent\n"
        "		matchedtxt.style[\"opacity\"] = \"1.0\";\n"
        "		pct = 100 * count / maxwidth;\n"
        "		if (pct == 100)\n"
        "			pct = \"100\"\n"
        "		else\n"
        "			pct = pct.toFixed(1)\n"
        "		matchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%\";\n"
        "	}\n"
        "	function searchover(e) {\n"
        "		searchbtn.style[\"opacity\"] = \"1.0\";\n"
        "	}\n"
        "	function searchout(e) {\n"
        "		if (searching) {\n"
        "			searchbtn.style[\"opacity\"] = \"1.0\";\n"
        "		} else {\n"
        "			searchbtn.style[\"opacity\"] = \"0.1\";\n"
        "		}\n"
        "	}\n"
        "]]>\n"
        "</script>\n"
        "INC\n"
        "$im->include($inc);\n"
        "$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');\n"
        "my ($white, $black, $vvdgrey, $vdgrey, $dgrey) = (\n"
        "	$im->colorAllocate(255, 255, 255),\n"
        "	$im->colorAllocate(0, 0, 0),\n"
        "	$im->colorAllocate(40, 40, 40),\n"
        "	$im->colorAllocate(160, 160, 160),\n"
        "	$im->colorAllocate(200, 200, 200),\n"
        "    );\n"
        "$im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize "
        "* 2, $titletext, \"middle\");\n"
        "if ($subtitletext ne \"\") {\n"
        "	$im->stringTTF($vdgrey, $fonttype, $fontsize, 0.0, int($imagewidth / 2), $fontsize "
        "* 4, $subtitletext, \"middle\");\n"
        "}\n"
        "$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), "
        "\" \", \"\", 'id=\"details\"');\n"
        "$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $fontsize * 2,\n"
        "    \"Reset Zoom\", \"\", 'id=\"unzoom\" onclick=\"unzoom()\" "
        "style=\"opacity:0.0;cursor:pointer\"');\n"
        "$im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100,\n"
        "    $fontsize * 2, \"Search\", \"\", 'id=\"search\" onmouseover=\"searchover()\" "
        "onmouseout=\"searchout()\" onclick=\"search_prompt()\" "
        "style=\"opacity:0.1;cursor:pointer\"');\n"
        "$im->stringTTF($black, $fonttype, $fontsize, 0.0, $imagewidth - $xpad - 100, "
        "$imageheight - ($ypad2 / 2), \" \", \"\", 'id=\"matched\"');\n"
        "\n"
        "if ($palette) {\n"
        "	read_palette();\n"
        "}\n"
        "\n"
        "# draw frames\n"
        "while (my ($id, $node) = each %Node) {\n"
        "	my ($func, $depth, $etime) = split \";\", $id;\n"
        "	my $stime = $node->{stime};\n"
        "	my $delta = $node->{delta};\n"
        "\n"
        "	$etime = $timemax if $func eq \"\" and $depth == 0;\n"
        "\n"
        "	my $x1 = $xpad + $stime * $widthpertime;\n"
        "	my $x2 = $xpad + $etime * $widthpertime;\n"
        "	my ($y1, $y2);\n"
        "	unless ($inverted) {\n"
        "		$y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad;\n"
        "		$y2 = $imageheight - $ypad2 - $depth * $frameheight;\n"
        "	} else {\n"
        "		$y1 = $ypad1 + $depth * $frameheight;\n"
        "		$y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad;\n"
        "	}\n"
        "\n"
        "	my $samples = sprintf \"%.0f\", ($etime - $stime) * $factor;\n"
        "	(my $samples_txt = $samples) # add commas per perlfaq5\n"
        "		=~ s/(^[-+]?\\d+?(?=(?>(?:\\d{3})+)(?!\\d))|\\G\\d{3}(?=\\d))/$1,/g;\n"
        "\n"
        "	my $info;\n"
        "	if ($func eq \"\" and $depth == 0) {\n"
        "		$info = \"all ($samples_txt $countname, 100%)\";\n"
        "	} else {\n"
        "		my $pct = sprintf \"%.2f\", ((100 * $samples) / ($timemax * $factor));\n"
        "		my $escaped_func = $func;\n"
        "		# clean up SVG breaking characters:\n"
        "		$escaped_func =~ s/&/&amp;/g;\n"
        "		$escaped_func =~ s/</&lt;/g;\n"
        "		$escaped_func =~ s/>/&gt;/g;\n"
        "		$escaped_func =~ s/\"/&quot;/g;\n"
        "		$escaped_func =~ s/_\\[[kwij]\\]$//;	# strip any annotation\n"
        "		unless (defined $delta) {\n"
        "			$info = \"$escaped_func ($samples_txt $countname, $pct%)\";\n"
        "		} else {\n"
        "			my $d = $negate ? -$delta : $delta;\n"
        "			my $deltapct = sprintf \"%.2f\", ((100 * $d) / ($timemax * "
        "$factor));\n"
        "			$deltapct = $d > 0 ? \"+$deltapct\" : $deltapct;\n"
        "			$info = \"$escaped_func ($samples_txt $countname, $pct%; "
        "$deltapct%)\";\n"
        "		}\n"
        "	}\n"
        "\n"
        "	my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone\n"
        "	$nameattr->{class}       ||= \"func_g\";\n"
        "	$nameattr->{onmouseover} ||= \"s(this)\";\n"
        "	$nameattr->{onmouseout}  ||= \"c()\";\n"
        "	$nameattr->{onclick}     ||= \"zoom(this)\";\n"
        "	$nameattr->{title}       ||= $info;\n"
        "	$im->group_start($nameattr);\n"
        "\n"
        "	my $color;\n"
        "	if ($func eq \"--\") {\n"
        "		$color = $vdgrey;\n"
        "	} elsif ($func eq \"-\") {\n"
        "		$color = $dgrey;\n"
        "	} elsif (defined $delta) {\n"
        "		$color = color_scale($delta, $maxdelta);\n"
        "	} elsif ($palette) {\n"
        "		$color = color_map($colors, $func);\n"
        "	} else {\n"
        "		$color = color($colors, $hash, $func);\n"
        "	}\n"
        "	$im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx=\"2\" ry=\"2\"');\n"
        "\n"
        "	my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));\n"
        "	my $text = \"\";\n"
        "	if ($chars >= 3) { #Â room for one char plus two dots\n"
        "		$func =~ s/_\\[[kwij]\\]$//;	# strip any annotation\n"
        "		$text = substr $func, 0, $chars;\n"
        "		substr($text, -2, 2) = \"..\" if $chars < length $func;\n"
        "		$text =~ s/&/&amp;/g;\n"
        "		$text =~ s/</&lt;/g;\n"
        "		$text =~ s/>/&gt;/g;\n"
        "	}\n"
        "	$im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, "
        "$text, \"\");\n"
        "\n"
        "	$im->group_end($nameattr);\n"
        "}\n"
        "\n"
        "print $im->svg;\n"
        "\n"
        "if ($palette) {\n"
        "	write_palette();\n"
        "}\n";
}

} // namespace brpc
