use strict;
use warnings;
use Getopt::Long;
use Cwd;
my $JEPROF_VERSION = "@jemalloc_version@";
my $PPROF_VERSION = "2.0";
my %obj_tool_map = (
"objdump" => "objdump",
"nm" => "nm",
"addr2line" => "addr2line",
"c++filt" => "c++filt",
);
my @DOT = ("dot"); my @GV = ("gv");
my @EVINCE = ("evince"); my @KCACHEGRIND = ("kcachegrind");
my @PS2PDF = ("ps2pdf");
my @URL_FETCHER = ("curl", "-s", "--fail");
my $HEAP_PAGE = "/pprof/heap";
my $PROFILE_PAGE = "/pprof/profile"; my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; my $GROWTH_PAGE = "/pprof/growth";
my $CONTENTION_PAGE = "/pprof/contention";
my $WALL_PAGE = "/pprof/wall(?:\\?.*)?"; my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; my $SYMBOL_PAGE = "/pprof/symbol"; my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
"$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
"$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
my $UNKNOWN_BINARY = "(unknown)";
my $address_length = 16;
my $dev_null = "/dev/null";
if (! -e $dev_null && $^O =~ /MSWin/) { $dev_null = "nul";
}
my @prefix_list = ();
my $sep_symbol = '_fini';
my $sep_address = undef;
sub usage_string {
return <<EOF;
Usage:
jeprof [options] <program> <profiles>
<profiles> is a space separated list of profile names.
jeprof [options] <symbolized-profiles>
<symbolized-profiles> is a list of profile files where each file contains
the necessary symbol mappings as well as profile data (likely generated
with --raw).
jeprof [options] <profile>
<profile> is a remote form. Symbols are obtained from host:port$SYMBOL_PAGE
Each name can be:
/path/to/profile - a path to a profile file
host:port[/<service>] - a location of a service to get profile from
The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
$GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
$CENSUSPROFILE_PAGE, or /pprof/filteredprofile.
For instance:
jeprof http://myserver.com:80$HEAP_PAGE
If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
jeprof --symbols <program>
Maps addresses to symbol names. In this mode, stdin should be a
list of library mappings, in the same format as is found in the heap-
and cpu-profile files (this loosely matches that of /proc/self/maps
on linux), followed by a list of hex addresses to map, one per line.
For more help with querying remote servers, including how to add the
necessary server-side support code, see this filename (or one like it):
/usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html
Options:
--cum Sort by cumulative data
--base=<base> Subtract <base> from <profile> before display
--interactive Run in interactive mode (interactive "help" gives help) [default]
--seconds=<n> Length of time for dynamic profiles [default=30 secs]
--add_lib=<file> Read additional symbols and line info from the given library
--lib_prefix=<dir> Comma separated list of library path prefixes
Reporting Granularity:
--addresses Report at address level
--lines Report at source line level
--functions Report at function level [default]
--files Report at source file level
Output type:
--text Generate text report
--callgrind Generate callgrind format to stdout
--gv Generate Postscript and display
--evince Generate PDF and display
--web Generate SVG and display
--list=<regexp> Generate source listing of matching routines
--disasm=<regexp> Generate disassembly of matching routines
--symbols Print demangled symbol names found at given addresses
--dot Generate DOT file to stdout
--ps Generate Postcript to stdout
--pdf Generate PDF to stdout
--svg Generate SVG to stdout
--gif Generate GIF to stdout
--raw Generate symbolized jeprof data (useful with remote fetch)
--collapsed Generate collapsed stacks for building flame graphs
(see http://www.brendangregg.com/flamegraphs.html)
Heap-Profile Options:
--inuse_space Display in-use (mega)bytes [default]
--inuse_objects Display in-use objects
--alloc_space Display allocated (mega)bytes
--alloc_objects Display allocated objects
--show_bytes Display space in bytes
--drop_negative Ignore negative differences
Contention-profile options:
--total_delay Display total delay at each region [default]
--contentions Display number of delays at each region
--mean_delay Display mean delay at each region
Call-graph Options:
--nodecount=<n> Show at most so many nodes [default=80]
--nodefraction=<f> Hide nodes below <f>*total [default=.005]
--edgefraction=<f> Hide edges below <f>*total [default=.001]
--maxdegree=<n> Max incoming/outgoing edges per node [default=8]
--focus=<regexp> Focus on backtraces with nodes matching <regexp>
--thread=<n> Show profile for thread <n>
--ignore=<regexp> Ignore backtraces with nodes matching <regexp>
--scale=<n> Set GV scaling [default=0]
--heapcheck Make nodes with non-0 object counts
(i.e. direct leak generators) more visible
--retain=<regexp> Retain only nodes that match <regexp>
--exclude=<regexp> Exclude all nodes that match <regexp>
Miscellaneous:
--tools=<prefix or binary:fullpath>[,...] \$PATH for object tool pathnames
--test Run unit tests
--help This message
--version Version information
--debug-syms-by-id (Linux only) Find debug symbol files by build ID as well as by name
Environment Variables:
JEPROF_TMPDIR Profiles directory. Defaults to \$HOME/jeprof
JEPROF_TOOLS Prefix for object tools pathnames
Examples:
jeprof /bin/ls ls.prof
Enters "interactive" mode
jeprof --text /bin/ls ls.prof
Outputs one line per procedure
jeprof --web /bin/ls ls.prof
Displays annotated call-graph in web browser
jeprof --gv /bin/ls ls.prof
Displays annotated call-graph via 'gv'
jeprof --gv --focus=Mutex /bin/ls ls.prof
Restricts to code paths including a .*Mutex.* entry
jeprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
Code paths including Mutex but not string
jeprof --list=getdir /bin/ls ls.prof
(Per-line) annotated source listing for getdir()
jeprof --disasm=getdir /bin/ls ls.prof
(Per-PC) annotated disassembly for getdir()
jeprof http://localhost:1234/
Enters "interactive" mode
jeprof --text localhost:1234
Outputs one line per procedure for localhost:1234
jeprof --raw localhost:1234 > ./local.raw
jeprof --text ./local.raw
Fetches a remote profile for later analysis and then
analyzes it in text mode.
EOF
}
sub version_string {
return <<EOF
jeprof (part of jemalloc $JEPROF_VERSION)
based on pprof (part of gperftools $PPROF_VERSION)
Copyright 1998-2007 Google Inc.
This is BSD licensed software; see the source for copying conditions
and license information.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
EOF
}
sub usage {
my $msg = shift;
print STDERR "$msg\n\n";
print STDERR usage_string();
print STDERR "\nFATAL ERROR: $msg\n"; exit(1);
}
sub Init() {
$main::tmpfile_sym = "/tmp/jeprof$$.sym";
$main::tmpfile_ps = "/tmp/jeprof$$";
$main::next_tmpfile = 0;
$SIG{'INT'} = \&sighandler;
$main::source_cache = ();
$main::opt_help = 0;
$main::opt_version = 0;
$main::opt_cum = 0;
$main::opt_base = '';
$main::opt_addresses = 0;
$main::opt_lines = 0;
$main::opt_functions = 0;
$main::opt_files = 0;
$main::opt_lib_prefix = "";
$main::opt_text = 0;
$main::opt_callgrind = 0;
$main::opt_list = "";
$main::opt_disasm = "";
$main::opt_symbols = 0;
$main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_web = 0;
$main::opt_dot = 0;
$main::opt_ps = 0;
$main::opt_pdf = 0;
$main::opt_gif = 0;
$main::opt_svg = 0;
$main::opt_raw = 0;
$main::opt_collapsed = 0;
$main::opt_nodecount = 80;
$main::opt_nodefraction = 0.005;
$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;
$main::opt_retain = '';
$main::opt_exclude = '';
$main::opt_seconds = 30;
$main::opt_lib = "";
$main::opt_inuse_space = 0;
$main::opt_inuse_objects = 0;
$main::opt_alloc_space = 0;
$main::opt_alloc_objects = 0;
$main::opt_show_bytes = 0;
$main::opt_drop_negative = 0;
$main::opt_interactive = 0;
$main::opt_total_delay = 0;
$main::opt_contentions = 0;
$main::opt_mean_delay = 0;
$main::opt_tools = "";
$main::opt_debug = 0;
$main::opt_test = 0;
$main::opt_debug_syms_by_id = 0;
$main::opt_test_stride = 0;
$main::use_symbol_page = 0;
%main::tempnames = ();
$main::profile_type = '';
GetOptions("help!" => \$main::opt_help,
"version!" => \$main::opt_version,
"cum!" => \$main::opt_cum,
"base=s" => \$main::opt_base,
"seconds=i" => \$main::opt_seconds,
"add_lib=s" => \$main::opt_lib,
"lib_prefix=s" => \$main::opt_lib_prefix,
"functions!" => \$main::opt_functions,
"lines!" => \$main::opt_lines,
"addresses!" => \$main::opt_addresses,
"files!" => \$main::opt_files,
"text!" => \$main::opt_text,
"callgrind!" => \$main::opt_callgrind,
"list=s" => \$main::opt_list,
"disasm=s" => \$main::opt_disasm,
"symbols!" => \$main::opt_symbols,
"gv!" => \$main::opt_gv,
"evince!" => \$main::opt_evince,
"web!" => \$main::opt_web,
"dot!" => \$main::opt_dot,
"ps!" => \$main::opt_ps,
"pdf!" => \$main::opt_pdf,
"svg!" => \$main::opt_svg,
"gif!" => \$main::opt_gif,
"raw!" => \$main::opt_raw,
"collapsed!" => \$main::opt_collapsed,
"interactive!" => \$main::opt_interactive,
"nodecount=i" => \$main::opt_nodecount,
"nodefraction=f" => \$main::opt_nodefraction,
"edgefraction=f" => \$main::opt_edgefraction,
"maxdegree=i" => \$main::opt_maxdegree,
"focus=s" => \$main::opt_focus,
"thread=s" => \$main::opt_thread,
"ignore=s" => \$main::opt_ignore,
"scale=i" => \$main::opt_scale,
"heapcheck" => \$main::opt_heapcheck,
"retain=s" => \$main::opt_retain,
"exclude=s" => \$main::opt_exclude,
"inuse_space!" => \$main::opt_inuse_space,
"inuse_objects!" => \$main::opt_inuse_objects,
"alloc_space!" => \$main::opt_alloc_space,
"alloc_objects!" => \$main::opt_alloc_objects,
"show_bytes!" => \$main::opt_show_bytes,
"drop_negative!" => \$main::opt_drop_negative,
"total_delay!" => \$main::opt_total_delay,
"contentions!" => \$main::opt_contentions,
"mean_delay!" => \$main::opt_mean_delay,
"tools=s" => \$main::opt_tools,
"test!" => \$main::opt_test,
"debug!" => \$main::opt_debug,
"debug-syms-by-id!" => \$main::opt_debug_syms_by_id,
"test_stride=i" => \$main::opt_test_stride,
) || usage("Invalid option(s)");
if ($main::opt_help) {
print usage_string();
exit(0);
}
if ($main::opt_version) {
print version_string();
exit(0);
}
if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {
$main::opt_functions = 0;
$main::opt_lines = 0;
$main::opt_addresses = 1;
$main::opt_files = 0;
}
if ($main::opt_inuse_space +
$main::opt_inuse_objects +
$main::opt_alloc_space +
$main::opt_alloc_objects > 1) {
usage("Specify at most on of --inuse/--alloc options");
}
my $grains =
$main::opt_functions +
$main::opt_lines +
$main::opt_addresses +
$main::opt_files +
0;
if ($grains > 1) {
usage("Only specify one output granularity option");
}
if ($grains == 0) {
$main::opt_functions = 1;
}
my $modes =
$main::opt_text +
$main::opt_callgrind +
($main::opt_list eq '' ? 0 : 1) +
($main::opt_disasm eq '' ? 0 : 1) +
($main::opt_symbols == 0 ? 0 : 1) +
$main::opt_gv +
$main::opt_evince +
$main::opt_web +
$main::opt_dot +
$main::opt_ps +
$main::opt_pdf +
$main::opt_svg +
$main::opt_gif +
$main::opt_raw +
$main::opt_collapsed +
$main::opt_interactive +
0;
if ($modes > 1) {
usage("Only specify one output mode");
}
if ($modes == 0) {
if (-t STDOUT) { $main::opt_interactive = 1;
} else {
$main::opt_text = 1;
}
}
if ($main::opt_test) {
RunUnitTests();
exit(1);
}
$main::prog = "";
@main::pfile_args = ();
if (@ARGV > 0) {
if (IsProfileURL($ARGV[0])) {
$main::use_symbol_page = 1;
} elsif (IsSymbolizedProfileFile($ARGV[0])) {
$main::use_symbolized_profile = 1;
$main::prog = $UNKNOWN_BINARY; }
}
if ($main::use_symbol_page || $main::use_symbolized_profile) {
my %disabled = ('--lines' => $main::opt_lines,
'--disasm' => $main::opt_disasm);
for my $option (keys %disabled) {
usage("$option cannot be used without a binary") if $disabled{$option};
}
scalar(@ARGV) || usage("Did not specify profile file");
} elsif ($main::opt_symbols) {
$main::prog = shift(@ARGV) || usage("Did not specify program");
} else {
$main::prog = shift(@ARGV) || usage("Did not specify program");
scalar(@ARGV) || usage("Did not specify profile file");
}
foreach my $farg (@ARGV) {
if ($farg =~ m/(.*)\@([0-9]+)(|\/.*)$/ ) {
my $machine = $1;
my $num_machines = $2;
my $path = $3;
for (my $i = 0; $i < $num_machines; $i++) {
unshift(@main::pfile_args, "$i.$machine$path");
}
} else {
unshift(@main::pfile_args, $farg);
}
}
if ($main::use_symbol_page) {
unless (IsProfileURL($main::pfile_args[0])) {
error("The first profile should be a remote form to use $SYMBOL_PAGE\n");
}
CheckSymbolPage();
$main::prog = FetchProgramName();
} elsif (!$main::use_symbolized_profile) { ConfigureObjTools($main::prog)
}
@prefix_list = split (',', $main::opt_lib_prefix);
foreach (@prefix_list) {
s|/+$||;
}
$main::gave_up_on_elfutils = 0;
}
sub FilterAndPrint {
my ($profile, $symbols, $libs, $thread) = @_;
my $total = TotalProfile($profile);
$profile = RemoveUninterestingFrames($symbols, $profile);
if ($main::opt_focus ne '') {
$profile = FocusProfile($symbols, $profile, $main::opt_focus);
}
if ($main::opt_ignore ne '') {
$profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
}
my $calls = ExtractCalls($symbols, $profile);
my $reduced = ReduceProfile($symbols, $profile);
my $flat = FlatProfile($reduced);
my $cumulative = CumulativeProfile($reduced);
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) {
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_collapsed) {
PrintCollapsedStacks($symbols, $profile);
} 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);
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;
@main::profile_files = ();
$main::op_time = time();
if ($main::opt_symbols) {
PrintSymbols(*STDIN); return;
}
FetchDynamicProfiles();
my $symbol_map = {};
my $data = ReadProfile($main::prog, pop(@main::profile_files));
my $profile = $data->{profile};
my $pcs = $data->{pcs};
my $libs = $data->{libs}; $symbol_map = MergeSymbols($symbol_map, $data->{symbols});
if (scalar(@main::profile_files) > 0) {
foreach my $pname (@main::profile_files) {
my $data2 = ReadProfile($main::prog, $pname);
$profile = AddProfile($profile, $data2->{profile});
$pcs = AddPcs($pcs, $data2->{pcs});
$symbol_map = MergeSymbols($symbol_map, $data2->{symbols});
}
}
if ($main::opt_base ne '') {
my $base = ReadProfile($main::prog, $main::opt_base);
$profile = SubtractProfile($profile, $base->{profile});
$pcs = AddPcs($pcs, $base->{pcs});
$symbol_map = MergeSymbols($symbol_map, $base->{symbols});
}
my $symbols;
if ($main::use_symbolized_profile) {
$symbols = FetchSymbols($pcs, $symbol_map);
} elsif ($main::use_symbol_page) {
$symbols = FetchSymbols($pcs);
} else {
$symbols = ExtractSymbols($libs, $pcs);
}
if (!defined($main::opt_thread)) {
FilterAndPrint($profile, $symbols, $libs);
}
if (defined($data->{threads})) {
foreach my $thread (sort { $a <=> $b } keys(%{$data->{threads}})) {
if (defined($main::opt_thread) &&
($main::opt_thread eq '*' || $main::opt_thread == $thread)) {
my $thread_profile = $data->{threads}{$thread};
FilterAndPrint($thread_profile, $symbols, $libs, $thread);
}
}
}
cleanup();
exit(0);
}
Main();
sub ReadlineMightFail {
if (-e '/lib/libtermcap.so.2') {
return 0; } else {
return 1;
}
}
sub RunGV {
my $fname = shift;
my $bg = shift; if (!system(ShellEscape(@GV, "--version") . " >$dev_null 2>&1")) {
system(ShellEscape(@GV, "--scale=$main::opt_scale", "--noantialias", $fname)
. $bg);
} else {
print STDERR ShellEscape(@GV, "-scale", $main::opt_scale) . "\n";
system(ShellEscape(@GV, "-scale", "$main::opt_scale", $fname) . $bg);
}
}
sub RunEvince {
my $fname = shift;
my $bg = shift; system(ShellEscape(@EVINCE, $fname) . $bg);
}
sub RunWeb {
my $fname = shift;
print STDERR "Loading web page file:///$fname\n";
if (`uname` =~ /Darwin/) {
system("/usr/bin/open", $fname);
return;
}
my @alt = (
"/etc/alternatives/gnome-www-browser",
"/etc/alternatives/x-www-browser",
"google-chrome",
"firefox",
);
foreach my $b (@alt) {
if (system($b, $fname) == 0) {
return;
}
}
print STDERR "Could not load web browser.\n";
}
sub RunKcachegrind {
my $fname = shift;
my $bg = shift; print STDERR "Starting '@KCACHEGRIND " . $fname . $bg . "'\n";
system(ShellEscape(@KCACHEGRIND, $fname) . $bg);
}
sub InteractiveMode {
$| = 1; my ($orig_profile, $symbols, $libs, $total) = @_;
print STDERR "Welcome to jeprof! For help, type 'help'.\n";
if ( -t STDIN &&
!ReadlineMightFail() &&
defined(eval {require Term::ReadLine}) ) {
my $term = new Term::ReadLine 'jeprof';
while ( defined ($_ = $term->readline('(jeprof) '))) {
$term->addhistory($_) if /\S/;
if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
last; }
}
} else { while (1) {
print STDERR "(jeprof) ";
$_ = <STDIN>;
last if ! defined $_ ;
s/\r//g;
my $save_opt_lines = $main::opt_lines;
if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
last; }
$main::opt_lines = $save_opt_lines;
}
}
}
sub InteractiveCommand {
my($orig_profile, $symbols, $libs, $total, $command) = @_;
$_ = $command; if (!defined($_)) {
print STDERR "\n";
return 0;
}
if (m/^\s*quit/) {
return 0;
}
if (m/^\s*help/) {
InteractiveHelpMessage();
return 1;
}
$main::opt_text = 0;
$main::opt_callgrind = 0;
$main::opt_disasm = 0;
$main::opt_list = 0;
$main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_cum = 0;
if (m/^\s*(text|top)(\d*)\s*(.*)/) {
$main::opt_text = 1;
my $line_limit = ($2 ne "") ? int($2) : 10;
my $routine;
my $ignore;
($routine, $ignore) = ParseInteractiveArgs($3);
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
my $reduced = ReduceProfile($symbols, $profile);
my $flat = FlatProfile($reduced);
my $cumulative = CumulativeProfile($reduced);
PrintText($symbols, $flat, $cumulative, $line_limit);
return 1;
}
if (m/^\s*callgrind\s*([^ \n]*)/) {
$main::opt_callgrind = 1;
my $calls = ExtractCalls($symbols, $orig_profile);
my $filename = $1;
if ( $1 eq '' ) {
$filename = TempName($main::next_tmpfile, "callgrind");
}
PrintCallgrind($calls, $filename);
if ( $1 eq '' ) {
RunKcachegrind($filename, " & ");
$main::next_tmpfile++;
}
return 1;
}
if (m/^\s*(web)?list\s*(.+)/) {
my $html = (defined($1) && ($1 eq "web"));
$main::opt_list = 1;
my $routine;
my $ignore;
($routine, $ignore) = ParseInteractiveArgs($2);
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
my $reduced = ReduceProfile($symbols, $profile);
my $flat = FlatProfile($reduced);
my $cumulative = CumulativeProfile($reduced);
PrintListing($total, $libs, $flat, $cumulative, $routine, $html);
return 1;
}
if (m/^\s*disasm\s*(.+)/) {
$main::opt_disasm = 1;
my $routine;
my $ignore;
($routine, $ignore) = ParseInteractiveArgs($1);
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
my $reduced = ReduceProfile($symbols, $profile);
my $flat = FlatProfile($reduced);
my $cumulative = CumulativeProfile($reduced);
PrintDisassembly($libs, $flat, $cumulative, $routine);
return 1;
}
if (m/^\s*(gv|web|evince)\s*(.*)/) {
$main::opt_gv = 0;
$main::opt_evince = 0;
$main::opt_web = 0;
if ($1 eq "gv") {
$main::opt_gv = 1;
} elsif ($1 eq "evince") {
$main::opt_evince = 1;
} elsif ($1 eq "web") {
$main::opt_web = 1;
}
my $focus;
my $ignore;
($focus, $ignore) = ParseInteractiveArgs($2);
my $profile = ProcessProfile($total, $orig_profile, $symbols,
$focus, $ignore);
my $reduced = ReduceProfile($symbols, $profile);
my $flat = FlatProfile($reduced);
my $cumulative = CumulativeProfile($reduced);
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) {
RunWeb(TempName($main::next_tmpfile, "svg"));
}
$main::next_tmpfile++;
}
return 1;
}
if (m/^\s*$/) {
return 1;
}
print STDERR "Unknown command: try 'help'.\n";
return 1;
}
sub ProcessProfile {
my $total_count = shift;
my $orig_profile = shift;
my $symbols = shift;
my $focus = shift;
my $ignore = shift;
my $profile = $orig_profile;
printf("Total: %s %s\n", Unparse($total_count), Units());
if ($focus ne '') {
$profile = FocusProfile($symbols, $profile, $focus);
my $focus_count = TotalProfile($profile);
printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
$focus,
Unparse($focus_count), Units(),
Unparse($total_count), ($focus_count*100.0) / $total_count);
}
if ($ignore ne '') {
$profile = IgnoreProfile($symbols, $profile, $ignore);
my $ignore_count = TotalProfile($profile);
printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
$ignore,
Unparse($ignore_count), Units(),
Unparse($total_count),
($ignore_count*100.0) / $total_count);
}
return $profile;
}
sub InteractiveHelpMessage {
print STDERR <<ENDOFHELP;
Interactive jeprof mode
Commands:
gv
gv [focus] [-ignore1] [-ignore2]
Show graphical hierarchical display of current profile. Without
any arguments, shows all samples in the profile. With the optional
"focus" argument, restricts the samples shown to just those where
the "focus" regular expression matches a routine name on the stack
trace.
web
web [focus] [-ignore1] [-ignore2]
Like GV, but displays profile in your web browser instead of using
Ghostview. Works best if your web browser is already running.
To change the browser that gets used:
On Linux, set the /etc/alternatives/gnome-www-browser symlink.
On OS X, change the Finder association for SVG files.
list [routine_regexp] [-ignore1] [-ignore2]
Show source listing of routines whose names match "routine_regexp"
weblist [routine_regexp] [-ignore1] [-ignore2]
Displays a source listing of routines whose names match "routine_regexp"
in a web browser. You can click on source lines to view the
corresponding disassembly.
top [--cum] [-ignore1] [-ignore2]
top20 [--cum] [-ignore1] [-ignore2]
top37 [--cum] [-ignore1] [-ignore2]
Show top lines ordered by flat profile count, or cumulative count
if --cum is specified. If a number is present after 'top', the
top K routines will be shown (defaults to showing the top 10)
disasm [routine_regexp] [-ignore1] [-ignore2]
Show disassembly of routines whose names match "routine_regexp",
annotated with sample counts.
callgrind
callgrind [filename]
Generates callgrind file. If no filename is given, kcachegrind is called.
help - This listing
quit or ^D - End jeprof
For commands that accept optional -ignore tags, samples where any routine in
the stack trace matches the regular expression in any of the -ignore
parameters will be ignored.
Further pprof details are available at this location (or one similar):
/usr/doc/gperftools-$PPROF_VERSION/cpu_profiler.html
/usr/doc/gperftools-$PPROF_VERSION/heap_profiler.html
ENDOFHELP
}
sub ParseInteractiveArgs {
my $args = shift;
my $focus = "";
my $ignore = "";
my @x = split(/ +/, $args);
foreach $a (@x) {
if ($a =~ m/^(--|-)lines$/) {
$main::opt_lines = 1;
} elsif ($a =~ m/^(--|-)cum$/) {
$main::opt_cum = 1;
} elsif ($a =~ m/^-(.*)/) {
$ignore .= (($ignore ne "") ? "|" : "" ) . $1;
} else {
$focus .= (($focus ne "") ? "|" : "" ) . $a;
}
}
if ($ignore ne "") {
print STDERR "Ignoring samples in call stacks that match '$ignore'\n";
}
return ($focus, $ignore);
}
sub TempName {
my $fnum = shift;
my $ext = shift;
my $file = "$main::tmpfile_ps.$fnum.$ext";
$main::tempnames{$file} = 1;
return $file;
}
sub PrintProfileData {
my $profile = shift;
print pack('L*', 0, 0, 3, 0, 0, 0, 1, 0, 0, 0);
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
if ($#addrs >= 0) {
my $depth = $#addrs + 1;
print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));
print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));
foreach my $full_addr (@addrs) {
my $addr = $full_addr;
$addr =~ s/0x0*//; if (length($addr) > 16) {
print STDERR "Invalid address in profile: $full_addr\n";
next;
}
my $low_addr = substr($addr, -8); my $high_addr = substr($addr, -16, 8); print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));
}
}
}
}
sub PrintSymbolizedProfile {
my $symbols = shift;
my $profile = shift;
my $prog = shift;
$SYMBOL_PAGE =~ m,[^/]+$,; my $symbol_marker = $&;
print '--- ', $symbol_marker, "\n";
if (defined($prog)) {
print 'binary=', $prog, "\n";
}
while (my ($pc, $name) = each(%{$symbols})) {
my $sep = ' ';
print '0x', $pc;
for (my $j = 2; $j <= $#{$name}; $j += 3) {
print $sep, $name->[$j];
$sep = '--';
}
print "\n";
}
print '---', "\n";
my $profile_marker;
if ($main::profile_type eq 'heap') {
$HEAP_PAGE =~ m,[^/]+$,; $profile_marker = $&;
} elsif ($main::profile_type eq 'growth') {
$GROWTH_PAGE =~ m,[^/]+$,; $profile_marker = $&;
} elsif ($main::profile_type eq 'contention') {
$CONTENTION_PAGE =~ m,[^/]+$,; $profile_marker = $&;
} else { $PROFILE_PAGE =~ m,[^/]+$,; $profile_marker = $&;
}
print '--- ', $profile_marker, "\n";
if (defined($main::collected_profile)) {
open(SRC, "<$main::collected_profile");
while (<SRC>) {
print $_;
}
close(SRC);
} else {
die "--raw/http: jeprof can only dump remote profiles for --raw\n";
PrintProfileData($profile);
}
}
sub PrintText {
my $symbols = shift;
my $flat = shift;
my $cumulative = shift;
my $line_limit = shift;
my $total = TotalProfile($flat);
my $s = $main::opt_cum ? $cumulative : $flat;
my $running_sum = 0;
my $lines = 0;
foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }
keys(%{$cumulative})) {
my $f = GetEntry($flat, $k);
my $c = GetEntry($cumulative, $k);
$running_sum += $f;
my $sym = $k;
if (exists($symbols->{$k})) {
$sym = $symbols->{$k}->[0] . " " . $symbols->{$k}->[1];
if ($main::opt_addresses) {
$sym = $k . " " . $sym;
}
}
if ($f != 0 || $c != 0) {
printf("%8s %6s %6s %8s %6s %s\n",
Unparse($f),
Percent($f, $total),
Percent($running_sum, $total),
Unparse($c),
Percent($c, $total),
$sym);
}
$lines++;
last if ($line_limit >= 0 && $lines >= $line_limit);
}
}
sub CompressedCGName {
my($key, $val, $map) = @_;
my $idx = $map->{$val};
if (length($val) <= 3) {
return "$key=$val\n";
} elsif (defined($idx)) {
return "$key=($idx)\n";
} else {
$idx = scalar(keys(%{$map})) + 1;
$map->{$val} = $idx;
return "$key=($idx) $val\n";
}
}
sub PrintCallgrind {
my $calls = shift;
my $filename;
my %filename_to_index_map;
my %fnname_to_index_map;
if ($main::opt_interactive) {
$filename = shift;
print STDERR "Writing callgrind file to '$filename'.\n"
} else {
$filename = "&STDOUT";
}
open(CG, ">$filename");
printf CG ("events: Hits\n\n");
foreach my $call ( map { $_->[0] }
sort { $a->[1] cmp $b ->[1] ||
$a->[2] <=> $b->[2] }
map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
[$_, $1, $2] }
keys %$calls ) {
my $count = int($calls->{$call});
$call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
my ( $caller_file, $caller_line, $caller_function,
$callee_file, $callee_line, $callee_function ) =
( $1, $2, $3, $5, $6, $7 );
printf CG CompressedCGName("fl", $caller_file, \%filename_to_index_map);
printf CG CompressedCGName("fn", $caller_function, \%fnname_to_index_map);
if (defined $6) {
printf CG CompressedCGName("cfl", $callee_file, \%filename_to_index_map);
printf CG CompressedCGName("cfn", $callee_function, \%fnname_to_index_map);
printf CG ("calls=$count $callee_line\n");
}
printf CG ("$caller_line $count\n\n");
}
}
sub PrintDisassembly {
my $libs = shift;
my $flat = shift;
my $cumulative = shift;
my $disasm_opts = shift;
my $total = TotalProfile($flat);
foreach my $lib (@{$libs}) {
my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);
my $offset = AddressSub($lib->[1], $lib->[3]);
foreach my $routine (sort ByName keys(%{$symbol_table})) {
my $start_addr = $symbol_table->{$routine}->[0];
my $end_addr = $symbol_table->{$routine}->[1];
my $length = hex(AddressSub($end_addr, $start_addr));
my $addr = AddressAdd($start_addr, $offset);
for (my $i = 0; $i < $length; $i++) {
if (defined($cumulative->{$addr})) {
PrintDisassembledFunction($lib->[0], $offset,
$routine, $flat, $cumulative,
$start_addr, $end_addr, $total);
last;
}
$addr = AddressInc($addr);
}
}
}
}
sub Disassemble {
my $prog = shift;
my $offset = shift;
my $start_addr = shift;
my $end_addr = shift;
my $objdump = $obj_tool_map{"objdump"};
my $cmd = ShellEscape($objdump, "-C", "-d", "-l", "--no-show-raw-insn",
"--start-address=0x$start_addr",
"--stop-address=0x$end_addr", $prog);
open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
my @result = ();
my $filename = "";
my $linenumber = -1;
my $last = ["", "", "", ""];
while (<OBJDUMP>) {
s/\r//g; chop;
if (m|\s*([^:\s]+):(\d+)\s*$|) {
$filename = $1;
$linenumber = $2;
} elsif (m/^ +([0-9a-f]+):\s*(.*)/) {
my $addr = HexExtend($1);
my $k = AddressAdd($addr, $offset);
$last->[4] = $k; $last = [$k, $filename, $linenumber, $2, $end_addr];
push(@result, $last);
}
}
close(OBJDUMP);
return @result;
}
sub PrintSymbols {
my $maps_and_symbols_file = shift;
my @pclist = (); my $pcs = {};
my $map = "";
foreach my $line (<$maps_and_symbols_file>) {
$line =~ s/\r//g; if ($line =~ /\b(0x[0-9a-f]+)\b/i) {
push(@pclist, HexExtend($1));
$pcs->{$pclist[-1]} = 1;
} else {
$map .= $line;
}
}
my $libs = ParseLibraries($main::prog, $map, $pcs);
my $symbols = ExtractSymbols($libs, $pcs);
foreach my $pc (@pclist) {
print(($symbols->{$pc}->[0] || "??") . "\n");
}
}
sub ByName {
return ShortFunctionName($a) cmp ShortFunctionName($b);
}
sub PrintListing {
my $total = shift;
my $libs = shift;
my $flat = shift;
my $cumulative = shift;
my $list_opts = shift;
my $html = shift;
my $output = \*STDOUT;
my $fname = "";
if ($html) {
$fname = TempName($main::next_tmpfile, "html");
$main::next_tmpfile++;
if (!open(TEMP, ">$fname")) {
print STDERR "$fname: $!\n";
return;
}
$output = \*TEMP;
print $output HtmlListingHeader();
printf $output ("<div class=\"legend\">%s<br>Total: %s %s</div>\n",
$main::prog, Unparse($total), Units());
}
my $listed = 0;
foreach my $lib (@{$libs}) {
my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);
my $offset = AddressSub($lib->[1], $lib->[3]);
foreach my $routine (sort ByName keys(%{$symbol_table})) {
my $start_addr = $symbol_table->{$routine}->[0];
my $end_addr = $symbol_table->{$routine}->[1];
my $length = hex(AddressSub($end_addr, $start_addr));
my $addr = AddressAdd($start_addr, $offset);
for (my $i = 0; $i < $length; $i++) {
if (defined($cumulative->{$addr})) {
$listed += PrintSource(
$lib->[0], $offset,
$routine, $flat, $cumulative,
$start_addr, $end_addr,
$html,
$output);
last;
}
$addr = AddressInc($addr);
}
}
}
if ($html) {
if ($listed > 0) {
print $output HtmlListingFooter();
close($output);
RunWeb($fname);
} else {
close($output);
unlink($fname);
}
}
}
sub HtmlListingHeader {
return <<'EOF';
<DOCTYPE html>
<html>
<head>
<title>Pprof listing</title>
<style type="text/css">
body {
font-family: sans-serif;
}
h1 {
font-size: 1.5em;
margin-bottom: 4px;
}
.legend {
font-size: 1.25em;
}
.line {
color: #aaaaaa;
}
.nop {
color: #aaaaaa;
}
.unimportant {
color: #cccccc;
}
.disasmloc {
color: #000000;
}
.deadsrc {
cursor: pointer;
}
.deadsrc:hover {
background-color: #eeeeee;
}
.livesrc {
color: #0000ff;
cursor: pointer;
}
.livesrc:hover {
background-color: #eeeeee;
}
.asm {
color: #008800;
display: none;
}
</style>
<script type="text/javascript">
function jeprof_toggle_asm(e) {
var target;
if (!e) e = window.event;
if (e.target) target = e.target;
else if (e.srcElement) target = e.srcElement;
if (target) {
var asm = target.nextSibling;
if (asm && asm.className == "asm") {
asm.style.display = (asm.style.display == "block" ? "" : "block");
e.preventDefault();
return false;
}
}
}
</script>
</head>
<body>
EOF
}
sub HtmlListingFooter {
return <<'EOF';
</body>
</html>
EOF
}
sub HtmlEscape {
my $text = shift;
$text =~ s/&/&/g;
$text =~ s/</</g;
$text =~ s/>/>/g;
return $text;
}
sub Indentation {
my $line = shift;
if (m/^(\s*)\S/) {
return length($1);
} else {
return -1;
}
}
sub GetTopLevelLineNumbers {
my ($lib, $offset, $instructions) = @_;
my $pcs = [];
for (my $i = 0; $i <= $#{$instructions}; $i++) {
push(@{$pcs}, $instructions->[$i]->[0]);
}
my $symbols = {};
MapToSymbols($lib, $offset, $pcs, $symbols);
for (my $i = 0; $i <= $#{$instructions}; $i++) {
my $e = $instructions->[$i];
push(@{$e}, $e->[1]);
push(@{$e}, $e->[2]);
my $addr = $e->[0];
my $sym = $symbols->{$addr};
if (defined($sym)) {
if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\d+)$/) {
$e->[1] = $1; $e->[2] = $2; }
}
}
}
sub PrintSource {
my $prog = shift;
my $offset = shift;
my $routine = shift;
my $flat = shift;
my $cumulative = shift;
my $start_addr = shift;
my $end_addr = shift;
my $html = shift;
my $output = shift;
my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
GetTopLevelLineNumbers($prog, $offset, \@instructions);
my $filename = undef;
for (my $i = 0; $i <= $#instructions; $i++) {
if ($instructions[$i]->[2] >= 0) {
$filename = $instructions[$i]->[1];
last;
}
}
if (!defined($filename)) {
print STDERR "no filename found in $routine\n";
return 0;
}
my $lastline = 0;
for (my $i = 0; $i <= $#instructions; $i++) {
my $f = $instructions[$i]->[1];
my $l = $instructions[$i]->[2];
if (($f eq $filename) && ($l > $lastline)) {
$lastline = $l;
}
}
my $firstline = 1;
for (my $i = 0; $i <= $#instructions; $i++) {
if ($instructions[$i]->[1] eq $filename) {
$firstline = $instructions[$i]->[2];
last;
}
}
my $oldlastline = $lastline;
{
if (!open(FILE, "<$filename")) {
print STDERR "$filename: $!\n";
return 0;
}
my $l = 0;
my $first_indentation = -1;
while (<FILE>) {
s/\r//g; $l++;
my $indent = Indentation($_);
if ($l >= $firstline) {
if ($first_indentation < 0 && $indent >= 0) {
$first_indentation = $indent;
last if ($first_indentation == 0);
}
}
if ($l >= $lastline && $indent >= 0) {
if ($indent >= $first_indentation) {
$lastline = $l+1;
} else {
last;
}
}
}
close(FILE);
}
my $samples1 = {}; my $samples2 = {}; my $running1 = 0; my $running2 = 0; my $total1 = 0; my $total2 = 0; my %disasm = (); my $running_disasm = ""; my $skip_marker = "---\n";
if ($html) {
$skip_marker = "";
for (my $l = $firstline; $l <= $lastline; $l++) {
$disasm{$l} = "";
}
}
my $last_dis_filename = '';
my $last_dis_linenum = -1;
my $last_touched_line = -1; foreach my $e (@instructions) {
my $c1 = 0;
my $c2 = 0;
for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
$c1 += GetEntry($flat, $a);
$c2 += GetEntry($cumulative, $a);
}
if ($html) {
my $dis = sprintf(" %6s %6s \t\t%8s: %s ",
HtmlPrintNumber($c1),
HtmlPrintNumber($c2),
UnparseAddress($offset, $e->[0]),
CleanDisassembly($e->[3]));
if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };
$dis = HtmlEscape($dis);
my $f = $e->[5];
my $l = $e->[6];
if ($f ne $last_dis_filename) {
$dis .= sprintf("<span class=disasmloc>%s:%d</span>",
HtmlEscape(CleanFileName($f)), $l);
} elsif ($l ne $last_dis_linenum) {
$dis .= sprintf("<span class=unimportant>%s</span>" .
"<span class=disasmloc>:%d</span>",
HtmlEscape(CleanFileName($f)), $l);
} else {
$dis .= sprintf("<span class=unimportant>%s:%d</span>",
HtmlEscape(CleanFileName($f)), $l);
}
$last_dis_filename = $f;
$last_dis_linenum = $l;
$running_disasm .= $dis;
$running_disasm .= "\n";
}
$running1 += $c1;
$running2 += $c2;
$total1 += $c1;
$total2 += $c2;
my $file = $e->[1];
my $line = $e->[2];
if (($file eq $filename) &&
($line >= $firstline) &&
($line <= $lastline)) {
AddEntry($samples1, $line, $running1);
AddEntry($samples2, $line, $running2);
$running1 = 0;
$running2 = 0;
if ($html) {
if ($line != $last_touched_line && $disasm{$line} ne '') {
$disasm{$line} .= "\n";
}
$disasm{$line} .= $running_disasm;
$running_disasm = '';
$last_touched_line = $line;
}
}
}
AddEntry($samples1, $lastline, $running1);
AddEntry($samples2, $lastline, $running2);
if ($html) {
if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {
$disasm{$lastline} .= "\n";
}
$disasm{$lastline} .= $running_disasm;
}
if ($html) {
printf $output (
"<h1>%s</h1>%s\n<pre onClick=\"jeprof_toggle_asm()\">\n" .
"Total:%6s %6s (flat / cumulative %s)\n",
HtmlEscape(ShortFunctionName($routine)),
HtmlEscape(CleanFileName($filename)),
Unparse($total1),
Unparse($total2),
Units());
} else {
printf $output (
"ROUTINE ====================== %s in %s\n" .
"%6s %6s Total %s (flat / cumulative)\n",
ShortFunctionName($routine),
CleanFileName($filename),
Unparse($total1),
Unparse($total2),
Units());
}
if (!open(FILE, "<$filename")) {
print STDERR "$filename: $!\n";
return 0;
}
my $l = 0;
while (<FILE>) {
s/\r//g; $l++;
if ($l >= $firstline - 5 &&
(($l <= $oldlastline + 5) || ($l <= $lastline))) {
chop;
my $text = $_;
if ($l == $firstline) { print $output $skip_marker; }
my $n1 = GetEntry($samples1, $l);
my $n2 = GetEntry($samples2, $l);
if ($html) {
my $dis = $disasm{$l};
my $asm = "";
if (defined($dis) && $dis ne '') {
$asm = "<span class=\"asm\">" . $dis . "</span>";
}
my $source_class = (($n1 + $n2 > 0)
? "livesrc"
: (($asm ne "") ? "deadsrc" : "nop"));
printf $output (
"<span class=\"line\">%5d</span> " .
"<span class=\"%s\">%6s %6s %s</span>%s\n",
$l, $source_class,
HtmlPrintNumber($n1),
HtmlPrintNumber($n2),
HtmlEscape($text),
$asm);
} else {
printf $output(
"%6s %6s %4d: %s\n",
UnparseAlt($n1),
UnparseAlt($n2),
$l,
$text);
}
if ($l == $lastline) { print $output $skip_marker; }
};
}
close(FILE);
if ($html) {
print $output "</pre>\n";
}
return 1;
}
sub SourceLine {
my $file = shift;
my $line = shift;
if (!defined($main::source_cache{$file})) {
if (100 < scalar keys(%main::source_cache)) {
$main::source_cache = ();
}
if (!open(FILE, "<$file")) {
print STDERR "$file: $!\n";
$main::source_cache{$file} = []; return undef;
}
my $lines = [];
push(@{$lines}, ""); while (<FILE>) {
push(@{$lines}, $_);
}
close(FILE);
$main::source_cache{$file} = $lines;
}
my $lines = $main::source_cache{$file};
if (($line < 0) || ($line > $#{$lines})) {
return undef;
} else {
return $lines->[$line];
}
}
sub PrintDisassembledFunction {
my $prog = shift;
my $offset = shift;
my $routine = shift;
my $flat = shift;
my $cumulative = shift;
my $start_addr = shift;
my $end_addr = shift;
my $total = shift;
my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
my @flat_count = ();
my @cum_count = ();
my $flat_total = 0;
my $cum_total = 0;
foreach my $e (@instructions) {
my $c1 = 0;
my $c2 = 0;
for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
$c1 += GetEntry($flat, $a);
$c2 += GetEntry($cumulative, $a);
}
push(@flat_count, $c1);
push(@cum_count, $c2);
$flat_total += $c1;
$cum_total += $c2;
}
printf("ROUTINE ====================== %s\n" .
"%6s %6s %s (flat, cumulative) %.1f%% of total\n",
ShortFunctionName($routine),
Unparse($flat_total),
Unparse($cum_total),
Units(),
($cum_total * 100.0) / $total);
my $current_file = "";
for (my $i = 0; $i <= $#instructions; ) {
my $e = $instructions[$i];
if ($e->[1] ne $current_file) {
$current_file = $e->[1];
my $fname = $current_file;
$fname =~ s|^\./||;
if (length($fname) >= 58) {
$fname = "..." . substr($fname, -55);
}
printf("-------------------- %s\n", $fname);
}
my $first_line = $e->[2];
my $last_line = $first_line;
my %flat_sum = ();
my %cum_sum = ();
for (my $l = $first_line; $l <= $last_line; $l++) {
$flat_sum{$l} = 0;
$cum_sum{$l} = 0;
}
my $first_inst = $i;
while (($i <= $#instructions) &&
($instructions[$i]->[2] >= $first_line) &&
($instructions[$i]->[2] <= $last_line)) {
$e = $instructions[$i];
$flat_sum{$e->[2]} += $flat_count[$i];
$cum_sum{$e->[2]} += $cum_count[$i];
$i++;
}
my $last_inst = $i - 1;
for (my $l = $first_line; $l <= $last_line; $l++) {
my $line = SourceLine($current_file, $l);
if (!defined($line)) {
$line = "?\n";
next;
} else {
$line =~ s/^\s+//;
}
printf("%6s %6s %5d: %s",
UnparseAlt($flat_sum{$l}),
UnparseAlt($cum_sum{$l}),
$l,
$line);
}
for (my $x = $first_inst; $x <= $last_inst; $x++) {
my $e = $instructions[$x];
printf("%6s %6s %8s: %6s\n",
UnparseAlt($flat_count[$x]),
UnparseAlt($cum_count[$x]),
UnparseAddress($offset, $e->[0]),
CleanDisassembly($e->[3]));
}
}
}
sub PrintDot {
my $prog = shift;
my $symbols = shift;
my $raw = shift;
my $flat = shift;
my $cumulative = shift;
my $overall_total = shift;
my $local_total = TotalProfile($flat);
my $nodelimit = int($main::opt_nodefraction * $local_total);
my $edgelimit = int($main::opt_edgefraction * $local_total);
my $nodecount = $main::opt_nodecount;
my @list = (sort { abs(GetEntry($cumulative, $b)) <=>
abs(GetEntry($cumulative, $a))
|| $a cmp $b }
keys(%{$cumulative}));
my $last = $nodecount - 1;
if ($last > $#list) {
$last = $#list;
}
while (($last >= 0) &&
(abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {
$last--;
}
if ($last < 0) {
print STDERR "No nodes to print\n";
return 0;
}
if ($nodelimit > 0 || $edgelimit > 0) {
printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n",
Unparse($nodelimit), Units(),
Unparse($edgelimit), Units());
}
my $output;
my $escaped_dot = ShellEscape(@DOT);
my $escaped_ps2pdf = ShellEscape(@PS2PDF);
if ($main::opt_gv) {
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps"));
$output = "| $escaped_dot -Tps2 >$escaped_outfile";
} elsif ($main::opt_evince) {
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf"));
$output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile";
} elsif ($main::opt_ps) {
$output = "| $escaped_dot -Tps2";
} elsif ($main::opt_pdf) {
$output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -";
} elsif ($main::opt_web || $main::opt_svg) {
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg"));
$output = "| $escaped_dot -Tsvg >$escaped_outfile";
} elsif ($main::opt_gif) {
$output = "| $escaped_dot -Tgif";
} else {
$output = ">&STDOUT";
}
open(DOT, $output) || error("$output: $!\n");
printf DOT ("digraph \"%s; %s %s\" {\n",
$prog,
Unparse($overall_total),
Units());
if ($main::opt_pdf) {
printf DOT ("size=\"8,11\"\n");
}
printf DOT ("node [width=0.375,height=0.25];\n");
printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," .
"label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n",
$prog,
sprintf("Total %s: %s", Units(), Unparse($overall_total)),
sprintf("Focusing on: %s", Unparse($local_total)),
sprintf("Dropped nodes with <= %s abs(%s)",
Unparse($nodelimit), Units()),
sprintf("Dropped edges with <= %s %s",
Unparse($edgelimit), Units())
);
my %node = ();
my $nextnode = 1;
foreach my $a (@list[0..$last]) {
my $f = GetEntry($flat, $a);
my $c = GetEntry($cumulative, $a);
my $fs = 8;
if ($local_total > 0) {
$fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));
}
$node{$a} = $nextnode++;
my $sym = $a;
$sym =~ s/\s+/\\n/g;
$sym =~ s/::/\\n/g;
my $extra = "";
if ($f != $c) {
$extra = sprintf("\\rof %s (%s)",
Unparse($c),
Percent($c, $local_total));
}
my $style = "";
if ($main::opt_heapcheck) {
if ($f > 0) {
$style = ",style=filled,fillcolor=gray"
} elsif ($f < 0) {
$style = ",peripheries=3"
}
}
printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
"\",shape=box,fontsize=%.1f%s];\n",
$node{$a},
$sym,
Unparse($f),
Percent($f, $local_total),
$extra,
$fs,
$style,
);
}
my %edge = ();
my $n;
my $fullname_to_shortname_map = {};
FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
foreach my $k (keys(%{$raw})) {
$n = $raw->{$k};
my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
for (my $i = 1; $i <= $#translated; $i++) {
my $src = $translated[$i];
my $dst = $translated[$i-1];
if (exists($node{$src}) && exists($node{$dst})) {
my $edge_label = "$src\001$dst";
if (!exists($edge{$edge_label})) {
$edge{$edge_label} = 0;
}
$edge{$edge_label} += $n;
}
}
}
my %indegree = (); my %outdegree = (); foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
my @x = split(/\001/, $e);
$n = $edge{$e};
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 = 1;
} elsif (abs($n) <= $edgelimit) {
$keep = 0;
} elsif ($outdegree{$src} >= $main::opt_maxdegree ||
$indegree{$dst} >= $main::opt_maxdegree) {
$keep = 0;
} else {
$keep = 1;
}
if ($keep) {
$outdegree{$src}++;
$indegree{$dst}++;
my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
if ($fraction > 1) { $fraction = 1; }
my $w = $fraction * 2;
if ($w < 1 && ($main::opt_web || $main::opt_svg)) {
$w = 1;
}
my $edgeweight = abs($n) ** 0.7;
if ($edgeweight > 100000) { $edgeweight = 100000; }
$edgeweight = int($edgeweight);
my $style = sprintf("setlinewidth(%f)", $w);
if ($x[1] =~ m/\(inline\)/) {
$style .= ",dashed";
}
printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n",
$node{$x[0]},
$node{$x[1]},
Unparse($n),
$edgeweight,
$style);
}
}
print DOT ("}\n");
close(DOT);
if ($main::opt_web || $main::opt_svg) {
RewriteSvg(TempName($main::next_tmpfile, "svg"));
}
return 1;
}
sub RewriteSvg {
my $svgfile = shift;
open(SVG, $svgfile) || die "open temp svg: $!";
my @svg = <SVG>;
close(SVG);
unlink $svgfile;
my $svg = join('', @svg);
$svg =~ s/(?s)<svg width="[^"]+" height="[^"]+"(.*?)viewBox="[^"]+"/<svg width="100%" height="100%"$1/;
my $svg_javascript = SvgJavascript();
my $viewport = "<g id=\"viewport\" transform=\"translate(0,0)\">\n";
$svg =~ s/<g id="graph\d"/$svg_javascript$viewport$&/;
$svg =~ s/(.*)(<\/svg>)/$1<\/g>$2/;
$svg =~ s/<g id="graph\d"(.*?)/<g id="viewport"$1/;
if ($main::opt_svg) {
print $svg;
} else {
open(SVG, ">$svgfile") || die "open $svgfile: $!";
print SVG $svg;
close(SVG);
}
}
sub SvgJavascript {
return <<'EOF';
<script type="text/ecmascript"><![CDATA[
// SVGPan
// http://www.cyberz.org/blog/2009/12/08/svgpan-a-javascript-svg-panzoomdrag-library/
// Local modification: if(true || ...) below to force panning, never moving.
/**
* SVGPan library 1.2
* ====================
*
* Given an unique existing element with id "viewport", including the
* the library into any SVG adds the following capabilities:
*
* - Mouse panning
* - Mouse zooming (using the wheel)
* - Object dargging
*
* Known issues:
*
* - Zooming (while panning) on Safari has still some issues
*
* Releases:
*
* 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
* Fixed a bug with browser mouse handler interaction
*
* 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui
* Updated the zoom code to support the mouse wheel on Safari/Chrome
*
* 1.0, Andrea Leofreddi
* First release
*
* This code is licensed under the following BSD license:
*
* Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Andrea Leofreddi.
*/
var root = document.documentElement;
var state = 'none', stateTarget, stateOrigin, stateTf;
setupHandlers(root);
/**
* Register handlers
*/
function setupHandlers(root){
setAttributes(root, {
"onmouseup" : "add(evt)",
"onmousedown" : "handleMouseDown(evt)",
"onmousemove" : "handleMouseMove(evt)",
"onmouseup" : "handleMouseUp(evt)",
//"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
});
if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
else
window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
var g = svgDoc.getElementById("svg");
g.width = "100%";
g.height = "100%";
}
/**
* Instance an SVGPoint object with given event coordinates.
*/
function getEventPoint(evt) {
var p = root.createSVGPoint();
p.x = evt.clientX;
p.y = evt.clientY;
return p;
}
/**
* Sets the current transform matrix of an element.
*/
function setCTM(element, matrix) {
var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
element.setAttribute("transform", s);
}
/**
* Dumps a matrix to a string (useful for debug).
*/
function dumpMatrix(matrix) {
var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]";
return s;
}
/**
* Sets attributes of an element.
*/
function setAttributes(element, attributes){
for (i in attributes)
element.setAttributeNS(null, i, attributes[i]);
}
/**
* Handle mouse move event.
*/
function handleMouseWheel(evt) {
if(evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
var delta;
if(evt.wheelDelta)
delta = evt.wheelDelta / 3600; // Chrome/Safari
else
delta = evt.detail / -90; // Mozilla
var z = 1 + delta; // Zoom factor: 0.9/1.1
var g = svgDoc.getElementById("viewport");
var p = getEventPoint(evt);
p = p.matrixTransform(g.getCTM().inverse());
// Compute new scale matrix in current mouse position
var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
setCTM(g, g.getCTM().multiply(k));
stateTf = stateTf.multiply(k.inverse());
}
/**
* Handle mouse move event.
*/
function handleMouseMove(evt) {
if(evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
var g = svgDoc.getElementById("viewport");
if(state == 'pan') {
// Pan mode
var p = getEventPoint(evt).matrixTransform(stateTf);
setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
} else if(state == 'move') {
// Move mode
var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
stateOrigin = p;
}
}
/**
* Handle click event.
*/
function handleMouseDown(evt) {
if(evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
var g = svgDoc.getElementById("viewport");
if(true || evt.target.tagName == "svg") {
// Pan mode
state = 'pan';
stateTf = g.getCTM().inverse();
stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
} else {
// Move mode
state = 'move';
stateTarget = evt.target;
stateTf = g.getCTM().inverse();
stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
}
}
/**
* Handle mouse button release event.
*/
function handleMouseUp(evt) {
if(evt.preventDefault)
evt.preventDefault();
evt.returnValue = false;
var svgDoc = evt.target.ownerDocument;
if(state == 'pan' || state == 'move') {
// Quit pan mode
state = '';
}
}
]]></script>
EOF
}
sub FillFullnameToShortnameMap {
my $symbols = shift;
my $fullname_to_shortname_map = shift;
my $shortnames_seen_once = {};
my $shortnames_seen_more_than_once = {};
foreach my $symlist (values(%{$symbols})) {
my $shortname = $symlist->[0];
my $fullname = $symlist->[2];
if ($fullname !~ /<[0-9a-fA-F]+>$/) { next; }
if (defined($shortnames_seen_once->{$shortname}) &&
$shortnames_seen_once->{$shortname} ne $fullname) {
$shortnames_seen_more_than_once->{$shortname} = 1;
} else {
$shortnames_seen_once->{$shortname} = $fullname;
}
}
foreach my $symlist (values(%{$symbols})) {
my $shortname = $symlist->[0];
my $fullname = $symlist->[2];
next if defined($fullname_to_shortname_map->{$fullname});
if (defined($shortnames_seen_more_than_once->{$shortname})) {
if ($fullname =~ /<0*([^>]*)>$/) { $fullname_to_shortname_map->{$fullname} = "$shortname\@$1";
}
}
}
}
sub ShortIdFor {
my $key = shift;
my $id = $main::uniqueid{$key};
if (!defined($id)) {
$id = keys(%main::uniqueid) + 1;
$main::uniqueid{$key} = $id;
}
return $id;
}
sub TranslateStack {
my $symbols = shift;
my $fullname_to_shortname_map = shift;
my $k = shift;
my @addrs = split(/\n/, $k);
my @result = ();
for (my $i = 0; $i <= $#addrs; $i++) {
my $a = $addrs[$i];
if (length($a) > 8 && $a gt "7fffffffffffffff") {
next;
}
if ($main::opt_disasm || $main::opt_list) {
push(@result, $a);
next;
}
my $symlist = $symbols->{$a};
if (!defined($symlist)) {
$symlist = [$a, "", $a];
}
for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {
my $func = $symlist->[$j-2];
my $fileline = $symlist->[$j-1];
my $fullfunc = $symlist->[$j];
if (defined($fullname_to_shortname_map->{$fullfunc})) {
$func = $fullname_to_shortname_map->{$fullfunc};
}
if ($j > 2) {
$func = "$func (inline)";
}
if ($func =~ m/Callback.*::Run$/) {
my $caller = ($i > 0) ? $addrs[$i-1] : 0;
$func = "Run#" . ShortIdFor($caller);
}
if ($main::opt_addresses) {
push(@result, "$a $func $fileline");
} elsif ($main::opt_lines) {
if ($func eq '??' && $fileline eq '??:0') {
push(@result, "$a");
} else {
push(@result, "$func $fileline");
}
} elsif ($main::opt_functions) {
if ($func eq '??') {
push(@result, "$a");
} else {
push(@result, $func);
}
} elsif ($main::opt_files) {
if ($fileline eq '??:0' || $fileline eq '') {
push(@result, "$a");
} else {
my $f = $fileline;
$f =~ s/:\d+$//;
push(@result, $f);
}
} else {
push(@result, $a);
last; }
}
}
return @result;
}
sub Percent {
my $num = shift;
my $tot = shift;
if ($tot != 0) {
return sprintf("%.1f%%", $num * 100.0 / $tot);
} else {
return ($num == 0) ? "nan" : (($num > 0) ? "+inf" : "-inf");
}
}
sub Unparse {
my $num = shift;
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
return sprintf("%d", $num);
} else {
if ($main::opt_show_bytes) {
return sprintf("%d", $num);
} else {
return sprintf("%.1f", $num / 1048576.0);
}
}
} elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
return sprintf("%.3f", $num / 1e9); } else {
return sprintf("%d", $num);
}
}
sub UnparseAlt {
my $num = shift;
if ($num == 0) {
return ".";
} else {
return Unparse($num);
}
}
sub HtmlPrintNumber {
my $num = shift;
if ($num == 0) {
return "";
} else {
return Unparse($num);
}
}
sub Units {
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
return "objects";
} else {
if ($main::opt_show_bytes) {
return "B";
} else {
return "MB";
}
}
} elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
return "seconds";
} else {
return "samples";
}
}
sub FlatProfile {
my $profile = shift;
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
if ($#addrs >= 0) {
AddEntry($result, $addrs[0], $count);
}
}
return $result;
}
sub CumulativeProfile {
my $profile = shift;
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
foreach my $a (@addrs) {
AddEntry($result, $a, $count);
}
}
return $result;
}
sub IsSecondPcAlwaysTheSame {
my $profile = shift;
my $second_pc = undef;
foreach my $k (keys(%{$profile})) {
my @addrs = split(/\n/, $k);
if ($#addrs < 1) {
return undef;
}
if (not defined $second_pc) {
$second_pc = $addrs[1];
} else {
if ($second_pc ne $addrs[1]) {
return undef;
}
}
}
return $second_pc;
}
sub ExtractSymbolNameInlineStack {
my $symbols = shift;
my $address = shift;
my @stack = ();
if (exists $symbols->{$address}) {
my @localinlinestack = @{$symbols->{$address}};
for (my $i = $#localinlinestack; $i > 0; $i-=3) {
my $file = $localinlinestack[$i-1];
my $fn = $localinlinestack[$i-0];
if ($file eq "?" || $file eq ":0") {
$file = "??:0";
}
if ($fn eq '??') {
$fn = $file;
}
my $suffix = "[inline]";
if ($i == 2) {
$suffix = "";
}
push (@stack, $fn.$suffix);
}
}
else {
push (@stack, $address);
}
return @stack;
}
sub ExtractSymbolLocation {
my $symbols = shift;
my $address = shift;
my $location = "??:0:unknown";
if (exists $symbols->{$address}) {
my $file = $symbols->{$address}->[1];
if ($file eq "?") {
$file = "??:0"
}
$location = $file . ":" . $symbols->{$address}->[0];
}
return $location;
}
sub ExtractCalls {
my $symbols = shift;
my $profile = shift;
my $calls = {};
while( my ($stack_trace, $count) = each %$profile ) {
my @address = split(/\n/, $stack_trace);
my $destination = ExtractSymbolLocation($symbols, $address[0]);
AddEntry($calls, $destination, $count);
for (my $i = 1; $i <= $#address; $i++) {
my $source = ExtractSymbolLocation($symbols, $address[$i]);
my $call = "$source -> $destination";
AddEntry($calls, $call, $count);
$destination = $source;
}
}
return $calls;
}
sub FilterFrames {
my $symbols = shift;
my $profile = shift;
if ($main::opt_retain eq '' && $main::opt_exclude eq '') {
return $profile;
}
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
my @path = ();
foreach my $a (@addrs) {
my $sym;
if (exists($symbols->{$a})) {
$sym = $symbols->{$a}->[0];
} else {
$sym = $a;
}
if ($main::opt_retain ne '' && $sym !~ m/$main::opt_retain/) {
next;
}
if ($main::opt_exclude ne '' && $sym =~ m/$main::opt_exclude/) {
next;
}
push(@path, $a);
}
if (scalar(@path) > 0) {
my $reduced_path = join("\n", @path);
AddEntry($result, $reduced_path, $count);
}
}
return $result;
}
sub PrintCollapsedStacks {
my $symbols = shift;
my $profile = shift;
while (my ($stack_trace, $count) = each %$profile) {
my @address = split(/\n/, $stack_trace);
my @names = reverse ( map { ExtractSymbolNameInlineStack($symbols, $_) } @address );
printf("%s %d\n", join(";", @names), $count);
}
}
sub RemoveUninterestingFrames {
my $symbols = shift;
my $profile = shift;
my %skip = ();
my $skip_regexp = 'NOMATCH';
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
foreach my $name ('@JEMALLOC_PREFIX@calloc',
'cfree',
'@JEMALLOC_PREFIX@malloc',
'newImpl',
'void* newImpl',
'@JEMALLOC_PREFIX@free',
'@JEMALLOC_PREFIX@memalign',
'@JEMALLOC_PREFIX@posix_memalign',
'@JEMALLOC_PREFIX@aligned_alloc',
'pvalloc',
'@JEMALLOC_PREFIX@valloc',
'@JEMALLOC_PREFIX@realloc',
'@JEMALLOC_PREFIX@mallocx',
'@JEMALLOC_PREFIX@rallocx',
'@JEMALLOC_PREFIX@xallocx',
'@JEMALLOC_PREFIX@dallocx',
'@JEMALLOC_PREFIX@sdallocx',
'@JEMALLOC_PREFIX@sdallocx_noflags',
'tc_calloc',
'tc_cfree',
'tc_malloc',
'tc_free',
'tc_memalign',
'tc_posix_memalign',
'tc_pvalloc',
'tc_valloc',
'tc_realloc',
'tc_new',
'tc_delete',
'tc_newarray',
'tc_deletearray',
'tc_new_nothrow',
'tc_newarray_nothrow',
'do_malloc',
'::do_malloc', '::do_malloc_or_cpp_alloc',
'DoSampledAllocation',
'simple_alloc::allocate',
'__malloc_alloc_template::allocate',
'__builtin_delete',
'__builtin_new',
'__builtin_vec_delete',
'__builtin_vec_new',
'operator new',
'operator new[]',
'malloc_zone_malloc',
'malloc_zone_calloc',
'malloc_zone_valloc',
'malloc_zone_realloc',
'malloc_zone_memalign',
'malloc_zone_free',
'__start_google_malloc',
'__stop_google_malloc',
'__start_malloc_hook',
'__stop_malloc_hook') {
$skip{$name} = 1;
$skip{"_" . $name} = 1; }
$skip_regexp = "TCMalloc|^tcmalloc::";
} elsif ($main::profile_type eq 'contention') {
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;
}
} elsif ($main::profile_type eq 'cpu') {
foreach my $name ('ProfileData::Add', 'ProfileData::prof_handler', 'CpuProfiler::prof_handler',
'__FRAME_END__',
'__pthread_sighandler',
'__restore') {
$skip{$name} = 1;
}
} else {
}
if ($main::profile_type eq 'cpu') {
while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {
my $result = {};
my $func = '';
if (exists($symbols->{$second_pc})) {
$second_pc = $symbols->{$second_pc}->[0];
}
print STDERR "Removing $second_pc from all stack traces.\n";
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
splice @addrs, 1, 1;
my $reduced_path = join("\n", @addrs);
AddEntry($result, $reduced_path, $count);
}
$profile = $result;
}
}
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
my @path = ();
foreach my $a (@addrs) {
if (exists($symbols->{$a})) {
my $func = $symbols->{$a}->[0];
if ($skip{$func} || ($func =~ m/$skip_regexp/)) {
@path = ();
next;
}
}
push(@path, $a);
}
my $reduced_path = join("\n", @path);
AddEntry($result, $reduced_path, $count);
}
$result = FilterFrames($symbols, $result);
return $result;
}
sub ReduceProfile {
my $symbols = shift;
my $profile = shift;
my $result = {};
my $fullname_to_shortname_map = {};
FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
my @path = ();
my %seen = ();
$seen{''} = 1; foreach my $e (@translated) {
if (!$seen{$e}) {
$seen{$e} = 1;
push(@path, $e);
}
}
my $reduced_path = join("\n", @path);
AddEntry($result, $reduced_path, $count);
}
return $result;
}
sub SymbolMatches {
my $sym = shift;
my $re = shift;
if (defined($sym)) {
for (my $i = 0; $i < $#{$sym}; $i += 3) {
if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {
return 1;
}
}
}
return 0;
}
sub FocusProfile {
my $symbols = shift;
my $profile = shift;
my $focus = shift;
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
foreach my $a (@addrs) {
if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {
AddEntry($result, $k, $count);
last;
}
}
}
return $result;
}
sub IgnoreProfile {
my $symbols = shift;
my $profile = shift;
my $ignore = shift;
my $result = {};
foreach my $k (keys(%{$profile})) {
my $count = $profile->{$k};
my @addrs = split(/\n/, $k);
my $matched = 0;
foreach my $a (@addrs) {
if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {
$matched = 1;
last;
}
}
if (!$matched) {
AddEntry($result, $k, $count);
}
}
return $result;
}
sub TotalProfile {
my $profile = shift;
my $result = 0;
foreach my $k (keys(%{$profile})) {
$result += $profile->{$k};
}
return $result;
}
sub AddProfile {
my $A = shift;
my $B = shift;
my $R = {};
foreach my $k (keys(%{$A})) {
my $v = $A->{$k};
AddEntry($R, $k, $v);
}
foreach my $k (keys(%{$B})) {
my $v = $B->{$k};
AddEntry($R, $k, $v);
}
return $R;
}
sub MergeSymbols {
my $A = shift;
my $B = shift;
my $R = {};
foreach my $k (keys(%{$A})) {
$R->{$k} = $A->{$k};
}
if (defined($B)) {
foreach my $k (keys(%{$B})) {
$R->{$k} = $B->{$k};
}
}
return $R;
}
sub AddPcs {
my $A = shift;
my $B = shift;
my $R = {};
foreach my $k (keys(%{$A})) {
$R->{$k} = 1
}
foreach my $k (keys(%{$B})) {
$R->{$k} = 1
}
return $R;
}
sub SubtractProfile {
my $A = shift;
my $B = shift;
my $R = {};
foreach my $k (keys(%{$A})) {
my $v = $A->{$k} - GetEntry($B, $k);
if ($v < 0 && $main::opt_drop_negative) {
$v = 0;
}
AddEntry($R, $k, $v);
}
if (!$main::opt_drop_negative) {
foreach my $k (keys(%{$B})) {
if (!exists($A->{$k})) {
AddEntry($R, $k, 0 - $B->{$k});
}
}
}
return $R;
}
sub GetEntry {
my $profile = shift;
my $k = shift;
if (exists($profile->{$k})) {
return $profile->{$k};
} else {
return 0;
}
}
sub AddEntry {
my $profile = shift;
my $k = shift;
my $n = shift;
if (!exists($profile->{$k})) {
$profile->{$k} = 0;
}
$profile->{$k} += $n;
}
sub AddEntries {
my $profile = shift;
my $pcs = shift;
my $stack = shift;
my $count = shift;
my @k = ();
foreach my $e (split(/\s+/, $stack)) {
my $pc = HexExtend($e);
$pcs->{$pc} = 1;
push @k, $pc;
}
AddEntry($profile, (join "\n", @k), $count);
}
sub CheckSymbolPage {
my $url = SymbolPageURL();
my $command = ShellEscape(@URL_FETCHER, $url);
open(SYMBOL, "$command |") or error($command);
my $line = <SYMBOL>;
$line =~ s/\r//g; close(SYMBOL);
unless (defined($line)) {
error("$url doesn't exist\n");
}
if ($line =~ /^num_symbols:\s+(\d+)$/) {
if ($1 == 0) {
error("Stripped binary. No symbols available.\n");
}
} else {
error("Failed to get the number of symbols from $url\n");
}
}
sub IsProfileURL {
my $profile_name = shift;
if (-f $profile_name) {
printf STDERR "Using local file $profile_name.\n";
return 0;
}
return 1;
}
sub ParseProfileURL {
my $profile_name = shift;
if (!defined($profile_name) || $profile_name eq "") {
return ();
}
$profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;
my $proto = $1 || "http://";
my $hostport = $2;
my $prefix = $3;
my $profile = $4 || "/";
my $host = $hostport;
$host =~ s/:.*//;
my $baseurl = "$proto$hostport$prefix";
return ($host, $baseurl, $profile);
}
sub SymbolPageURL {
my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
return "$baseURL$SYMBOL_PAGE";
}
sub FetchProgramName() {
my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
my $url = "$baseURL$PROGRAM_NAME_PAGE";
my $command_line = ShellEscape(@URL_FETCHER, $url);
open(CMDLINE, "$command_line |") or error($command_line);
my $cmdline = <CMDLINE>;
$cmdline =~ s/\r//g; close(CMDLINE);
error("Failed to get program name from $url\n") unless defined($cmdline);
$cmdline =~ s/\x00.+//; $cmdline =~ s!\n!!g; return $cmdline;
}
sub ResolveRedirectionForCurl {
my $url = shift;
my $command_line = ShellEscape(@URL_FETCHER, "--head", $url);
open(CMDLINE, "$command_line |") or error($command_line);
while (<CMDLINE>) {
s/\r//g; if (/^Location: (.*)/) {
$url = $1;
}
}
close(CMDLINE);
return $url;
}
sub AddFetchTimeout {
my $timeout = shift;
my @fetcher = @_;
if (defined($timeout)) {
if (join(" ", @fetcher) =~ m/\bcurl -s/) {
push(@fetcher, "--max-time", sprintf("%d", $timeout));
} elsif (join(" ", @fetcher) =~ m/\brpcget\b/) {
push(@fetcher, sprintf("--deadline=%d", $timeout));
}
}
return @fetcher;
}
sub ReadSymbols {
my $in = shift;
my $map = {};
while (<$in>) {
s/\r//g; if (m/^0x0*([0-9a-f]+)\s+(.+)/) {
$map->{$1} = $2;
} elsif (m/^---/) {
last;
} elsif (m/^([a-z][^=]*)=(.*)$/ ) {
my ($variable, $value) = ($1, $2);
for ($variable, $value) {
s/^\s+//;
s/\s+$//;
}
if ($variable eq "binary") {
if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {
printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n",
$main::prog, $value);
}
$main::prog = $value;
} else {
printf STDERR ("Ignoring unknown variable in symbols list: " .
"'%s' = '%s'\n", $variable, $value);
}
}
}
return $map;
}
sub URLEncode {
my $str = shift;
$str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg;
return $str;
}
sub AppendSymbolFilterParams {
my $url = shift;
my @params = ();
if ($main::opt_retain ne '') {
push(@params, sprintf("retain=%s", URLEncode($main::opt_retain)));
}
if ($main::opt_exclude ne '') {
push(@params, sprintf("exclude=%s", URLEncode($main::opt_exclude)));
}
if (scalar @params > 0) {
$url = sprintf("%s?%s", $url, join("&", @params));
}
return $url;
}
sub FetchSymbols {
my $pcset = shift;
my $symbol_map = shift;
my %seen = ();
my @pcs = grep { !$seen{$_}++ } keys(%$pcset);
if (!defined($symbol_map)) {
my $post_data = join("+", sort((map {"0x" . "$_"} @pcs)));
open(POSTFILE, ">$main::tmpfile_sym");
print POSTFILE $post_data;
close(POSTFILE);
my $url = SymbolPageURL();
my $command_line;
if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) {
$url = ResolveRedirectionForCurl($url);
$url = AppendSymbolFilterParams($url);
$command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym",
$url);
} else {
$url = AppendSymbolFilterParams($url);
$command_line = (ShellEscape(@URL_FETCHER, "--post", $url)
. " < " . ShellEscape($main::tmpfile_sym));
}
my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"});
open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line);
$symbol_map = ReadSymbols(*SYMBOL{IO});
close(SYMBOL);
}
my $symbols = {};
foreach my $pc (@pcs) {
my $fullname;
my $shortpc = $pc;
$shortpc =~ s/^0*//;
my $fullnames;
if (defined($symbol_map->{$shortpc})) {
$fullnames = $symbol_map->{$shortpc};
} else {
$fullnames = "0x" . $pc; }
my $sym = [];
$symbols->{$pc} = $sym;
foreach my $fullname (split("--", $fullnames)) {
my $name = ShortFunctionName($fullname);
push(@{$sym}, $name, "?", $fullname);
}
}
return $symbols;
}
sub BaseName {
my $file_name = shift;
$file_name =~ s!^.*/!!; return $file_name;
}
sub MakeProfileBaseName {
my ($binary_name, $profile_name) = @_;
my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
my $binary_shortname = BaseName($binary_name);
return sprintf("%s.%s.%s",
$binary_shortname, $main::op_time, $host);
}
sub FetchDynamicProfile {
my $binary_name = shift;
my $profile_name = shift;
my $fetch_name_only = shift;
my $encourage_patience = shift;
if (!IsProfileURL($profile_name)) {
return $profile_name;
} else {
my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
if ($path eq "" || $path eq "/") {
$path = $PROFILE_PAGE;
}
my $profile_file = MakeProfileBaseName($binary_name, $profile_name);
my $url = "$baseURL$path";
my $fetch_timeout = undef;
if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {
if ($path =~ m/[?]/) {
$url .= "&";
} else {
$url .= "?";
}
$url .= sprintf("seconds=%d", $main::opt_seconds);
$fetch_timeout = $main::opt_seconds * 1.01 + 60;
$main::profile_type = 'cpu';
} else {
my $suffix = $path;
$suffix =~ s,/,.,g;
$profile_file .= $suffix;
if ($path =~ m/$HEAP_PAGE/) {
$main::profile_type = 'heap';
} elsif ($path =~ m/$GROWTH_PAGE/) {
$main::profile_type = 'growth';
} elsif ($path =~ m/$CONTENTION_PAGE/) {
$main::profile_type = 'contention';
}
}
my $profile_dir = $ENV{"JEPROF_TMPDIR"} || ($ENV{HOME} . "/jeprof");
if (! -d $profile_dir) {
mkdir($profile_dir)
|| die("Unable to create profile directory $profile_dir: $!\n");
}
my $tmp_profile = "$profile_dir/.tmp.$profile_file";
my $real_profile = "$profile_dir/$profile_file";
if ($fetch_name_only > 0) {
return $real_profile;
}
my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);
my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile);
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";
if ($encourage_patience) {
print STDERR "Be patient...\n";
}
} else {
print STDERR "Fetching $path profile from $url to\n ${real_profile}\n";
}
(system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
(system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n");
print STDERR "Wrote profile to $real_profile\n";
$main::collected_profile = $real_profile;
return $main::collected_profile;
}
}
sub FetchDynamicProfiles {
my $items = scalar(@main::pfile_args);
my $levels = log($items) / log(2);
if ($items == 1) {
$main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1);
} else {
if ((2 ** $levels) < $items) {
$levels++;
}
my $count = scalar(@main::pfile_args);
for (my $i = 0; $i < $count; $i++) {
$main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0);
}
print STDERR "Fetching $count profiles, Be patient...\n";
FetchDynamicProfilesRecurse($levels, 0, 0);
$main::collected_profile = join(" \\\n ", @main::profile_files);
}
}
sub FetchDynamicProfilesRecurse {
my $maxlevel = shift;
my $level = shift;
my $position = shift;
if (my $pid = fork()) {
$position = 0 | ($position << 1);
TryCollectProfile($maxlevel, $level, $position);
wait;
} else {
$position = 1 | ($position << 1);
TryCollectProfile($maxlevel, $level, $position);
cleanup();
exit(0);
}
}
sub TryCollectProfile {
my $maxlevel = shift;
my $level = shift;
my $position = shift;
if ($level >= ($maxlevel - 1)) {
if ($position < scalar(@main::pfile_args)) {
FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);
}
} else {
FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);
}
}
BEGIN {
package CpuProfileStream;
sub new {
my ($class, $file, $fname) = @_;
my $self = { file => $file,
base => 0,
stride => 512 * 1024, slots => [],
unpack_code => "", perl_is_64bit => 1, };
bless $self, $class;
if ($main::opt_test_stride > 0) {
$self->{stride} = $main::opt_test_stride;
}
my $slots = $self->{slots};
my $str;
read($self->{file}, $str, 8);
$address_length = ($str eq (chr(0)x8)) ? 16 : 8;
if ($address_length == 8) {
if (substr($str, 6, 2) eq chr(0)x2) {
$self->{unpack_code} = 'V'; } elsif (substr($str, 4, 2) eq chr(0)x2) {
$self->{unpack_code} = 'N'; } else {
::error("$fname: header size >= 2**16\n");
}
@$slots = unpack($self->{unpack_code} . "*", $str);
} else {
my $has_q = 0;
eval { $has_q = pack("Q", "1") ? 1 : 1; };
if (!$has_q) {
$self->{perl_is_64bit} = 0;
}
read($self->{file}, $str, 8);
if (substr($str, 4, 4) eq chr(0)x4) {
$self->{unpack_code} = 'V'; } elsif (substr($str, 0, 4) eq chr(0)x4) {
$self->{unpack_code} = 'N'; } else {
::error("$fname: header size >= 2**32\n");
}
my @pair = unpack($self->{unpack_code} . "*", $str);
@$slots = (0, $pair[0] + $pair[1]);
}
return $self;
}
sub overflow {
my ($self) = @_;
my $slots = $self->{slots};
$self->{base} += $#$slots + 1; my $str;
read($self->{file}, $str, $self->{stride});
if ($address_length == 8) { @$slots = unpack($self->{unpack_code} . "*", $str);
} else {
my @b32_values = unpack($self->{unpack_code} . "*", $str);
my @b64_values = ();
for (my $i = 0; $i < $#b32_values; $i += 2) {
my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);
if ($self->{unpack_code} eq 'N') { ($lo, $hi) = ($hi, $lo);
}
my $value = $lo + $hi * (2**32);
if (!$self->{perl_is_64bit} && (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {
::error("Need a 64-bit perl to process this 64-bit profile.\n");
}
push(@b64_values, $value);
}
@$slots = @b64_values;
}
}
sub get {
my ($self, $idx) = @_;
my $slots = $self->{slots};
while ($#$slots >= 0) {
if ($idx < $self->{base}) {
print STDERR "Unexpected look-back reading CPU profile";
return -1; } elsif ($idx > $self->{base} + $#$slots) {
$self->overflow();
} else {
return $slots->[$idx - $self->{base}];
}
}
return -1; }
}
sub ReadProfileHeader {
local *PROFILE = shift;
my $firstchar = "";
my $line = "";
read(PROFILE, $firstchar, 1);
seek(PROFILE, -1, 1); if ($firstchar !~ /[[:print:]]/) { return "";
}
while (defined($line = <PROFILE>)) {
$line =~ s/\r//g; if ($line =~ /^%warn\s+(.*)/) { print STDERR "WARNING: $1\n"; } elsif ($line =~ /^%/) {
print STDERR "Ignoring unknown command from profile header: $line";
} else {
return $line;
}
}
return undef; }
sub IsSymbolizedProfileFile {
my $file_name = shift;
if (!(-e $file_name) || !(-r $file_name)) {
return 0;
}
open(TFILE, "<$file_name");
binmode TFILE;
my $firstline = ReadProfileHeader(*TFILE);
close(TFILE);
if (!$firstline) {
return 0;
}
$SYMBOL_PAGE =~ m,[^/]+$,; my $symbol_marker = $&;
return $firstline =~ /^--- *$symbol_marker/;
}
sub ReadProfile {
my $prog = shift;
my $fname = shift;
my $result;
$CONTENTION_PAGE =~ m,[^/]+$,; my $contention_marker = $&;
$GROWTH_PAGE =~ m,[^/]+$,; my $growth_marker = $&;
$SYMBOL_PAGE =~ m,[^/]+$,; my $symbol_marker = $&;
$PROFILE_PAGE =~ m,[^/]+$,; my $profile_marker = $&;
$HEAP_PAGE =~ m,[^/]+$,; my $heap_marker = $&;
open(PROFILE, "<$fname") || error("$fname: $!\n");
binmode PROFILE; my $header = ReadProfileHeader(*PROFILE);
if (!defined($header)) { error("Profile is empty.\n");
}
my $symbols;
if ($header =~ m/^--- *$symbol_marker/o) {
if (!$main::use_symbolized_profile) {
error("FATAL ERROR: Symbolized profile\n $fname\ncannot be used with " .
"a binary arg. Try again without passing\n $prog\n");
}
$symbols = ReadSymbols(*PROFILE{IO});
$header = ReadProfileHeader(*PROFILE) || "";
}
if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) {
$header = ReadProfileHeader(*PROFILE) || "";
}
$main::profile_type = '';
if ($header =~ m/^heap profile:.*$growth_marker/o) {
$main::profile_type = 'growth';
$result = ReadHeapProfile($prog, *PROFILE, $header);
} 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);
} elsif ($header =~ m/^--- *Stacks:/) {
print STDERR
"Old format contention profile: mistakenly reports " .
"condition variable signals as lock contentions.\n";
$main::profile_type = 'contention';
$result = ReadSynchProfile($prog, *PROFILE);
} elsif ($header =~ m/^--- *$profile_marker/) {
$main::profile_type = 'cpu';
$result = ReadCPUProfile($prog, $fname, *PROFILE);
} else {
if (defined($symbols)) {
error("$fname: Cannot recognize profile section after symbols.\n");
}
$main::profile_type = 'cpu';
$result = ReadCPUProfile($prog, $fname, *PROFILE);
}
close(PROFILE);
if (defined($symbols)) {
$result->{symbols} = $symbols;
}
return $result;
}
sub FixCallerAddresses {
my $stack = shift;
{
$stack =~ /(\s)/;
my $delimiter = $1;
my @addrs = split(' ', $stack);
my @fixedaddrs;
$#fixedaddrs = $#addrs;
if ($#addrs >= 0) {
$fixedaddrs[0] = $addrs[0];
}
for (my $i = 1; $i <= $#addrs; $i++) {
$fixedaddrs[$i] = AddressSub($addrs[$i], "0x1");
}
return join $delimiter, @fixedaddrs;
}
}
sub ReadCPUProfile {
my $prog = shift;
my $fname = shift; local *PROFILE = shift;
my $version;
my $period;
my $i;
my $profile = {};
my $pcs = {};
my $slots = CpuProfileStream->new(*PROFILE, $fname);
if ($slots->get(0) != 0 ) {
error("$fname: not a profile file, or old format profile file\n");
}
$i = 2 + $slots->get(1);
$version = $slots->get(2);
$period = $slots->get(3);
if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {
error("$fname: not a profile file, or corrupted profile file\n");
}
while ($slots->get($i) != -1) {
my $n = $slots->get($i++);
my $d = $slots->get($i++);
if ($d > (2**16)) { my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8));
print STDERR "At index $i (address $addr):\n";
error("$fname: stack trace depth >= 2**32\n");
}
if ($slots->get($i) == 0) {
$i += $d;
last;
}
my @k = ();
for (my $j = 0; $j < $d; $j++) {
my $pc = $slots->get($i+$j);
$pc--;
$pc = sprintf("%0*x", $address_length, $pc);
$pcs->{$pc} = 1;
push @k, $pc;
}
AddEntry($profile, (join "\n", @k), $n);
$i += $d;
}
my $map = '';
seek(PROFILE, $i * 4, 0);
read(PROFILE, $map, (stat PROFILE)[7]);
my $r = {};
$r->{version} = $version;
$r->{period} = $period;
$r->{profile} = $profile;
$r->{libs} = ParseLibraries($prog, $map, $pcs);
$r->{pcs} = $pcs;
return $r;
}
sub HeapProfileIndex {
my $index = 1;
if ($main::opt_inuse_space) {
$index = 1;
} elsif ($main::opt_inuse_objects) {
$index = 0;
} elsif ($main::opt_alloc_space) {
$index = 3;
} elsif ($main::opt_alloc_objects) {
$index = 2;
}
return $index;
}
sub ReadMappedLibraries {
my $fh = shift;
my $map = "";
while (<$fh>) {
s/\r//g; $map .= $_;
}
return $map;
}
sub ReadMemoryMap {
my $fh = shift;
my $map = "";
my $buildvar = "";
while (<PROFILE>) {
s/\r//g; if (m/^\s*build=(.*)\n/) {
$buildvar = $1;
}
$_ =~ 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) {
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 {
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();
my $sampling_algorithm = 0;
my $sample_adjustment = 0;
chomp($header);
my $type = "unknown";
if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") {
if (defined($6) && ($6 ne '')) {
$type = $6;
my $sample_period = $8;
if (($type eq "heapprofile") || ($type !~ /heap/) ) {
$sampling_algorithm = 0;
} elsif ($type =~ /_v2/) {
$sampling_algorithm = 2; if (defined($sample_period) && ($sample_period ne '')) {
$sample_adjustment = int($sample_period);
}
} else {
$sampling_algorithm = 1; if (defined($sample_period) && ($sample_period ne '')) {
$sample_adjustment = int($sample_period)/2;
}
}
} else {
my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
if (($n1 == $n2) && ($s1 == $s2)) {
$sampling_algorithm = 1;
}
}
}
if ($sampling_algorithm > 0) {
if ($sample_adjustment == 0) {
$sample_adjustment = 128*1024;
print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n";
} else {
printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n",
$sample_adjustment);
}
if ($sampling_algorithm > 1) {
printf STDERR "Heap version $sampling_algorithm\n";
}
}
my $profile = {};
my $pcs = {};
my $map = "";
while (<PROFILE>) {
s/\r//g; if (/^MAPPED_LIBRARIES:/) {
$map .= ReadMappedLibraries(*PROFILE);
last;
}
if (/^--- Memory map:/) {
$map .= ReadMemoryMap(*PROFILE);
last;
}
s/^\s*//;
s/\s*$//;
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);
my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
$n1, $s1, $n2, $s2);
AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
}
}
my $r = {};
$r->{version} = "heap";
$r->{period} = 1;
$r->{profile} = $profile;
$r->{libs} = ParseLibraries($prog, $map, $pcs);
$r->{pcs} = $pcs;
return $r;
}
sub ReadThreadedHeapProfile {
my ($prog, $fname, $header) = @_;
my $index = HeapProfileIndex();
my $sampling_algorithm = 0;
my $sample_adjustment = 0;
chomp($header);
my $type = "unknown";
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;
}
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 "") {
next;
}
my $thread = $2;
my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6);
my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,
$n1, $s1, $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;
my $header = shift;
my $map = '';
my $profile = {};
my $pcs = {};
my $sampling_period = 1;
my $cyclespernanosec = 2.8; my $seen_clockrate = 0;
my $line;
my $index = 0;
if ($main::opt_total_delay) {
$index = 0;
} elsif ($main::opt_contentions) {
$index = 1;
} elsif ($main::opt_mean_delay) {
$index = 2;
}
while ( $line = <PROFILE> ) {
$line =~ s/\r//g; if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) {
my ($cycles, $count, $stack) = ($1, $2, $3);
$cycles /= $cyclespernanosec;
$cycles *= $sampling_period;
$count *= $sampling_period;
my @values = ($cycles, $count, $cycles / $count);
AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);
} elsif ( $line =~ /^(slow release).*thread \d+ \@\s*(.*?)\s*$/ ||
$line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) {
my ($cycles, $stack) = ($1, $2);
if ($cycles !~ /^\d+$/) {
next;
}
$cycles /= $cyclespernanosec;
$cycles *= $sampling_period;
AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);
} elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {
my ($variable, $value) = ($1,$2);
for ($variable, $value) {
s/^\s+//;
s/\s+$//;
}
if ($variable eq "cycles/second") {
$cyclespernanosec = $value / 1e9;
$seen_clockrate = 1;
} elsif ($variable eq "sampling period") {
$sampling_period = $value;
} elsif ($variable eq "ms since reset") {
} elsif ($variable eq "discarded samples") {
} else {
printf STDERR ("Ignoring unnknown variable in /contention output: " .
"'%s' = '%s'\n",$variable,$value);
}
} else {
$map .= $line;
}
}
if (!$seen_clockrate) {
printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n",
$cyclespernanosec);
}
my $r = {};
$r->{version} = 0;
$r->{period} = $sampling_period;
$r->{profile} = $profile;
$r->{libs} = ParseLibraries($prog, $map, $pcs);
$r->{pcs} = $pcs;
return $r;
}
sub HexExtend {
my $addr = shift;
$addr =~ s/^(0x)?0*//;
my $zeros_needed = $address_length - length($addr);
if ($zeros_needed < 0) {
printf STDERR "Warning: address $addr is longer than address length $address_length\n";
return $addr;
}
return ("0" x $zeros_needed) . $addr;
}
sub FindLibrary {
my $file = shift;
my $suffix = $file;
do {
foreach my $prefix (@prefix_list) {
my $fullpath = $prefix . $suffix;
if (-e $fullpath) {
return $fullpath;
}
}
} while ($suffix =~ s|^/[^/]+/|/|);
return $file;
}
sub DebuggingLibrary {
my $file = shift;
if ($file !~ m|^/|) {
return undef;
}
if (-f "/usr/lib/debug$file") {
if($main::opt_debug) { print STDERR "found debug info for $file in /usr/lib/debug$file\n"; }
return "/usr/lib/debug$file";
} elsif (-f "/usr/lib/debug$file.debug") {
if($main::opt_debug) { print STDERR "found debug info for $file in /usr/lib/debug$file.debug\n"; }
return "/usr/lib/debug$file.debug";
}
if(!$main::opt_debug_syms_by_id) {
if($main::opt_debug) { print STDERR "no debug symbols found for $file\n" };
return undef;
}
my $readelf = '';
if (!$main::gave_up_on_elfutils) {
$readelf = qx/eu-readelf -n ${file}/;
if ($?) {
print STDERR "Cannot run eu-readelf. To use --debug-syms-by-id you must be on Linux, with elfutils installed.\n";
$main::gave_up_on_elfutils = 1;
return undef;
}
my $buildID = $1 if $readelf =~ /Build ID: ([A-Fa-f0-9]+)/s;
if (defined $buildID && length $buildID > 0) {
my $symbolFile = '/usr/lib/debug/.build-id/' . substr($buildID, 0, 2) . '/' . substr($buildID, 2) . '.debug';
if (-e $symbolFile) {
if($main::opt_debug) { print STDERR "found debug symbol file $symbolFile for $file\n" };
return $symbolFile;
} else {
if($main::opt_debug) { print STDERR "no debug symbol file found for $file, build ID: $buildID\n" };
return undef;
}
}
}
if($main::opt_debug) { print STDERR "no debug symbols found for $file, build ID unknown\n" };
return undef;
}
sub ParseTextSectionHeaderFromObjdump {
my $lib = shift;
my $size = undef;
my $vma;
my $file_offset;
my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib);
open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
while (<OBJDUMP>) {
s/\r//g; my @x = split;
if (($#x >= 6) && ($x[1] eq '.text')) {
$size = $x[2];
$vma = $x[3];
$file_offset = $x[5];
last;
}
}
close(OBJDUMP);
if (!defined($size)) {
return undef;
}
my $r = {};
$r->{size} = $size;
$r->{vma} = $vma;
$r->{file_offset} = $file_offset;
return $r;
}
sub ParseTextSectionHeaderFromOtool {
my $lib = shift;
my $size = undef;
my $vma = undef;
my $file_offset = undef;
my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib);
open(OTOOL, "$command |") || error("$command: $!\n");
my $cmd = "";
my $sectname = "";
my $segname = "";
foreach my $line (<OTOOL>) {
$line =~ s/\r//g; if ($line =~ /Load command/) {
$cmd = "";
$sectname = "";
$segname = "";
} elsif ($line =~ /Section/) {
$sectname = "";
$segname = "";
} elsif ($line =~ /cmd (\w+)/) {
$cmd = $1;
} elsif ($line =~ /sectname (\w+)/) {
$sectname = $1;
} elsif ($line =~ /segname (\w+)/) {
$segname = $1;
} elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") &&
$sectname eq "__text" &&
$segname eq "__TEXT")) {
next;
} elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) {
$vma = $1;
} elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) {
$size = $1;
} elsif ($line =~ /\boffset ([0-9]+)/) {
$file_offset = sprintf("%016x", $1);
}
if (defined($vma) && defined($size) && defined($file_offset)) {
last;
}
}
close(OTOOL);
if (!defined($vma) || !defined($size) || !defined($file_offset)) {
return undef;
}
my $r = {};
$r->{size} = $size;
$r->{vma} = $vma;
$r->{file_offset} = $file_offset;
return $r;
}
sub ParseTextSectionHeader {
if (defined($obj_tool_map{"otool"})) {
my $r = ParseTextSectionHeaderFromOtool(@_);
if (defined($r)){
return $r;
}
}
return ParseTextSectionHeaderFromObjdump(@_);
}
sub ParseLibraries {
return if $main::use_symbol_page; my $prog = Cwd::abs_path(shift);
my $map = shift;
my $pcs = shift;
my $result = [];
my $h = "[a-f0-9]+";
my $zero_offset = HexExtend("0");
my $buildvar = "";
foreach my $l (split("\n", $map)) {
if ($l =~ m/^\s*build=(.*)$/) {
$buildvar = $1;
}
my $start;
my $finish;
my $offset;
my $lib;
if ($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+\.(so|dll|dylib|bundle)((\.\d+)+\w*(\.\d+){0,3})?)$/i) {
$start = HexExtend($1);
$finish = HexExtend($2);
$offset = HexExtend($3);
$lib = $4;
$lib =~ s|\\|/|g; } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) {
$start = HexExtend($1);
$finish = HexExtend($2);
$offset = $zero_offset;
$lib = $3;
} elsif (($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+)$/i) && ($4 eq $prog)) {
$start = HexExtend($1);
$finish = HexExtend($2);
$offset = HexExtend($3);
$lib = $4;
$lib =~ s|\\|/|g; }
elsif ($l =~ /^(0x$h)\s(0x$h)\s\d+\s\d+\s0x$h\sr-x\s\d+\s\d+\s0x\d+\s(COW|NCO)\s(NC|NNC)\svnode\s(\S+\.so(\.\d+)*)/) {
$start = HexExtend($1);
$finish = HexExtend($2);
$offset = $zero_offset;
$lib = FindLibrary($5);
} else {
next;
}
$lib =~ s/\$build\b/$buildvar/g;
$lib = FindLibrary($lib);
if (!DebuggingLibrary($lib)) {
my $text = ParseTextSectionHeader($lib);
if (defined($text)) {
my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});
$offset = AddressAdd($offset, $vma_offset);
}
}
if($main::opt_debug) { printf STDERR "$start:$finish ($offset) $lib\n"; }
push(@{$result}, [$lib, $start, $finish, $offset]);
}
if ($main::opt_lib ne "") {
my $text = ParseTextSectionHeader($main::opt_lib);
if (defined($text)) {
my $start = $text->{vma};
my $finish = AddressAdd($start, $text->{size});
push(@{$result}, [$main::opt_lib, $start, $finish, $start]);
}
}
my $min_pc = HexExtend("0");
my $max_pc = $min_pc; foreach my $pc (keys(%{$pcs})) {
if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }
}
push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);
return $result;
}
sub AddressAdd {
my $addr1 = shift;
my $addr2 = shift;
my $sum;
if ($address_length == 8) {
$sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);
return sprintf("%08x", $sum);
} else {
if ($main::opt_debug and $main::opt_test) {
print STDERR "AddressAdd $addr1 + $addr2 = ";
}
my $a1 = substr($addr1,-7);
$addr1 = substr($addr1,0,-7);
my $a2 = substr($addr2,-7);
$addr2 = substr($addr2,0,-7);
$sum = hex($a1) + hex($a2);
my $c = 0;
if ($sum > 0xfffffff) {
$c = 1;
$sum -= 0x10000000;
}
my $r = sprintf("%07x", $sum);
$a1 = substr($addr1,-7);
$addr1 = substr($addr1,0,-7);
$a2 = substr($addr2,-7);
$addr2 = substr($addr2,0,-7);
$sum = hex($a1) + hex($a2) + $c;
$c = 0;
if ($sum > 0xfffffff) {
$c = 1;
$sum -= 0x10000000;
}
$r = sprintf("%07x", $sum) . $r;
$sum = hex($addr1) + hex($addr2) + $c;
if ($sum > 0xff) { $sum -= 0x100; }
$r = sprintf("%02x", $sum) . $r;
if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; }
return $r;
}
}
sub AddressSub {
my $addr1 = shift;
my $addr2 = shift;
my $diff;
if ($address_length == 8) {
$diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);
return sprintf("%08x", $diff);
} else {
my $a1 = hex(substr($addr1,-7));
$addr1 = substr($addr1,0,-7);
my $a2 = hex(substr($addr2,-7));
$addr2 = substr($addr2,0,-7);
my $b = 0;
if ($a2 > $a1) {
$b = 1;
$a1 += 0x10000000;
}
$diff = $a1 - $a2;
my $r = sprintf("%07x", $diff);
$a1 = hex(substr($addr1,-7));
$addr1 = substr($addr1,0,-7);
$a2 = hex(substr($addr2,-7)) + $b;
$addr2 = substr($addr2,0,-7);
$b = 0;
if ($a2 > $a1) {
$b = 1;
$a1 += 0x10000000;
}
$diff = $a1 - $a2;
$r = sprintf("%07x", $diff) . $r;
$a1 = hex($addr1);
$a2 = hex($addr2) + $b;
if ($a2 > $a1) { $a1 += 0x100; }
$diff = $a1 - $a2;
$r = sprintf("%02x", $diff) . $r;
return $r;
}
}
sub AddressInc {
my $addr = shift;
my $sum;
if ($address_length == 8) {
$sum = (hex($addr)+1) % (0x10000000 * 16);
return sprintf("%08x", $sum);
} else {
my $a1 = substr($addr,-7);
$addr = substr($addr,0,-7);
$sum = hex($a1) + 1;
my $r = sprintf("%07x", $sum);
if ($sum <= 0xfffffff) {
$r = $addr . $r;
return HexExtend($r);
} else {
$r = "0000000";
}
$a1 = substr($addr,-7);
$addr = substr($addr,0,-7);
$sum = hex($a1) + 1;
$r = sprintf("%07x", $sum) . $r;
if ($sum <= 0xfffffff) {
$r = $addr . $r;
return HexExtend($r);
} else {
$r = "00000000000000";
}
$sum = hex($addr) + 1;
if ($sum > 0xff) { $sum -= 0x100; }
$r = sprintf("%02x", $sum) . $r;
return $r;
}
}
sub ExtractSymbols {
my $libs = shift;
my $pcset = shift;
my $symbols = {};
my @pcs = (sort { $a cmp $b } keys(%{$pcset})); foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {
my $libname = $lib->[0];
my $start = $lib->[1];
my $finish = $lib->[2];
my $offset = $lib->[3];
my $debug_libname = DebuggingLibrary($libname);
if ($debug_libname) {
$libname = $debug_libname;
}
my $contained = [];
my ($start_pc_index, $finish_pc_index);
for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;
$finish_pc_index--) {
last if $pcs[$finish_pc_index - 1] le $finish;
}
for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;
$start_pc_index--) {
last if $pcs[$start_pc_index - 1] lt $start;
}
@{$contained} = splice(@pcs, $start_pc_index,
$finish_pc_index - $start_pc_index);
MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);
}
return $symbols;
}
sub MapToSymbols {
my $image = shift;
my $offset = shift;
my $pclist = shift;
my $symbols = shift;
my $debug = 0;
if ($#{$pclist} < 0) { return; }
my $addr2line = $obj_tool_map{"addr2line"};
my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image);
if (exists $obj_tool_map{"addr2line_pdb"}) {
$addr2line = $obj_tool_map{"addr2line_pdb"};
$cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image);
}
if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) {
MapSymbolsWithNM($image, $offset, $pclist, $symbols);
return;
}
$sep_address = undef; my $nm_symbols = {};
MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);
if (defined($sep_address)) {
if (system("$cmd -i --help >$dev_null 2>&1") == 0) {
$cmd .= " -i";
} else {
$sep_address = undef; }
}
open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n");
if ($debug) { print("---- $image ---\n"); }
for (my $i = 0; $i <= $#{$pclist}; $i++) {
if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); }
printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset));
if (defined($sep_address)) {
printf ADDRESSES ("%s\n", $sep_address);
}
}
close(ADDRESSES);
if ($debug) {
print("----\n");
system("cat", $main::tmpfile_sym);
print("----\n");
system("$cmd < " . ShellEscape($main::tmpfile_sym));
print("----\n");
}
open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |")
|| error("$cmd: $!\n");
my $count = 0; while (<SYMBOLS>) {
s/\r?\n$//g;
my $fullfunction = $_;
$_ = <SYMBOLS>;
s/\r?\n$//g;
my $filelinenum = $_;
if (defined($sep_address) && $fullfunction eq $sep_symbol) {
$count++;
next;
}
$filelinenum =~ s|\\|/|g;
my $pcstr = $pclist->[$count];
my $function = ShortFunctionName($fullfunction);
my $nms = $nm_symbols->{$pcstr};
if (defined($nms)) {
if ($fullfunction eq '??') {
$function = $nms->[0];
$fullfunction = $nms->[2];
} else {
if ($nms->[2] =~ m/^\Q$function\E/) { $function = $nms->[0];
$fullfunction = $nms->[2];
}
}
}
my $sym = $symbols->{$pcstr};
if (!defined($sym)) {
$sym = [];
$symbols->{$pcstr} = $sym;
}
unshift(@{$sym}, $function, $filelinenum, $fullfunction);
if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); }
if (!defined($sep_address)) {
$count++;
}
}
close(SYMBOLS);
}
sub MapSymbolsWithNM {
my $image = shift;
my $offset = shift;
my $pclist = shift;
my $symbols = shift;
my $symbol_table = GetProcedureBoundaries($image, ".");
if (!%{$symbol_table}) {
return 0;
}
my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }
keys(%{$symbol_table});
if ($#names < 0) {
foreach my $pc (@{$pclist}) {
my $pcstr = "0x" . $pc;
$symbols->{$pc} = [$pcstr, "?", $pcstr];
}
return 0;
}
my $index = 0;
my $fullname = $names[0];
my $name = ShortFunctionName($fullname);
foreach my $pc (sort { $a cmp $b } @{$pclist}) {
my $mpc = AddressSub($pc, $offset);
while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){
$index++;
$fullname = $names[$index];
$name = ShortFunctionName($fullname);
}
if ($mpc lt $symbol_table->{$fullname}->[1]) {
$symbols->{$pc} = [$name, "?", $fullname];
} else {
my $pcstr = "0x" . $pc;
$symbols->{$pc} = [$pcstr, "?", $pcstr];
}
}
return 1;
}
sub ShortFunctionName {
my $function = shift;
while ($function =~ s/\([^()]*\)(\s*const)?//g) { } while ($function =~ s/<[^<>]*>//g) { } $function =~ s/^.*\s+(\w+::)/$1/; return $function;
}
sub CleanDisassembly {
my $d = shift;
while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } return $d;
}
sub CleanFileName {
my ($f) = @_;
$f =~ s|^/proc/self/cwd/||;
$f =~ s|^\./||;
return $f;
}
sub UnparseAddress {
my ($offset, $address) = @_;
$address = AddressSub($address, $offset);
$address =~ s/^0x//;
$address =~ s/^0*//;
return $address;
}
sub ConfigureObjTools {
my $prog_file = shift;
(-e $prog_file) || error("$prog_file does not exist.\n");
my $file_type = undef;
if (-e "/usr/bin/file") {
my $escaped_prog_file = ShellEscape($prog_file);
$file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||
/usr/bin/file $escaped_prog_file`;
} elsif ($^O == "MSWin32") {
$file_type = "MS Windows";
} else {
print STDERR "WARNING: Can't determine the file type of $prog_file";
}
if ($file_type =~ /64-bit/) {
$address_length = 16;
}
if ($file_type =~ /MS Windows/) {
$obj_tool_map{"nm_pdb"} = "nm-pdb";
$obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
}
if ($file_type =~ /Mach-O/) {
$obj_tool_map{"otool"} = "otool";
$obj_tool_map{"addr2line"} = "false"; $obj_tool_map{"objdump"} = "false"; }
foreach my $tool (keys %obj_tool_map) {
$obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});
}
}
sub ConfigureTool {
my $tool = shift;
my $path;
my $tools = $main::opt_tools || $ENV{"JEPROF_TOOLS"} || "";
if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) {
$path = $2;
} elsif ($tools ne '') {
foreach my $prefix (split(',', $tools)) {
next if ($prefix =~ /:/); if (-x $prefix . $tool) {
$path = $prefix . $tool;
last;
}
}
if (!$path) {
error("No '$tool' found with prefix specified by " .
"--tools (or \$JEPROF_TOOLS) '$tools'\n");
}
} else {
$0 =~ m,[^/]*$,; my $dirname = $`; if (-x "$dirname$tool") {
$path = "$dirname$tool";
} else {
$path = $tool;
}
}
if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; }
return $path;
}
sub ShellEscape {
my @escaped_words = ();
foreach my $word (@_) {
my $escaped_word = $word;
if ($word =~ m![^a-zA-Z0-9/.,_=-]!) { $escaped_word =~ s/'/'\\''/;
$escaped_word = "'$escaped_word'";
}
push(@escaped_words, $escaped_word);
}
return join(" ", @escaped_words);
}
sub cleanup {
unlink($main::tmpfile_sym);
unlink(keys %main::tempnames);
if ((scalar(@main::profile_files) > 0) &&
defined($main::collected_profile)) {
if (scalar(@main::profile_files) == 1) {
print STDERR "Dynamically gathered profile is in $main::collected_profile\n";
}
print STDERR "If you want to investigate this profile further, you can do:\n";
print STDERR "\n";
print STDERR " jeprof \\\n";
print STDERR " $main::prog \\\n";
print STDERR " $main::collected_profile\n";
print STDERR "\n";
}
}
sub sighandler {
cleanup();
exit(1);
}
sub error {
my $msg = shift;
print STDERR $msg;
cleanup();
exit(1);
}
sub GetProcedureBoundariesViaNm {
my $escaped_nm_command = shift; my $regexp = shift;
my $symbol_table = {};
open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n");
my $last_start = "0";
my $routine = "";
while (<NM>) {
s/\r//g; if (m/^\s*([0-9a-f]+) (.) (..*)/) {
my $start_val = $1;
my $type = $2;
my $this_routine = $3;
if ($start_val eq $last_start && $type =~ /t/i) {
$routine = $this_routine;
next;
} elsif ($start_val eq $last_start) {
next;
}
if ($this_routine eq $sep_symbol) {
$sep_address = HexExtend($start_val);
}
$this_routine .= "<$start_val>";
if (defined($routine) && $routine =~ m/$regexp/) {
$symbol_table->{$routine} = [HexExtend($last_start),
HexExtend($start_val)];
}
$last_start = $start_val;
$routine = $this_routine;
} elsif (m/^Loaded image name: (.+)/) {
if ($main::opt_debug) { print STDERR "Using Image $1\n"; }
} elsif (m/^PDB file name: (.+)/) {
if ($main::opt_debug) { print STDERR "Using PDB $1\n"; }
}
}
close(NM);
if (defined($routine) && $routine =~ m/$regexp/) {
$symbol_table->{$routine} = [HexExtend($last_start),
HexExtend($last_start)];
}
return $symbol_table;
}
sub GetProcedureBoundaries {
my $image = shift;
my $regexp = shift;
$image =~ s#^[^/]#./$&#;
my $debugging = DebuggingLibrary($image);
if ($debugging) {
$image = $debugging;
}
my $nm = $obj_tool_map{"nm"};
my $cppfilt = $obj_tool_map{"c++filt"};
my $demangle_flag = "";
my $cppfilt_flag = "";
my $to_devnull = ">$dev_null 2>&1";
if (system(ShellEscape($nm, "--demangle", $image) . $to_devnull) == 0) {
$demangle_flag = "--demangle";
$cppfilt_flag = "";
} elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {
$cppfilt_flag = " | " . ShellEscape($cppfilt);
};
my $flatten_flag = "";
if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) {
$flatten_flag = "-f";
}
my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag,
$image) . " 2>$dev_null $cppfilt_flag",
ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag,
$image) . " 2>$dev_null $cppfilt_flag",
ShellEscape("6nm", "$image") . " 2>$dev_null | sort",
);
if (exists $obj_tool_map{"nm_pdb"}) {
push(@nm_commands,
ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image)
. " 2>$dev_null");
}
foreach my $nm_command (@nm_commands) {
my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);
return $symbol_table if (%{$symbol_table});
}
my $symbol_table = {};
return $symbol_table;
}
sub CanonicalHex {
my $arg = shift;
return join '', (split '_',$arg);
}
sub AddressAddUnitTest {
my $test_data_8 = shift;
my $test_data_16 = shift;
my $error_count = 0;
my $fail_count = 0;
my $pass_count = 0;
$address_length = 8;
foreach my $row (@{$test_data_8}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressAdd ($row->[0], $row->[1]);
if ($sum ne $row->[2]) {
printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
$row->[0], $row->[1], $row->[2];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count = $fail_count;
$fail_count = 0;
$pass_count = 0;
$address_length = 16;
foreach my $row (@{$test_data_16}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
my $expected = join '', (split '_',$row->[2]);
if ($sum ne CanonicalHex($row->[2])) {
printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
$row->[0], $row->[1], $row->[2];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count += $fail_count;
return $error_count;
}
sub AddressSubUnitTest {
my $test_data_8 = shift;
my $test_data_16 = shift;
my $error_count = 0;
my $fail_count = 0;
my $pass_count = 0;
$address_length = 8;
foreach my $row (@{$test_data_8}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressSub ($row->[0], $row->[1]);
if ($sum ne $row->[3]) {
printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
$row->[0], $row->[1], $row->[3];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count = $fail_count;
$fail_count = 0;
$pass_count = 0;
$address_length = 16;
foreach my $row (@{$test_data_16}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
if ($sum ne CanonicalHex($row->[3])) {
printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
$row->[0], $row->[1], $row->[3];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count += $fail_count;
return $error_count;
}
sub AddressIncUnitTest {
my $test_data_8 = shift;
my $test_data_16 = shift;
my $error_count = 0;
my $fail_count = 0;
my $pass_count = 0;
$address_length = 8;
foreach my $row (@{$test_data_8}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressInc ($row->[0]);
if ($sum ne $row->[4]) {
printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
$row->[0], $row->[4];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count = $fail_count;
$fail_count = 0;
$pass_count = 0;
$address_length = 16;
foreach my $row (@{$test_data_16}) {
if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
my $sum = AddressInc (CanonicalHex($row->[0]));
if ($sum ne CanonicalHex($row->[4])) {
printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
$row->[0], $row->[4];
++$fail_count;
} else {
++$pass_count;
}
}
printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n",
$pass_count, $fail_count;
$error_count += $fail_count;
return $error_count;
}
sub RunUnitTests {
my $error_count = 0;
my $unit_test_data_8 = [
[qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],
[qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],
[qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],
[qw(00000001 ffffffff 00000000 00000002 00000002)],
[qw(00000001 fffffff0 fffffff1 00000011 00000002)],
];
my $unit_test_data_16 = [
[qw(aaaaaaaa 50505050
00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],
[qw(50505050 aaaaaaaa
00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],
[qw(ffffffff aaaaaaaa
00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],
[qw(00000001 ffffffff
00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],
[qw(00000001 fffffff0
00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],
[qw(00_a00000a_aaaaaaa 50505050
00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],
[qw(0f_fff0005_0505050 aaaaaaaa
0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],
[qw(00_000000f_fffffff 01_800000a_aaaaaaa
01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],
[qw(00_0000000_0000001 ff_fffffff_fffffff
00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],
[qw(00_0000000_0000001 ff_fffffff_ffffff0
ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],
];
$error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);
$error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);
$error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);
if ($error_count > 0) {
print STDERR $error_count, " errors: FAILED\n";
} else {
print STDERR "PASS\n";
}
exit ($error_count);
}