Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
B
brpc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
brpc
Commits
613bcc7b
Commit
613bcc7b
authored
Jul 30, 2019
by
skilxnTL
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add flamegraph view for profiling builtin service
parent
907ac3d8
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1349 additions
and
56 deletions
+1349
-56
flamegraph_perl.cpp
src/brpc/builtin/flamegraph_perl.cpp
+1192
-0
flamegraph_perl.h
src/brpc/builtin/flamegraph_perl.h
+25
-0
hotspots_service.cpp
src/brpc/builtin/hotspots_service.cpp
+132
-56
pprof_perl.cpp
src/brpc/builtin/pprof_perl.cpp
+0
-0
No files found.
src/brpc/builtin/flamegraph_perl.cpp
0 → 100644
View file @
613bcc7b
// 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/&/&/g;
\n
"
" $escaped_func =~ s/</</g;
\n
"
" $escaped_func =~ s/>/>/g;
\n
"
" $escaped_func =~ s/
\"
/"/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/&/&/g;
\n
"
" $text =~ s/</</g;
\n
"
" $text =~ s/>/>/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
src/brpc/builtin/flamegraph_perl.h
0 → 100644
View file @
613bcc7b
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Authors: Tian,Ye (tianye15@baidu.com)
#pragma once
namespace
brpc
{
const
char
*
flamegraph_perl
();
}
// namespace brpc
src/brpc/builtin/hotspots_service.cpp
View file @
613bcc7b
...
...
@@ -16,8 +16,10 @@
// under the License.
// Authors: Ge,Jun (gejun@baidu.com)
// Tian,Ye(tianye15@baidu.com)
#include <stdio.h>
#include <thread>
#include <gflags/gflags.h>
#include "butil/files/file_enumerator.h"
#include "butil/file_util.h" // butil::FilePath
...
...
@@ -28,6 +30,7 @@
#include "brpc/server.h"
#include "brpc/reloadable_flags.h"
#include "brpc/builtin/pprof_perl.h"
#include "brpc/builtin/flamegraph_perl.h"
#include "brpc/builtin/hotspots_service.h"
#include "brpc/details/tcmalloc_extension.h"
...
...
@@ -43,6 +46,67 @@ void ContentionProfilerStop();
namespace
brpc
{
enum
class
DisplayType
{
kUnknown
,
#if defined(OS_LINUX)
kFlameGraph
,
#endif
kDot
,
kText
};
static
const
char
*
DisplayTypeToString
(
DisplayType
type
)
{
switch
(
type
)
{
#if defined(OS_LINUX)
case
DisplayType
:
:
kFlameGraph
:
return
"flame"
;
#endif
case
DisplayType
:
:
kDot
:
return
"dot"
;
case
DisplayType
:
:
kText
:
return
"text"
;
default:
return
"unknown"
;
}
}
static
DisplayType
StringToDisplayType
(
const
std
::
string
&
val
)
{
static
butil
::
CaseIgnoredFlatMap
<
DisplayType
>*
display_type_map
;
static
std
::
once_flag
flag
;
std
::
call_once
(
flag
,
[]()
{
display_type_map
=
new
butil
::
CaseIgnoredFlatMap
<
DisplayType
>
;
display_type_map
->
init
(
10
);
#if defined(OS_LINUX)
(
*
display_type_map
)[
"flame"
]
=
DisplayType
::
kFlameGraph
;
#endif
(
*
display_type_map
)[
"dot"
]
=
DisplayType
::
kDot
;
(
*
display_type_map
)[
"text"
]
=
DisplayType
::
kText
;
});
auto
type
=
display_type_map
->
seek
(
val
);
if
(
type
==
nullptr
)
{
return
DisplayType
::
kUnknown
;
}
return
*
type
;
}
static
std
::
string
DisplayTypeToPProfArgument
(
DisplayType
type
)
{
switch
(
type
)
{
#if defined(OS_LINUX)
case
DisplayType
:
:
kFlameGraph
:
return
" --collapsed "
;
case
DisplayType
:
:
kDot
:
return
" --dot "
;
case
DisplayType
:
:
kText
:
return
" --text "
;
#elif defined(OS_MACOSX)
case
DisplayType
:
:
kDot
:
return
" -dot "
;
case
DisplayType
:
:
kText
:
return
" -text "
;
#endif
default
:
return
" unknown type "
;
}
}
static
std
::
string
GeneratePerlScriptPath
(
const
std
::
string
&
filename
)
{
std
::
string
path
;
path
.
reserve
(
FLAGS_rpc_profiling_dir
.
size
()
+
1
+
filename
.
size
());
path
+=
FLAGS_rpc_profiling_dir
;
path
.
push_back
(
'/'
);
path
+=
filename
;
return
std
::
move
(
path
);
}
extern
bool
cpu_profiler_enabled
;
...
...
@@ -54,6 +118,7 @@ DEFINE_int32(max_profiles_kept, 32,
BRPC_VALIDATE_GFLAG
(
max_profiles_kept
,
PassValidate
);
static
const
char
*
const
PPROF_FILENAME
=
"pprof.pl"
;
static
const
char
*
const
FLAMEGRAPH_FILENAME
=
"flamegraph.pl"
;
static
int
DEFAULT_PROFILING_SECONDS
=
10
;
static
size_t
CONCURRENT_PROFILING_LIMIT
=
256
;
...
...
@@ -228,16 +293,16 @@ static bool ValidProfilePath(const butil::StringPiece& path) {
static
int
MakeCacheName
(
char
*
cache_name
,
size_t
len
,
const
char
*
prof_name
,
const
char
*
base_name
,
bool
use_text
,
DisplayType
display_type
,
bool
show_ccount
)
{
if
(
base_name
)
{
return
snprintf
(
cache_name
,
len
,
"%s.cache/base_%s%s%s"
,
prof_name
,
return
snprintf
(
cache_name
,
len
,
"%s.cache/base_%s
.
%s%s"
,
prof_name
,
base_name
,
(
use_text
?
".text"
:
".dot"
),
DisplayTypeToString
(
display_type
),
(
show_ccount
?
".ccount"
:
""
));
}
else
{
return
snprintf
(
cache_name
,
len
,
"%s.cache/%s%s"
,
prof_name
,
(
use_text
?
"text"
:
"dot"
),
DisplayTypeToString
(
display_type
),
(
show_ccount
?
".ccount"
:
""
));
}
...
...
@@ -344,9 +409,16 @@ static void DisplayResult(Controller* cntl,
}
butil
::
IOBuf
&
resp
=
cntl
->
response_attachment
();
const
bool
use_html
=
UseHTML
(
cntl
->
http_request
());
const
bool
use_text
=
cntl
->
http_request
().
uri
().
GetQuery
(
"text"
);
const
bool
show_ccount
=
cntl
->
http_request
().
uri
().
GetQuery
(
"ccount"
);
const
std
::
string
*
base_name
=
cntl
->
http_request
().
uri
().
GetQuery
(
"base"
);
const
std
::
string
*
display_type_query
=
cntl
->
http_request
().
uri
().
GetQuery
(
"display_type"
);
DisplayType
display_type
=
DisplayType
::
kFlameGraph
;
if
(
display_type_query
)
{
display_type
=
StringToDisplayType
(
*
display_type_query
);
if
(
display_type
==
DisplayType
::
kUnknown
)
{
return
cntl
->
SetFailed
(
EINVAL
,
"Invalid display_type=%s"
,
display_type_query
->
c_str
());
}
}
if
(
base_name
!=
NULL
)
{
if
(
!
ValidProfilePath
(
*
base_name
))
{
return
cntl
->
SetFailed
(
EINVAL
,
"Invalid query `base'"
);
...
...
@@ -361,7 +433,7 @@ static void DisplayResult(Controller* cntl,
char
expected_result_name
[
256
];
MakeCacheName
(
expected_result_name
,
sizeof
(
expected_result_name
),
prof_name
,
GetBaseName
(
base_name
),
use_text
,
show_ccount
);
display_type
,
show_ccount
);
// Try to read cache first.
FILE
*
fp
=
fopen
(
expected_result_name
,
"r"
);
if
(
fp
!=
NULL
)
{
...
...
@@ -400,23 +472,29 @@ static void DisplayResult(Controller* cntl,
}
std
::
ostringstream
cmd_builder
;
std
::
string
pprof_tool
;
pprof_tool
.
reserve
(
FLAGS_rpc_profiling_dir
.
size
()
+
1
+
strlen
(
PPROF_FILENAME
));
pprof_tool
+=
FLAGS_rpc_profiling_dir
;
pprof_tool
.
push_back
(
'/'
);
pprof_tool
+=
PPROF_FILENAME
;
std
::
string
pprof_tool
{
GeneratePerlScriptPath
(
PPROF_FILENAME
)};
std
::
string
flamegraph_tool
{
GeneratePerlScriptPath
(
FLAMEGRAPH_FILENAME
)};
#if defined(OS_LINUX)
cmd_builder
<<
"perl "
<<
pprof_tool
<<
(
use_text
?
" --text "
:
" --dot "
)
<<
DisplayTypeToPProfArgument
(
display_type
)
<<
(
show_ccount
?
" --contention "
:
""
);
if
(
base_name
)
{
cmd_builder
<<
"--base "
<<
*
base_name
<<
' '
;
}
cmd_builder
<<
GetProgramName
()
<<
" "
<<
prof_name
<<
" 2>&1 "
;
cmd_builder
<<
GetProgramName
()
<<
" "
<<
prof_name
;
if
(
display_type
==
DisplayType
::
kFlameGraph
)
{
// For flamegraph, we don't care about pprof error msg,
// which will cause confusing messages in the final result.
cmd_builder
<<
" 2>/dev/null "
<<
" | "
<<
"perl "
<<
flamegraph_tool
;
}
cmd_builder
<<
" 2>&1 "
;
#elif defined(OS_MACOSX)
cmd_builder
<<
getenv
(
"GOOGLE_PPROF_BINARY_PATH"
)
<<
" "
<<
(
use_text
?
" -text "
:
" -dot "
)
<<
DisplayTypeToPProfArgument
(
display_type
)
<<
(
show_ccount
?
" -contentions "
:
""
);
if
(
base_name
)
{
cmd_builder
<<
"-base "
<<
*
base_name
<<
' '
;
...
...
@@ -427,7 +505,8 @@ static void DisplayResult(Controller* cntl,
const
std
::
string
cmd
=
cmd_builder
.
str
();
for
(
int
ntry
=
0
;
ntry
<
2
;
++
ntry
)
{
if
(
!
g_written_pprof_perl
)
{
if
(
!
WriteSmallFile
(
pprof_tool
.
c_str
(),
pprof_perl
()))
{
if
(
!
WriteSmallFile
(
pprof_tool
.
c_str
(),
pprof_perl
())
||
!
WriteSmallFile
(
flamegraph_tool
.
c_str
(),
flamegraph_perl
()))
{
os
<<
"Fail to write "
<<
pprof_tool
<<
(
use_html
?
"</body></html>"
:
"
\n
"
);
os
.
move_to
(
resp
);
...
...
@@ -442,12 +521,20 @@ static void DisplayResult(Controller* cntl,
butil
::
IOBufBuilder
pprof_output
;
const
int
rc
=
butil
::
read_command_output
(
pprof_output
,
cmd
.
c_str
());
if
(
rc
!=
0
)
{
butil
::
FilePath
path
(
pprof_tool
);
if
(
!
butil
::
PathExists
(
path
))
{
butil
::
FilePath
p
prof_p
ath
(
pprof_tool
);
if
(
!
butil
::
PathExists
(
p
prof_p
ath
))
{
// Write the script again.
g_written_pprof_perl
=
false
;
// tell user.
os
<<
path
.
value
()
<<
" was removed, recreate ...
\n\n
"
;
os
<<
pprof_path
.
value
()
<<
" was removed, recreate ...
\n\n
"
;
continue
;
}
butil
::
FilePath
flamegraph_path
(
flamegraph_tool
);
if
(
!
butil
::
PathExists
(
flamegraph_path
))
{
// Write the script again.
g_written_pprof_perl
=
false
;
// tell user.
os
<<
flamegraph_path
.
value
()
<<
" was removed, recreate ...
\n\n
"
;
continue
;
}
if
(
rc
<
0
)
{
...
...
@@ -464,7 +551,7 @@ static void DisplayResult(Controller* cntl,
// Cache result in file.
char
result_name
[
256
];
MakeCacheName
(
result_name
,
sizeof
(
result_name
),
prof_name
,
GetBaseName
(
base_name
),
use_text
,
show_ccount
);
GetBaseName
(
base_name
),
display_type
,
show_ccount
);
// Append the profile name as the visual reminder for what
// current profile is.
...
...
@@ -757,7 +844,6 @@ static void StartProfiling(ProfilingType type,
butil
::
IOBufBuilder
os
;
bool
enabled
=
false
;
const
char
*
extra_desc
=
""
;
if
(
type
==
PROFILING_CPU
)
{
enabled
=
cpu_profiler_enabled
;
}
else
if
(
type
==
PROFILING_CONTENTION
)
{
...
...
@@ -796,9 +882,16 @@ static void StartProfiling(ProfilingType type,
const
int
seconds
=
ReadSeconds
(
cntl
);
const
std
::
string
*
view
=
cntl
->
http_request
().
uri
().
GetQuery
(
"view"
);
const
bool
use_text
=
cntl
->
http_request
().
uri
().
GetQuery
(
"text"
);
const
bool
show_ccount
=
cntl
->
http_request
().
uri
().
GetQuery
(
"ccount"
);
const
std
::
string
*
base_name
=
cntl
->
http_request
().
uri
().
GetQuery
(
"base"
);
const
std
::
string
*
display_type_query
=
cntl
->
http_request
().
uri
().
GetQuery
(
"display_type"
);
DisplayType
display_type
=
DisplayType
::
kFlameGraph
;
if
(
display_type_query
)
{
display_type
=
StringToDisplayType
(
*
display_type_query
);
if
(
display_type
==
DisplayType
::
kUnknown
)
{
return
cntl
->
SetFailed
(
EINVAL
,
"Invalid display_type=%s"
,
display_type_query
->
c_str
());
}
}
ProfilingClient
profiling_client
;
size_t
nwaiters
=
0
;
...
...
@@ -824,38 +917,19 @@ static void StartProfiling(ProfilingType type,
"function generateURL() {
\n
"
" var past_prof = document.getElementById('view_prof').value;
\n
"
" var base_prof = document.getElementById('base_prof').value;
\n
"
" var
use_text = document.getElementById('text_cb').checked
;
\n
"
;
" var
display_type = document.getElementById('display_type').value
;
\n
"
;
if
(
type
==
PROFILING_CONTENTION
)
{
os
<<
" var show_ccount = document.getElementById('ccount_cb').checked;
\n
"
;
}
os
<<
" var targetURL = '/hotspots/"
<<
type_str
<<
"';
\n
"
"
var first = tru
e;
\n
"
"
targetURL += '?' + 'display_type=' + display_typ
e;
\n
"
" if (past_prof != '') {
\n
"
" if (first) {
\n
"
" targetURL += '?';
\n
"
" first = false;
\n
"
" } else {
\n
"
" targetURL += '&';
\n
"
" }
\n
"
" targetURL += '&';
\n
"
" targetURL += 'view=' + past_prof;
\n
"
" }
\n
"
" if (base_prof != '') {
\n
"
" if (first) {
\n
"
" targetURL += '?';
\n
"
" first = false;
\n
"
" } else {
\n
"
" targetURL += '&';
\n
"
" }
\n
"
" targetURL += '&';
\n
"
" targetURL += 'base=' + base_prof;
\n
"
" }
\n
"
" if (use_text) {
\n
"
" if (first) {
\n
"
" targetURL += '?';
\n
"
" first = false;
\n
"
" } else {
\n
"
" targetURL += '&';
\n
"
" }
\n
"
" targetURL += 'text';
\n
"
" }
\n
"
;
if
(
type
==
PROFILING_CONTENTION
)
{
os
<<
...
...
@@ -904,6 +978,7 @@ static void StartProfiling(ProfilingType type,
" data = data.substring(selEnd + '[addToProfEnd]'.length);
\n
"
" }
\n
"
" $(
\"
#profiling-result
\"
).html('<pre>' + data + '</pre>');
\n
"
" if (data.indexOf('FlameGraph') != -1) { init(); }"
" } else {
\n
"
" $(
\"
#profiling-result
\"
).html('Plotting ...');
\n
"
" var svg = Viz(data.substring(index),
\"
svg
\"
);
\n
"
...
...
@@ -921,9 +996,7 @@ static void StartProfiling(ProfilingType type,
if
(
profiling_client
.
id
!=
0
)
{
os
<<
"&profiling_id="
<<
profiling_client
.
id
;
}
if
(
use_text
)
{
os
<<
"&text"
;
}
os
<<
"&display_type="
<<
DisplayTypeToString
(
display_type
);
if
(
show_ccount
)
{
os
<<
"&ccount"
;
}
...
...
@@ -1004,18 +1077,21 @@ static void StartProfiling(ProfilingType type,
}
os
<<
'>'
<<
GetBaseName
(
&
past_profs
[
i
]);
}
os
<<
"</select>"
" <label for='text_cb'>"
"<input id='text_cb' type='checkbox'"
<<
(
use_text
?
" checked=''"
:
""
)
<<
" onclick='onChangedCB(this);'>text</label>"
;
os
<<
"</select>"
;
os
<<
"<div><pre style='display:inline'>Display Type: </pre>"
"<select id='display_type' onchange='onSelectProf()'>"
#if defined(OS_LINUX)
"<option value=flame"
<<
(
display_type
==
DisplayType
::
kFlameGraph
?
" selected"
:
""
)
<<
">flame</option>"
#endif
"<option value=dot"
<<
(
display_type
==
DisplayType
::
kDot
?
" selected"
:
""
)
<<
">dot</option>"
"<option value=text"
<<
(
display_type
==
DisplayType
::
kText
?
" selected"
:
""
)
<<
">text</option></select>"
;
if
(
type
==
PROFILING_CONTENTION
)
{
os
<<
" <label for='ccount_cb'>"
"<input id='ccount_cb' type='checkbox'"
<<
(
show_ccount
?
" checked=''"
:
""
)
<<
" onclick='onChangedCB(this);'>count</label>"
;
}
os
<<
"<
br
><pre style='display:inline'>Diff: </pre>"
os
<<
"<
/div><div
><pre style='display:inline'>Diff: </pre>"
"<select id='base_prof' onchange='onSelectProf()'>"
"<option value=''><none></option>"
;
for
(
size_t
i
=
0
;
i
<
past_profs
.
size
();
++
i
)
{
...
...
@@ -1025,7 +1101,7 @@ static void StartProfiling(ProfilingType type,
}
os
<<
'>'
<<
GetBaseName
(
&
past_profs
[
i
]);
}
os
<<
"</select>"
;
os
<<
"</select>
</div>
"
;
if
(
!
enabled
&&
view
==
NULL
)
{
os
<<
"<p><span style='color:red'>Error:</span> "
...
...
@@ -1076,7 +1152,7 @@ static void StartProfiling(ProfilingType type,
}
os
<<
"</div><pre class='logo'><span class='logo_text'>"
<<
logo
()
<<
"</span></pre></body>
\n
"
;
if
(
!
use_tex
t
)
{
if
(
display_type
==
DisplayType
::
kDo
t
)
{
// don't need viz.js in text mode.
os
<<
"<script language=
\"
javascript
\"
type=
\"
text/javascript
\"
"
" src=
\"
/js/viz_min
\"
></script>
\n
"
;
...
...
src/brpc/builtin/pprof_perl.cpp
View file @
613bcc7b
This source diff could not be displayed because it is too large. You can
view the blob
instead.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment