Support threaded heap profiles in pprof
- Add a --thread N option to select profile for thread N (otherwise, all threads will be printed) - The $profile map now has a {threads} element that is a map from thread id to a profile that has the same format as the {profile} element - Refactor ReadHeapProfile into smaller components and use them to implement ReadThreadedHeapProfile
This commit is contained in:
parent
ffe93419d5
commit
d95e704fea
383
bin/pprof
383
bin/pprof
@ -223,6 +223,7 @@ Call-graph Options:
|
||||
--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>
|
||||
--thread=<n> Show profile for thread <n>
|
||||
--ignore=<regexp> Ignore nodes matching <regexp>
|
||||
--scale=<n> Set GV scaling [default=0]
|
||||
--heapcheck Make nodes with non-0 object counts
|
||||
@ -332,6 +333,7 @@ sub Init() {
|
||||
$main::opt_edgefraction = 0.001;
|
||||
$main::opt_maxdegree = 8;
|
||||
$main::opt_focus = '';
|
||||
$main::opt_thread = undef;
|
||||
$main::opt_ignore = '';
|
||||
$main::opt_scale = 0;
|
||||
$main::opt_heapcheck = 0;
|
||||
@ -402,6 +404,7 @@ sub Init() {
|
||||
"edgefraction=f" => \$main::opt_edgefraction,
|
||||
"maxdegree=i" => \$main::opt_maxdegree,
|
||||
"focus=s" => \$main::opt_focus,
|
||||
"thread=i" => \$main::opt_thread,
|
||||
"ignore=s" => \$main::opt_ignore,
|
||||
"scale=i" => \$main::opt_scale,
|
||||
"heapcheck" => \$main::opt_heapcheck,
|
||||
@ -562,6 +565,86 @@ sub Init() {
|
||||
}
|
||||
}
|
||||
|
||||
sub FilterAndPrint {
|
||||
my ($profile, $symbols, $libs, $thread) = @_;
|
||||
|
||||
# Get total data in profile
|
||||
my $total = TotalProfile($profile);
|
||||
|
||||
# Remove uniniteresting stack items
|
||||
$profile = RemoveUninterestingFrames($symbols, $profile);
|
||||
|
||||
# Focus?
|
||||
if ($main::opt_focus ne '') {
|
||||
$profile = FocusProfile($symbols, $profile, $main::opt_focus);
|
||||
}
|
||||
|
||||
# Ignore?
|
||||
if ($main::opt_ignore ne '') {
|
||||
$profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
|
||||
}
|
||||
|
||||
my $calls = ExtractCalls($symbols, $profile);
|
||||
|
||||
# Reduce profiles to required output granularity, and also clean
|
||||
# each stack trace so a given entry exists at most once.
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
my $flat = FlatProfile($reduced);
|
||||
my $cumulative = CumulativeProfile($reduced);
|
||||
|
||||
# Print
|
||||
if (!$main::opt_interactive) {
|
||||
if ($main::opt_disasm) {
|
||||
PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);
|
||||
} elsif ($main::opt_list) {
|
||||
PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
|
||||
} elsif ($main::opt_text) {
|
||||
# Make sure the output is empty when have nothing to report
|
||||
# (only matters when --heapcheck is given but we must be
|
||||
# compatible with old branches that did not pass --heapcheck always):
|
||||
if ($total != 0) {
|
||||
printf("Total%s: %s %s\n",
|
||||
(defined($thread) ? " (t$thread)" : ""),
|
||||
Unparse($total), Units());
|
||||
}
|
||||
PrintText($symbols, $flat, $cumulative, -1);
|
||||
} elsif ($main::opt_raw) {
|
||||
PrintSymbolizedProfile($symbols, $profile, $main::prog);
|
||||
} elsif ($main::opt_callgrind) {
|
||||
PrintCallgrind($calls);
|
||||
} else {
|
||||
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
|
||||
if ($main::opt_gv) {
|
||||
RunGV(TempName($main::next_tmpfile, "ps"), "");
|
||||
} elsif ($main::opt_evince) {
|
||||
RunEvince(TempName($main::next_tmpfile, "pdf"), "");
|
||||
} elsif ($main::opt_web) {
|
||||
my $tmp = TempName($main::next_tmpfile, "svg");
|
||||
RunWeb($tmp);
|
||||
# The command we run might hand the file name off
|
||||
# to an already running browser instance and then exit.
|
||||
# Normally, we'd remove $tmp on exit (right now),
|
||||
# but fork a child to remove $tmp a little later, so that the
|
||||
# browser has time to load it first.
|
||||
delete $main::tempnames{$tmp};
|
||||
if (fork() == 0) {
|
||||
sleep 5;
|
||||
unlink($tmp);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
InteractiveMode($profile, $symbols, $libs, $total);
|
||||
}
|
||||
}
|
||||
|
||||
sub Main() {
|
||||
Init();
|
||||
$main::collected_profile = undef;
|
||||
@ -605,9 +688,6 @@ sub Main() {
|
||||
$symbol_map = MergeSymbols($symbol_map, $base->{symbols});
|
||||
}
|
||||
|
||||
# Get total data in profile
|
||||
my $total = TotalProfile($profile);
|
||||
|
||||
# Collect symbols
|
||||
my $symbols;
|
||||
if ($main::use_symbolized_profile) {
|
||||
@ -622,75 +702,16 @@ sub Main() {
|
||||
$symbols = ExtractSymbols($libs, $pcs);
|
||||
}
|
||||
|
||||
# Remove uniniteresting stack items
|
||||
$profile = RemoveUninterestingFrames($symbols, $profile);
|
||||
|
||||
# Focus?
|
||||
if ($main::opt_focus ne '') {
|
||||
$profile = FocusProfile($symbols, $profile, $main::opt_focus);
|
||||
if (!defined($main::opt_thread)) {
|
||||
FilterAndPrint($profile, $symbols, $libs);
|
||||
}
|
||||
|
||||
# Ignore?
|
||||
if ($main::opt_ignore ne '') {
|
||||
$profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
|
||||
}
|
||||
|
||||
my $calls = ExtractCalls($symbols, $profile);
|
||||
|
||||
# Reduce profiles to required output granularity, and also clean
|
||||
# each stack trace so a given entry exists at most once.
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
my $flat = FlatProfile($reduced);
|
||||
my $cumulative = CumulativeProfile($reduced);
|
||||
|
||||
# Print
|
||||
if (!$main::opt_interactive) {
|
||||
if ($main::opt_disasm) {
|
||||
PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);
|
||||
} elsif ($main::opt_list) {
|
||||
PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
|
||||
} elsif ($main::opt_text) {
|
||||
# Make sure the output is empty when have nothing to report
|
||||
# (only matters when --heapcheck is given but we must be
|
||||
# compatible with old branches that did not pass --heapcheck always):
|
||||
if ($total != 0) {
|
||||
printf("Total: %s %s\n", Unparse($total), Units());
|
||||
}
|
||||
PrintText($symbols, $flat, $cumulative, -1);
|
||||
} elsif ($main::opt_raw) {
|
||||
PrintSymbolizedProfile($symbols, $profile, $main::prog);
|
||||
} elsif ($main::opt_callgrind) {
|
||||
PrintCallgrind($calls);
|
||||
} else {
|
||||
if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
|
||||
if ($main::opt_gv) {
|
||||
RunGV(TempName($main::next_tmpfile, "ps"), "");
|
||||
} elsif ($main::opt_evince) {
|
||||
RunEvince(TempName($main::next_tmpfile, "pdf"), "");
|
||||
} elsif ($main::opt_web) {
|
||||
my $tmp = TempName($main::next_tmpfile, "svg");
|
||||
RunWeb($tmp);
|
||||
# The command we run might hand the file name off
|
||||
# to an already running browser instance and then exit.
|
||||
# Normally, we'd remove $tmp on exit (right now),
|
||||
# but fork a child to remove $tmp a little later, so that the
|
||||
# browser has time to load it first.
|
||||
delete $main::tempnames{$tmp};
|
||||
if (fork() == 0) {
|
||||
sleep 5;
|
||||
unlink($tmp);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
exit(1);
|
||||
if (defined($data->{threads})) {
|
||||
foreach my $thread (sort { $a <=> $b } keys(%{$data->{threads}})) {
|
||||
if (!defined($main::opt_thread) || $main::opt_thread == $thread) {
|
||||
my $thread_profile = $data->{threads}{$thread};
|
||||
FilterAndPrint($thread_profile, $symbols, $libs, $thread);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
InteractiveMode($profile, $symbols, $libs, $total);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
@ -3689,6 +3710,7 @@ sub IsSymbolizedProfileFile {
|
||||
# $result->{version} Version number of profile file
|
||||
# $result->{period} Sampling period (in microseconds)
|
||||
# $result->{profile} Profile object
|
||||
# $result->{threads} Map of thread IDs to profile objects
|
||||
# $result->{map} Memory map info from profile
|
||||
# $result->{pcs} Hash of all PC values seen, key is hex address
|
||||
sub ReadProfile {
|
||||
@ -3737,6 +3759,9 @@ sub ReadProfile {
|
||||
} elsif ($header =~ m/^heap profile:/) {
|
||||
$main::profile_type = 'heap';
|
||||
$result = ReadHeapProfile($prog, *PROFILE, $header);
|
||||
} elsif ($header =~ m/^heap/) {
|
||||
$main::profile_type = 'heap';
|
||||
$result = ReadThreadedHeapProfile($prog, $fname, $header);
|
||||
} elsif ($header =~ m/^--- *$contention_marker/o) {
|
||||
$main::profile_type = 'contention';
|
||||
$result = ReadSynchProfile($prog, *PROFILE);
|
||||
@ -3879,11 +3904,7 @@ sub ReadCPUProfile {
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub ReadHeapProfile {
|
||||
my $prog = shift;
|
||||
local *PROFILE = shift;
|
||||
my $header = shift;
|
||||
|
||||
sub HeapProfileIndex {
|
||||
my $index = 1;
|
||||
if ($main::opt_inuse_space) {
|
||||
$index = 1;
|
||||
@ -3894,6 +3915,84 @@ sub ReadHeapProfile {
|
||||
} elsif ($main::opt_alloc_objects) {
|
||||
$index = 2;
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
sub ReadMappedLibraries {
|
||||
my $fh = shift;
|
||||
my $map = "";
|
||||
# Read the /proc/self/maps data
|
||||
while (<$fh>) {
|
||||
s/\r//g; # turn windows-looking lines into unix-looking lines
|
||||
$map .= $_;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
sub ReadMemoryMap {
|
||||
my $fh = shift;
|
||||
my $map = "";
|
||||
# Read /proc/self/maps data as formatted by DumpAddressMap()
|
||||
my $buildvar = "";
|
||||
while (<PROFILE>) {
|
||||
s/\r//g; # turn windows-looking lines into unix-looking lines
|
||||
# Parse "build=<dir>" specification if supplied
|
||||
if (m/^\s*build=(.*)\n/) {
|
||||
$buildvar = $1;
|
||||
}
|
||||
|
||||
# Expand "$build" variable if available
|
||||
$_ =~ s/\$build\b/$buildvar/g;
|
||||
|
||||
$map .= $_;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
sub AdjustSamples {
|
||||
my ($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2) = @_;
|
||||
if ($sample_adjustment) {
|
||||
if ($sampling_algorithm == 2) {
|
||||
# Remote-heap version 2
|
||||
# The sampling frequency is the rate of a Poisson process.
|
||||
# This means that the probability of sampling an allocation of
|
||||
# size X with sampling rate Y is 1 - exp(-X/Y)
|
||||
if ($n1 != 0) {
|
||||
my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
|
||||
my $scale_factor = 1/(1 - exp(-$ratio));
|
||||
$n1 *= $scale_factor;
|
||||
$s1 *= $scale_factor;
|
||||
}
|
||||
if ($n2 != 0) {
|
||||
my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
|
||||
my $scale_factor = 1/(1 - exp(-$ratio));
|
||||
$n2 *= $scale_factor;
|
||||
$s2 *= $scale_factor;
|
||||
}
|
||||
} else {
|
||||
# Remote-heap version 1
|
||||
my $ratio;
|
||||
$ratio = (($s1*1.0)/$n1)/($sample_adjustment);
|
||||
if ($ratio < 1) {
|
||||
$n1 /= $ratio;
|
||||
$s1 /= $ratio;
|
||||
}
|
||||
$ratio = (($s2*1.0)/$n2)/($sample_adjustment);
|
||||
if ($ratio < 1) {
|
||||
$n2 /= $ratio;
|
||||
$s2 /= $ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($n1, $s1, $n2, $s2);
|
||||
}
|
||||
|
||||
sub ReadHeapProfile {
|
||||
my $prog = shift;
|
||||
local *PROFILE = shift;
|
||||
my $header = shift;
|
||||
|
||||
my $index = HeapProfileIndex();
|
||||
|
||||
# Find the type of this profile. The header line looks like:
|
||||
# heap profile: 1246: 8800744 [ 1246: 8800744] @ <heap-url>/266053
|
||||
@ -3983,29 +4082,12 @@ sub ReadHeapProfile {
|
||||
while (<PROFILE>) {
|
||||
s/\r//g; # turn windows-looking lines into unix-looking lines
|
||||
if (/^MAPPED_LIBRARIES:/) {
|
||||
# Read the /proc/self/maps data
|
||||
while (<PROFILE>) {
|
||||
s/\r//g; # turn windows-looking lines into unix-looking lines
|
||||
$map .= $_;
|
||||
}
|
||||
$map .= ReadMappedLibraries(*PROFILE);
|
||||
last;
|
||||
}
|
||||
|
||||
if (/^--- Memory map:/) {
|
||||
# Read /proc/self/maps data as formatted by DumpAddressMap()
|
||||
my $buildvar = "";
|
||||
while (<PROFILE>) {
|
||||
s/\r//g; # turn windows-looking lines into unix-looking lines
|
||||
# Parse "build=<dir>" specification if supplied
|
||||
if (m/^\s*build=(.*)\n/) {
|
||||
$buildvar = $1;
|
||||
}
|
||||
|
||||
# Expand "$build" variable if available
|
||||
$_ =~ s/\$build\b/$buildvar/g;
|
||||
|
||||
$map .= $_;
|
||||
}
|
||||
$map .= ReadMemoryMap(*PROFILE);
|
||||
last;
|
||||
}
|
||||
|
||||
@ -4016,42 +4098,8 @@ sub ReadHeapProfile {
|
||||
if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) {
|
||||
my $stack = $5;
|
||||
my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
|
||||
|
||||
if ($sample_adjustment) {
|
||||
if ($sampling_algorithm == 2) {
|
||||
# Remote-heap version 2
|
||||
# The sampling frequency is the rate of a Poisson process.
|
||||
# This means that the probability of sampling an allocation of
|
||||
# size X with sampling rate Y is 1 - exp(-X/Y)
|
||||
if ($n1 != 0) {
|
||||
my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
|
||||
my $scale_factor = 1/(1 - exp(-$ratio));
|
||||
$n1 *= $scale_factor;
|
||||
$s1 *= $scale_factor;
|
||||
}
|
||||
if ($n2 != 0) {
|
||||
my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
|
||||
my $scale_factor = 1/(1 - exp(-$ratio));
|
||||
$n2 *= $scale_factor;
|
||||
$s2 *= $scale_factor;
|
||||
}
|
||||
} else {
|
||||
# Remote-heap version 1
|
||||
my $ratio;
|
||||
$ratio = (($s1*1.0)/$n1)/($sample_adjustment);
|
||||
if ($ratio < 1) {
|
||||
$n1 /= $ratio;
|
||||
$s1 /= $ratio;
|
||||
}
|
||||
$ratio = (($s2*1.0)/$n2)/($sample_adjustment);
|
||||
if ($ratio < 1) {
|
||||
$n2 /= $ratio;
|
||||
$s2 /= $ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @counts = ($n1, $s1, $n2, $s2);
|
||||
my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
|
||||
$n1, $s1, $n2, $s2);
|
||||
AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
|
||||
}
|
||||
}
|
||||
@ -4065,6 +4113,83 @@ sub ReadHeapProfile {
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub ReadThreadedHeapProfile {
|
||||
my ($prog, $fname, $header) = @_;
|
||||
|
||||
my $index = HeapProfileIndex();
|
||||
my $sampling_algorithm = 0;
|
||||
my $sample_adjustment = 0;
|
||||
chomp($header);
|
||||
my $type = "unknown";
|
||||
# Assuming a very specific type of header for now.
|
||||
if ($header =~ m"^heap_v2/(\d+)") {
|
||||
$type = "_v2";
|
||||
$sampling_algorithm = 2;
|
||||
$sample_adjustment = int($1);
|
||||
}
|
||||
if ($type ne "_v2" || !defined($sample_adjustment)) {
|
||||
die "Threaded heap profiles require v2 sampling with a sample rate\n";
|
||||
}
|
||||
|
||||
my $profile = {};
|
||||
my $thread_profiles = {};
|
||||
my $pcs = {};
|
||||
my $map = "";
|
||||
my $stack = "";
|
||||
|
||||
while (<PROFILE>) {
|
||||
s/\r//g;
|
||||
if (/^MAPPED_LIBRARIES:/) {
|
||||
$map .= ReadMappedLibraries(*PROFILE);
|
||||
last;
|
||||
}
|
||||
|
||||
if (/^--- Memory map:/) {
|
||||
$map .= ReadMemoryMap(*PROFILE);
|
||||
last;
|
||||
}
|
||||
|
||||
# Read entry of the form:
|
||||
# @ a1 a2 ... an
|
||||
# t*: <count1>: <bytes1> [<count2>: <bytes2>]
|
||||
# t1: <count1>: <bytes1> [<count2>: <bytes2>]
|
||||
# ...
|
||||
# tn: <count1>: <bytes1> [<count2>: <bytes2>]
|
||||
s/^\s*//;
|
||||
s/\s*$//;
|
||||
if (m/^@\s+(.*)$/) {
|
||||
$stack = $1;
|
||||
} elsif (m/^\s*(t(\*|\d+)):\s+(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]$/) {
|
||||
if ($stack eq "") {
|
||||
# Still in the header, so this is just a per-thread summary.
|
||||
next;
|
||||
}
|
||||
my $thread = $2;
|
||||
my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6);
|
||||
my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
|
||||
$n1, $s2, $n2, $s2);
|
||||
if ($thread eq "*") {
|
||||
AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
|
||||
} else {
|
||||
if (!exists($thread_profiles->{$thread})) {
|
||||
$thread_profiles->{$thread} = {};
|
||||
}
|
||||
AddEntries($thread_profiles->{$thread}, $pcs,
|
||||
FixCallerAddresses($stack), $counts[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $r = {};
|
||||
$r->{version} = "heap";
|
||||
$r->{period} = 1;
|
||||
$r->{profile} = $profile;
|
||||
$r->{threads} = $thread_profiles;
|
||||
$r->{libs} = ParseLibraries($prog, $map, $pcs);
|
||||
$r->{pcs} = $pcs;
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub ReadSynchProfile {
|
||||
my $prog = shift;
|
||||
local *PROFILE = shift;
|
||||
|
Loading…
Reference in New Issue
Block a user