Update pprof.

Import updated pprof from google-perftools 1.7.
This commit is contained in:
Jason Evans 2011-03-18 18:18:42 -07:00
parent 92d3284ff8
commit 9a8fc41bb9

View File

@ -72,7 +72,7 @@ use strict;
use warnings; use warnings;
use Getopt::Long; use Getopt::Long;
my $PPROF_VERSION = "1.5"; my $PPROF_VERSION = "1.7";
# These are the object tools we use which can come from a # These are the object tools we use which can come from a
# user-specified location using --tools, from the PPROF_TOOLS # user-specified location using --tools, from the PPROF_TOOLS
@ -89,6 +89,7 @@ my %obj_tool_map = (
); );
my $DOT = "dot"; # leave non-absolute, since it may be in /usr/local my $DOT = "dot"; # leave non-absolute, since it may be in /usr/local
my $GV = "gv"; my $GV = "gv";
my $EVINCE = "evince"; # could also be xpdf or perhaps acroread
my $KCACHEGRIND = "kcachegrind"; my $KCACHEGRIND = "kcachegrind";
my $PS2PDF = "ps2pdf"; my $PS2PDF = "ps2pdf";
# These are used for dynamic profiles # These are used for dynamic profiles
@ -103,6 +104,7 @@ my $GROWTH_PAGE = "/pprof/growth";
my $CONTENTION_PAGE = "/pprof/contention"; my $CONTENTION_PAGE = "/pprof/contention";
my $WALL_PAGE = "/pprof/wall(?:\\?.*)?"; # accepts options like namefilter my $WALL_PAGE = "/pprof/wall(?:\\?.*)?"; # accepts options like namefilter
my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?"; my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
my $CENSUSPROFILE_PAGE = "/pprof/censusprofile"; # must support "?seconds=#"
my $SYMBOL_PAGE = "/pprof/symbol"; # must support symbol lookup via POST my $SYMBOL_PAGE = "/pprof/symbol"; # must support symbol lookup via POST
my $PROGRAM_NAME_PAGE = "/pprof/cmdline"; my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
@ -110,7 +112,7 @@ my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
# All the alternatives must begin with /. # All the alternatives must begin with /.
my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" . my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
"$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" . "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
"$FILTEREDPROFILE_PAGE)"; "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
# default binary name # default binary name
my $UNKNOWN_BINARY = "(unknown)"; my $UNKNOWN_BINARY = "(unknown)";
@ -148,7 +150,7 @@ pprof [options] <profile>
The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile, The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
$GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall, $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
or /pprof/filteredprofile. $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.
For instance: "pprof http://myserver.com:80$HEAP_PAGE". For instance: "pprof http://myserver.com:80$HEAP_PAGE".
If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling). If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
pprof --symbols <program> pprof --symbols <program>
@ -180,6 +182,7 @@ Output type:
--text Generate text report --text Generate text report
--callgrind Generate callgrind format to stdout --callgrind Generate callgrind format to stdout
--gv Generate Postscript and display --gv Generate Postscript and display
--evince Generate PDF and display
--web Generate SVG and display --web Generate SVG and display
--list=<regexp> Generate source listing of matching routines --list=<regexp> Generate source listing of matching routines
--disasm=<regexp> Generate disassembly of matching routines --disasm=<regexp> Generate disassembly of matching routines
@ -208,6 +211,7 @@ Call-graph Options:
--nodecount=<n> Show at most so many nodes [default=80] --nodecount=<n> Show at most so many nodes [default=80]
--nodefraction=<f> Hide nodes below <f>*total [default=.005] --nodefraction=<f> Hide nodes below <f>*total [default=.005]
--edgefraction=<f> Hide edges below <f>*total [default=.001] --edgefraction=<f> Hide edges below <f>*total [default=.001]
--maxdegree=<n> Max incoming/outgoing edges per node [default=8]
--focus=<regexp> Focus on nodes matching <regexp> --focus=<regexp> Focus on nodes matching <regexp>
--ignore=<regexp> Ignore nodes matching <regexp> --ignore=<regexp> Ignore nodes matching <regexp>
--scale=<n> Set GV scaling [default=0] --scale=<n> Set GV scaling [default=0]
@ -304,6 +308,7 @@ sub Init() {
$main::opt_disasm = ""; $main::opt_disasm = "";
$main::opt_symbols = 0; $main::opt_symbols = 0;
$main::opt_gv = 0; $main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_web = 0; $main::opt_web = 0;
$main::opt_dot = 0; $main::opt_dot = 0;
$main::opt_ps = 0; $main::opt_ps = 0;
@ -315,6 +320,7 @@ sub Init() {
$main::opt_nodecount = 80; $main::opt_nodecount = 80;
$main::opt_nodefraction = 0.005; $main::opt_nodefraction = 0.005;
$main::opt_edgefraction = 0.001; $main::opt_edgefraction = 0.001;
$main::opt_maxdegree = 8;
$main::opt_focus = ''; $main::opt_focus = '';
$main::opt_ignore = ''; $main::opt_ignore = '';
$main::opt_scale = 0; $main::opt_scale = 0;
@ -372,6 +378,7 @@ sub Init() {
"disasm=s" => \$main::opt_disasm, "disasm=s" => \$main::opt_disasm,
"symbols!" => \$main::opt_symbols, "symbols!" => \$main::opt_symbols,
"gv!" => \$main::opt_gv, "gv!" => \$main::opt_gv,
"evince!" => \$main::opt_evince,
"web!" => \$main::opt_web, "web!" => \$main::opt_web,
"dot!" => \$main::opt_dot, "dot!" => \$main::opt_dot,
"ps!" => \$main::opt_ps, "ps!" => \$main::opt_ps,
@ -383,6 +390,7 @@ sub Init() {
"nodecount=i" => \$main::opt_nodecount, "nodecount=i" => \$main::opt_nodecount,
"nodefraction=f" => \$main::opt_nodefraction, "nodefraction=f" => \$main::opt_nodefraction,
"edgefraction=f" => \$main::opt_edgefraction, "edgefraction=f" => \$main::opt_edgefraction,
"maxdegree=i" => \$main::opt_maxdegree,
"focus=s" => \$main::opt_focus, "focus=s" => \$main::opt_focus,
"ignore=s" => \$main::opt_ignore, "ignore=s" => \$main::opt_ignore,
"scale=i" => \$main::opt_scale, "scale=i" => \$main::opt_scale,
@ -452,6 +460,7 @@ sub Init() {
($main::opt_disasm eq '' ? 0 : 1) + ($main::opt_disasm eq '' ? 0 : 1) +
($main::opt_symbols == 0 ? 0 : 1) + ($main::opt_symbols == 0 ? 0 : 1) +
$main::opt_gv + $main::opt_gv +
$main::opt_evince +
$main::opt_web + $main::opt_web +
$main::opt_dot + $main::opt_dot +
$main::opt_ps + $main::opt_ps +
@ -646,6 +655,8 @@ sub Main() {
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) { if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
if ($main::opt_gv) { if ($main::opt_gv) {
RunGV(TempName($main::next_tmpfile, "ps"), ""); RunGV(TempName($main::next_tmpfile, "ps"), "");
} elsif ($main::opt_evince) {
RunEvince(TempName($main::next_tmpfile, "pdf"), "");
} elsif ($main::opt_web) { } elsif ($main::opt_web) {
my $tmp = TempName($main::next_tmpfile, "svg"); my $tmp = TempName($main::next_tmpfile, "svg");
RunWeb($tmp); RunWeb($tmp);
@ -708,6 +719,12 @@ sub RunGV {
} }
} }
sub RunEvince {
my $fname = shift;
my $bg = shift; # "" or " &" if we should run in background
system("$EVINCE " . $fname . $bg);
}
sub RunWeb { sub RunWeb {
my $fname = shift; my $fname = shift;
print STDERR "Loading web page file:///$fname\n"; print STDERR "Loading web page file:///$fname\n";
@ -805,6 +822,7 @@ sub InteractiveCommand {
$main::opt_disasm = 0; $main::opt_disasm = 0;
$main::opt_list = 0; $main::opt_list = 0;
$main::opt_gv = 0; $main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_cum = 0; $main::opt_cum = 0;
if (m/^\s*(text|top)(\d*)\s*(.*)/) { if (m/^\s*(text|top)(\d*)\s*(.*)/) {
@ -878,11 +896,14 @@ sub InteractiveCommand {
PrintDisassembly($libs, $flat, $cumulative, $routine, $total); PrintDisassembly($libs, $flat, $cumulative, $routine, $total);
return 1; return 1;
} }
if (m/^\s*(gv|web)\s*(.*)/) { if (m/^\s*(gv|web|evince)\s*(.*)/) {
$main::opt_gv = 0; $main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_web = 0; $main::opt_web = 0;
if ($1 eq "gv") { if ($1 eq "gv") {
$main::opt_gv = 1; $main::opt_gv = 1;
} elsif ($1 eq "evince") {
$main::opt_evince = 1;
} elsif ($1 eq "web") { } elsif ($1 eq "web") {
$main::opt_web = 1; $main::opt_web = 1;
} }
@ -902,6 +923,8 @@ sub InteractiveCommand {
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) { if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
if ($main::opt_gv) { if ($main::opt_gv) {
RunGV(TempName($main::next_tmpfile, "ps"), " &"); RunGV(TempName($main::next_tmpfile, "ps"), " &");
} elsif ($main::opt_evince) {
RunEvince(TempName($main::next_tmpfile, "pdf"), " &");
} elsif ($main::opt_web) { } elsif ($main::opt_web) {
RunWeb(TempName($main::next_tmpfile, "svg")); RunWeb(TempName($main::next_tmpfile, "svg"));
} }
@ -1685,6 +1708,8 @@ sub PrintDot {
my $output; my $output;
if ($main::opt_gv) { if ($main::opt_gv) {
$output = "| $DOT -Tps2 >" . TempName($main::next_tmpfile, "ps"); $output = "| $DOT -Tps2 >" . TempName($main::next_tmpfile, "ps");
} elsif ($main::opt_evince) {
$output = "| $DOT -Tps2 | $PS2PDF - " . TempName($main::next_tmpfile, "pdf");
} elsif ($main::opt_ps) { } elsif ($main::opt_ps) {
$output = "| $DOT -Tps2"; $output = "| $DOT -Tps2";
} elsif ($main::opt_pdf) { } elsif ($main::opt_pdf) {
@ -1792,12 +1817,38 @@ sub PrintDot {
} }
} }
# Print edges # Print edges (process in order of decreasing counts)
foreach my $e (keys(%edge)) { my %indegree = (); # Number of incoming edges added per node so far
my %outdegree = (); # Number of outgoing edges added per node so far
foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
my @x = split(/\001/, $e); my @x = split(/\001/, $e);
$n = $edge{$e}; $n = $edge{$e};
if (abs($n) > $edgelimit) { # Initialize degree of kept incoming and outgoing edges if necessary
my $src = $x[0];
my $dst = $x[1];
if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }
if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }
my $keep;
if ($indegree{$dst} == 0) {
# Keep edge if needed for reachability
$keep = 1;
} elsif (abs($n) <= $edgelimit) {
# Drop if we are below --edgefraction
$keep = 0;
} elsif ($outdegree{$src} >= $main::opt_maxdegree ||
$indegree{$dst} >= $main::opt_maxdegree) {
# Keep limited number of in/out edges per node
$keep = 0;
} else {
$keep = 1;
}
if ($keep) {
$outdegree{$src}++;
$indegree{$dst}++;
# Compute line width based on edge count # Compute line width based on edge count
my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0); my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
if ($fraction > 1) { $fraction = 1; } if ($fraction > 1) { $fraction = 1; }
@ -2135,6 +2186,19 @@ function handleMouseUp(evt) {
EOF EOF
} }
# Return a small number that identifies the argument.
# Multiple calls with the same argument will return the same number.
# Calls with different arguments will return different numbers.
sub ShortIdFor {
my $key = shift;
my $id = $main::uniqueid{$key};
if (!defined($id)) {
$id = keys(%main::uniqueid) + 1;
$main::uniqueid{$key} = $id;
}
return $id;
}
# Translate a stack of addresses into a stack of symbols # Translate a stack of addresses into a stack of symbols
sub TranslateStack { sub TranslateStack {
my $symbols = shift; my $symbols = shift;
@ -2172,6 +2236,15 @@ sub TranslateStack {
if ($j > 2) { if ($j > 2) {
$func = "$func (inline)"; $func = "$func (inline)";
} }
# Do not merge nodes corresponding to Callback::Run since that
# causes confusing cycles in dot display. Instead, we synthesize
# a unique name for this frame per caller.
if ($func =~ m/Callback.*::Run$/) {
my $caller = ($i > 0) ? $addrs[$i-1] : 0;
$func = "Run#" . ShortIdFor($caller);
}
if ($main::opt_addresses) { if ($main::opt_addresses) {
push(@result, "$a $func $fileline"); push(@result, "$a $func $fileline");
} elsif ($main::opt_lines) { } elsif ($main::opt_lines) {
@ -2415,7 +2488,16 @@ sub RemoveUninterestingFrames {
# old code out of the system. # old code out of the system.
$skip_regexp = "TCMalloc|^tcmalloc::"; $skip_regexp = "TCMalloc|^tcmalloc::";
} elsif ($main::profile_type eq 'contention') { } elsif ($main::profile_type eq 'contention') {
foreach my $vname ('Mutex::Unlock', 'Mutex::UnlockSlow') { foreach my $vname ('base::RecordLockProfileData',
'base::SubmitMutexProfileData',
'base::SubmitSpinLockProfileData',
'Mutex::Unlock',
'Mutex::UnlockSlow',
'Mutex::ReaderUnlock',
'MutexLock::~MutexLock',
'SpinLock::Unlock',
'SpinLock::SlowUnlock',
'SpinLockHolder::~SpinLockHolder') {
$skip{$vname} = 1; $skip{$vname} = 1;
} }
} elsif ($main::profile_type eq 'cpu') { } elsif ($main::profile_type eq 'cpu') {
@ -2955,7 +3037,7 @@ sub FetchDynamicProfile {
my $fetcher = AddFetchTimeout($URL_FETCHER, $fetch_timeout); my $fetcher = AddFetchTimeout($URL_FETCHER, $fetch_timeout);
my $cmd = "$fetcher '$url' > '$tmp_profile'"; my $cmd = "$fetcher '$url' > '$tmp_profile'";
if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/){ if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){
print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n"; print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n";
if ($encourage_patience) { if ($encourage_patience) {
print STDERR "Be patient...\n"; print STDERR "Be patient...\n";
@ -3154,24 +3236,47 @@ BEGIN {
} }
} }
# Return the next line from the profile file, assuming it's a text # Reads the top, 'header' section of a profile, and returns the last
# line (which in this case means, doesn't start with a NUL byte). If # line of the header, commonly called a 'header line'. The header
# it's not a text line, return "". At EOF, return undef, like perl does. # section of a profile consists of zero or more 'command' lines that
# Input file should be in binmode. # are instructions to pprof, which pprof executes when reading the
sub ReadProfileLine { # header. All 'command' lines start with a %. After the command
# lines is the 'header line', which is a profile-specific line that
# indicates what type of profile it is, and perhaps other global
# information about the profile. For instance, here's a header line
# for a heap profile:
# heap profile: 53: 38236 [ 5525: 1284029] @ heapprofile
# For historical reasons, the CPU profile does not contain a text-
# readable header line. If the profile looks like a CPU profile,
# this function returns "". If no header line could be found, this
# function returns undef.
#
# The following commands are recognized:
# %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'
#
# The input file should be in binmode.
sub ReadProfileHeader {
local *PROFILE = shift; local *PROFILE = shift;
my $firstchar = ""; my $firstchar = "";
my $line = ""; my $line = "";
read(PROFILE, $firstchar, 1); read(PROFILE, $firstchar, 1);
seek(PROFILE, -1, 1); # unread the firstchar seek(PROFILE, -1, 1); # unread the firstchar
if ($firstchar eq "\0") { if ($firstchar !~ /[[:print:]]/) { # is not a text character
return ""; return "";
} }
$line = <PROFILE>; while (defined($line = <PROFILE>)) {
if (defined($line)) {
$line =~ s/\r//g; # turn windows-looking lines into unix-looking lines $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
if ($line =~ /^%warn\s+(.*)/) { # 'warn' command
# Note this matches both '%warn blah\n' and '%warn\n'.
print STDERR "WARNING: $1\n"; # print the rest of the line
} elsif ($line =~ /^%/) {
print STDERR "Ignoring unknown command from profile header: $line";
} else {
# End of commands, must be the header line.
return $line;
}
} }
return $line; return undef; # got to EOF without seeing a header line
} }
sub IsSymbolizedProfileFile { sub IsSymbolizedProfileFile {
@ -3182,7 +3287,7 @@ sub IsSymbolizedProfileFile {
# Check if the file contains a symbol-section marker. # Check if the file contains a symbol-section marker.
open(TFILE, "<$file_name"); open(TFILE, "<$file_name");
binmode TFILE; binmode TFILE;
my $firstline = ReadProfileLine(*TFILE); my $firstline = ReadProfileHeader(*TFILE);
close(TFILE); close(TFILE);
if (!$firstline) { if (!$firstline) {
return 0; return 0;
@ -3202,14 +3307,7 @@ sub IsSymbolizedProfileFile {
sub ReadProfile { sub ReadProfile {
my $prog = shift; my $prog = shift;
my $fname = shift; my $fname = shift;
my $result; # return value
if (IsSymbolizedProfileFile($fname) && !$main::use_symbolized_profile) {
# we have both a binary and symbolized profiles, abort
usage("Symbolized profile '$fname' cannot be used with a binary arg. " .
"Try again without passing '$prog'.");
}
$main::profile_type = '';
$CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash
my $contention_marker = $&; my $contention_marker = $&;
@ -3226,40 +3324,45 @@ sub ReadProfile {
# whole firstline, since it may be gigabytes(!) of data. # whole firstline, since it may be gigabytes(!) of data.
open(PROFILE, "<$fname") || error("$fname: $!\n"); open(PROFILE, "<$fname") || error("$fname: $!\n");
binmode PROFILE; # New perls do UTF-8 processing binmode PROFILE; # New perls do UTF-8 processing
my $header = ReadProfileLine(*PROFILE); my $header = ReadProfileHeader(*PROFILE);
if (!defined($header)) { # means "at EOF" if (!defined($header)) { # means "at EOF"
error("Profile is empty.\n"); error("Profile is empty.\n");
} }
my $symbols; my $symbols;
if ($header =~ m/^--- *$symbol_marker/o) { if ($header =~ m/^--- *$symbol_marker/o) {
# Verify that the user asked for a symbolized profile
if (!$main::use_symbolized_profile) {
# we have both a binary and symbolized profiles, abort
error("FATAL ERROR: Symbolized profile\n $fname\ncannot be used with " .
"a binary arg. Try again without passing\n $prog\n");
}
# Read the symbol section of the symbolized profile file. # Read the symbol section of the symbolized profile file.
$symbols = ReadSymbols(*PROFILE{IO}); $symbols = ReadSymbols(*PROFILE{IO});
# Read the next line to get the header for the remaining profile. # Read the next line to get the header for the remaining profile.
$header = ReadProfileLine(*PROFILE) || ""; $header = ReadProfileHeader(*PROFILE) || "";
} }
my $result; $main::profile_type = '';
if ($header =~ m/^heap profile:.*$growth_marker/o) { if ($header =~ m/^heap profile:.*$growth_marker/o) {
$main::profile_type = 'growth'; $main::profile_type = 'growth';
$result = ReadHeapProfile($prog, $fname, $header); $result = ReadHeapProfile($prog, *PROFILE, $header);
} elsif ($header =~ m/^heap profile:/) { } elsif ($header =~ m/^heap profile:/) {
$main::profile_type = 'heap'; $main::profile_type = 'heap';
$result = ReadHeapProfile($prog, $fname, $header); $result = ReadHeapProfile($prog, *PROFILE, $header);
} elsif ($header =~ m/^--- *$contention_marker/o) { } elsif ($header =~ m/^--- *$contention_marker/o) {
$main::profile_type = 'contention'; $main::profile_type = 'contention';
$result = ReadSynchProfile($prog, $fname); $result = ReadSynchProfile($prog, *PROFILE);
} elsif ($header =~ m/^--- *Stacks:/) { } elsif ($header =~ m/^--- *Stacks:/) {
print STDERR print STDERR
"Old format contention profile: mistakenly reports " . "Old format contention profile: mistakenly reports " .
"condition variable signals as lock contentions.\n"; "condition variable signals as lock contentions.\n";
$main::profile_type = 'contention'; $main::profile_type = 'contention';
$result = ReadSynchProfile($prog, $fname); $result = ReadSynchProfile($prog, *PROFILE);
} elsif ($header =~ m/^--- *$profile_marker/) { } elsif ($header =~ m/^--- *$profile_marker/) {
# the binary cpu profile data starts immediately after this line # the binary cpu profile data starts immediately after this line
$main::profile_type = 'cpu'; $main::profile_type = 'cpu';
$result = ReadCPUProfile($prog, $fname); $result = ReadCPUProfile($prog, $fname, *PROFILE);
} else { } else {
if (defined($symbols)) { if (defined($symbols)) {
# a symbolized profile contains a format we don't recognize, bail out # a symbolized profile contains a format we don't recognize, bail out
@ -3267,9 +3370,11 @@ sub ReadProfile {
} }
# no ascii header present -- must be a CPU profile # no ascii header present -- must be a CPU profile
$main::profile_type = 'cpu'; $main::profile_type = 'cpu';
$result = ReadCPUProfile($prog, $fname); $result = ReadCPUProfile($prog, $fname, *PROFILE);
} }
close(PROFILE);
# if we got symbols along with the profile, return those as well # if we got symbols along with the profile, return those as well
if (defined($symbols)) { if (defined($symbols)) {
$result->{symbols} = $symbols; $result->{symbols} = $symbols;
@ -3308,7 +3413,8 @@ sub FixCallerAddresses {
# CPU profile reader # CPU profile reader
sub ReadCPUProfile { sub ReadCPUProfile {
my $prog = shift; my $prog = shift;
my $fname = shift; my $fname = shift; # just used for logging
local *PROFILE = shift;
my $version; my $version;
my $period; my $period;
my $i; my $i;
@ -3375,7 +3481,6 @@ sub ReadCPUProfile {
my $map = ''; my $map = '';
seek(PROFILE, $i * 4, 0); seek(PROFILE, $i * 4, 0);
read(PROFILE, $map, (stat PROFILE)[7]); read(PROFILE, $map, (stat PROFILE)[7]);
close(PROFILE);
my $r = {}; my $r = {};
$r->{version} = $version; $r->{version} = $version;
@ -3389,7 +3494,7 @@ sub ReadCPUProfile {
sub ReadHeapProfile { sub ReadHeapProfile {
my $prog = shift; my $prog = shift;
my $fname = shift; local *PROFILE = shift;
my $header = shift; my $header = shift;
my $index = 1; my $index = 1;
@ -3534,14 +3639,14 @@ sub ReadHeapProfile {
if ($n1 != 0) { if ($n1 != 0) {
my $ratio = (($s1*1.0)/$n1)/($sample_adjustment); my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
my $scale_factor = 1/(1 - exp(-$ratio)); my $scale_factor = 1/(1 - exp(-$ratio));
$n1 *= $scale_factor; $n1 *= $scale_factor;
$s1 *= $scale_factor; $s1 *= $scale_factor;
} }
if ($n2 != 0) { if ($n2 != 0) {
my $ratio = (($s2*1.0)/$n2)/($sample_adjustment); my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
my $scale_factor = 1/(1 - exp(-$ratio)); my $scale_factor = 1/(1 - exp(-$ratio));
$n2 *= $scale_factor; $n2 *= $scale_factor;
$s2 *= $scale_factor; $s2 *= $scale_factor;
} }
} else { } else {
# Remote-heap version 1 # Remote-heap version 1
@ -3574,7 +3679,9 @@ sub ReadHeapProfile {
} }
sub ReadSynchProfile { sub ReadSynchProfile {
my ($prog, $fname, $header) = @_; my $prog = shift;
local *PROFILE = shift;
my $header = shift;
my $map = ''; my $map = '';
my $profile = {}; my $profile = {};
@ -3649,7 +3756,6 @@ sub ReadSynchProfile {
$map .= $line; $map .= $line;
} }
} }
close PROFILE;
if (!$seen_clockrate) { if (!$seen_clockrate) {
printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n", printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n",
@ -4098,8 +4204,9 @@ sub ExtractSymbols {
# advance through the libraries as we advance the pc. Sometimes the # advance through the libraries as we advance the pc. Sometimes the
# addresses of libraries may overlap with the addresses of the main # addresses of libraries may overlap with the addresses of the main
# binary, so to make sure the libraries 'win', we iterate over the # binary, so to make sure the libraries 'win', we iterate over the
# libraries in reverse order (binary will have the lowest start addr). # libraries in reverse order (which assumes the binary doesn't start
my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # in the middle of a library, which seems a fair assumption).
my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # pcset is 0-extended strings
foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) { foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {
my $libname = $lib->[0]; my $libname = $lib->[0];
my $start = $lib->[1]; my $start = $lib->[1];
@ -4109,14 +4216,18 @@ sub ExtractSymbols {
# Get list of pcs that belong in this library. # Get list of pcs that belong in this library.
my $contained = []; my $contained = [];
my ($start_pc_index, $finish_pc_index); my ($start_pc_index, $finish_pc_index);
# Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].
for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0; for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;
$finish_pc_index--) { $finish_pc_index--) {
last if $pcs[$finish_pc_index - 1] le $finish; last if $pcs[$finish_pc_index - 1] le $finish;
} }
# Find smallest start_pc_index such that $start <= $pc[$start_pc_index].
for ($start_pc_index = $finish_pc_index; $start_pc_index > 0; for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;
$start_pc_index--) { $start_pc_index--) {
last if $pcs[$start_pc_index - 1] lt $start; last if $pcs[$start_pc_index - 1] lt $start;
} }
# This keeps PC values higher than $pc[$finish_pc_index] in @pcs,
# in case there are overlaps in libraries and the main binary.
@{$contained} = splice(@pcs, $start_pc_index, @{$contained} = splice(@pcs, $start_pc_index,
$finish_pc_index - $start_pc_index); $finish_pc_index - $start_pc_index);
# Map to symbols # Map to symbols