blob: c0c64bcc17b7842aed839f92cf005aab72cbed96 [file] [log] [blame]
Austin Schuh745610d2015-09-06 18:19:50 -07001#! /usr/bin/env perl
2
3# Copyright (c) 1998-2007, Google Inc.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10# * Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# * Redistributions in binary form must reproduce the above
13# copyright notice, this list of conditions and the following disclaimer
14# in the documentation and/or other materials provided with the
15# distribution.
16# * Neither the name of Google Inc. nor the names of its
17# contributors may be used to endorse or promote products derived from
18# this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32# ---
33# Program for printing the profile generated by common/profiler.cc,
34# or by the heap profiler (common/debugallocation.cc)
35#
36# The profile contains a sequence of entries of the form:
37# <count> <stack trace>
38# This program parses the profile, and generates user-readable
39# output.
40#
41# Examples:
42#
43# % tools/pprof "program" "profile"
44# Enters "interactive" mode
45#
46# % tools/pprof --text "program" "profile"
47# Generates one line per procedure
48#
49# % tools/pprof --gv "program" "profile"
50# Generates annotated call-graph and displays via "gv"
51#
52# % tools/pprof --gv --focus=Mutex "program" "profile"
53# Restrict to code paths that involve an entry that matches "Mutex"
54#
55# % tools/pprof --gv --focus=Mutex --ignore=string "program" "profile"
56# Restrict to code paths that involve an entry that matches "Mutex"
57# and does not match "string"
58#
59# % tools/pprof --list=IBF_CheckDocid "program" "profile"
60# Generates disassembly listing of all routines with at least one
61# sample that match the --list=<regexp> pattern. The listing is
62# annotated with the flat and cumulative sample counts at each line.
63#
64# % tools/pprof --disasm=IBF_CheckDocid "program" "profile"
65# Generates disassembly listing of all routines with at least one
66# sample that match the --disasm=<regexp> pattern. The listing is
67# annotated with the flat and cumulative sample counts at each PC value.
68#
69# TODO: Use color to indicate files?
70
71use strict;
72use warnings;
73use Getopt::Long;
74use Cwd;
75use POSIX;
76
77my $PPROF_VERSION = "2.0";
78
79# These are the object tools we use which can come from a
80# user-specified location using --tools, from the PPROF_TOOLS
81# environment variable, or from the environment.
82my %obj_tool_map = (
83 "objdump" => "objdump",
84 "nm" => "nm",
85 "addr2line" => "addr2line",
86 "c++filt" => "c++filt",
87 ## ConfigureObjTools may add architecture-specific entries:
88 #"nm_pdb" => "nm-pdb", # for reading windows (PDB-format) executables
89 #"addr2line_pdb" => "addr2line-pdb", # ditto
90 #"otool" => "otool", # equivalent of objdump on OS X
91);
92# NOTE: these are lists, so you can put in commandline flags if you want.
93my @DOT = ("dot"); # leave non-absolute, since it may be in /usr/local
94my @GV = ("gv");
95my @EVINCE = ("evince"); # could also be xpdf or perhaps acroread
96my @KCACHEGRIND = ("kcachegrind");
97my @PS2PDF = ("ps2pdf");
98# These are used for dynamic profiles
99my @URL_FETCHER = ("curl", "-s");
100
101# These are the web pages that servers need to support for dynamic profiles
102my $HEAP_PAGE = "/pprof/heap";
103my $PROFILE_PAGE = "/pprof/profile"; # must support cgi-param "?seconds=#"
104my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; # must support cgi-param
105 # ?seconds=#&event=x&period=n
106my $GROWTH_PAGE = "/pprof/growth";
107my $CONTENTION_PAGE = "/pprof/contention";
108my $WALL_PAGE = "/pprof/wall(?:\\?.*)?"; # accepts options like namefilter
109my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
110my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; # must support cgi-param
111 # "?seconds=#",
112 # "?tags_regexp=#" and
113 # "?type=#".
114my $SYMBOL_PAGE = "/pprof/symbol"; # must support symbol lookup via POST
115my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
116
117# These are the web pages that can be named on the command line.
118# All the alternatives must begin with /.
119my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
120 "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
121 "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
122
123# default binary name
124my $UNKNOWN_BINARY = "(unknown)";
125
126# There is a pervasive dependency on the length (in hex characters,
127# i.e., nibbles) of an address, distinguishing between 32-bit and
128# 64-bit profiles. To err on the safe size, default to 64-bit here:
129my $address_length = 16;
130
131my $dev_null = "/dev/null";
132if (! -e $dev_null && $^O =~ /MSWin/) { # $^O is the OS perl was built for
133 $dev_null = "nul";
134}
135
136# A list of paths to search for shared object files
137my @prefix_list = ();
138
139# Special routine name that should not have any symbols.
140# Used as separator to parse "addr2line -i" output.
141my $sep_symbol = '_fini';
142my $sep_address = undef;
143
144my @stackTraces;
145
146##### Argument parsing #####
147
148sub usage_string {
149 return <<EOF;
150Usage:
151pprof [options] <program> <profiles>
152 <profiles> is a space separated list of profile names.
153pprof [options] <symbolized-profiles>
154 <symbolized-profiles> is a list of profile files where each file contains
155 the necessary symbol mappings as well as profile data (likely generated
156 with --raw).
157pprof [options] <profile>
158 <profile> is a remote form. Symbols are obtained from host:port$SYMBOL_PAGE
159
160 Each name can be:
161 /path/to/profile - a path to a profile file
162 host:port[/<service>] - a location of a service to get profile from
163
164 The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
165 $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
166 $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.
167 For instance:
168 pprof http://myserver.com:80$HEAP_PAGE
169 If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
170pprof --symbols <program>
171 Maps addresses to symbol names. In this mode, stdin should be a
172 list of library mappings, in the same format as is found in the heap-
173 and cpu-profile files (this loosely matches that of /proc/self/maps
174 on linux), followed by a list of hex addresses to map, one per line.
175
176 For more help with querying remote servers, including how to add the
177 necessary server-side support code, see this filename (or one like it):
178
179 /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html
180
181Options:
182 --cum Sort by cumulative data
183 --base=<base> Subtract <base> from <profile> before display
184 --interactive Run in interactive mode (interactive "help" gives help) [default]
185 --seconds=<n> Length of time for dynamic profiles [default=30 secs]
186 --add_lib=<file> Read additional symbols and line info from the given library
187 --lib_prefix=<dir> Comma separated list of library path prefixes
188 --no_strip_temp Do not strip template arguments from function names
189
190Reporting Granularity:
191 --addresses Report at address level
192 --lines Report at source line level
193 --functions Report at function level [default]
194 --files Report at source file level
195
196Output type:
197 --text Generate text report
198 --stacks Generate stack traces similar to the heap profiler (requires --text)
199 --callgrind Generate callgrind format to stdout
200 --gv Generate Postscript and display
201 --evince Generate PDF and display
202 --web Generate SVG and display
203 --list=<regexp> Generate source listing of matching routines
204 --disasm=<regexp> Generate disassembly of matching routines
205 --symbols Print demangled symbol names found at given addresses
206 --dot Generate DOT file to stdout
207 --ps Generate Postcript to stdout
208 --pdf Generate PDF to stdout
209 --svg Generate SVG to stdout
210 --gif Generate GIF to stdout
211 --raw Generate symbolized pprof data (useful with remote fetch)
212 --collapsed Generate collapsed stacks for building flame graphs
213 (see http://www.brendangregg.com/flamegraphs.html)
214
215Heap-Profile Options:
216 --inuse_space Display in-use (mega)bytes [default]
217 --inuse_objects Display in-use objects
218 --alloc_space Display allocated (mega)bytes
219 --alloc_objects Display allocated objects
220 --show_bytes Display space in bytes
221 --drop_negative Ignore negative differences
222
223Contention-profile options:
224 --total_delay Display total delay at each region [default]
225 --contentions Display number of delays at each region
226 --mean_delay Display mean delay at each region
227
228Call-graph Options:
229 --nodecount=<n> Show at most so many nodes [default=80]
230 --nodefraction=<f> Hide nodes below <f>*total [default=.005]
231 --edgefraction=<f> Hide edges below <f>*total [default=.001]
232 --maxdegree=<n> Max incoming/outgoing edges per node [default=8]
233 --focus=<regexp> Focus on nodes matching <regexp>
234 --ignore=<regexp> Ignore nodes matching <regexp>
235 --scale=<n> Set GV scaling [default=0]
236 --heapcheck Make nodes with non-0 object counts
237 (i.e. direct leak generators) more visible
238
239Miscellaneous:
240 --no-auto-signal-frm Automatically drop 2nd frame that is always same (cpu-only)
241 (assuming that it is artifact of bad stack captures
242 which include signal handler frames)
243 --show_addresses Always show addresses when applicable
244 --tools=<prefix or binary:fullpath>[,...] \$PATH for object tool pathnames
245 --test Run unit tests
246 --help This message
247 --version Version information
248
249Environment Variables:
250 PPROF_TMPDIR Profiles directory. Defaults to \$HOME/pprof
251 PPROF_TOOLS Prefix for object tools pathnames
252
253Examples:
254
255pprof /bin/ls ls.prof
256 Enters "interactive" mode
257pprof --text /bin/ls ls.prof
258 Outputs one line per procedure
259pprof --web /bin/ls ls.prof
260 Displays annotated call-graph in web browser
261pprof --gv /bin/ls ls.prof
262 Displays annotated call-graph via 'gv'
263pprof --gv --focus=Mutex /bin/ls ls.prof
264 Restricts to code paths including a .*Mutex.* entry
265pprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
266 Code paths including Mutex but not string
267pprof --list=getdir /bin/ls ls.prof
268 (Per-line) annotated source listing for getdir()
269pprof --disasm=getdir /bin/ls ls.prof
270 (Per-PC) annotated disassembly for getdir()
271
272pprof http://localhost:1234/
273 Enters "interactive" mode
274pprof --text localhost:1234
275 Outputs one line per procedure for localhost:1234
276pprof --raw localhost:1234 > ./local.raw
277pprof --text ./local.raw
278 Fetches a remote profile for later analysis and then
279 analyzes it in text mode.
280EOF
281}
282
283sub version_string {
284 return <<EOF
285pprof (part of gperftools $PPROF_VERSION)
286
287Copyright 1998-2007 Google Inc.
288
289This is BSD licensed software; see the source for copying conditions
290and license information.
291There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
292PARTICULAR PURPOSE.
293EOF
294}
295
296sub usage {
297 my $msg = shift;
298 print STDERR "$msg\n\n";
299 print STDERR usage_string();
300 exit(1);
301}
302
303sub Init() {
304 # Setup tmp-file name and handler to clean it up.
305 # We do this in the very beginning so that we can use
306 # error() and cleanup() function anytime here after.
307 $main::tmpfile_sym = "/tmp/pprof$$.sym";
308 $main::tmpfile_ps = "/tmp/pprof$$";
309 $main::next_tmpfile = 0;
310 $SIG{'INT'} = \&sighandler;
311
312 # Cache from filename/linenumber to source code
313 $main::source_cache = ();
314
315 $main::opt_help = 0;
316 $main::opt_version = 0;
317 $main::opt_show_addresses = 0;
318 $main::opt_no_auto_signal_frames = 0;
319
320 $main::opt_cum = 0;
321 $main::opt_base = '';
322 $main::opt_addresses = 0;
323 $main::opt_lines = 0;
324 $main::opt_functions = 0;
325 $main::opt_files = 0;
326 $main::opt_lib_prefix = "";
327
328 $main::opt_text = 0;
329 $main::opt_stacks = 0;
330 $main::opt_callgrind = 0;
331 $main::opt_list = "";
332 $main::opt_disasm = "";
333 $main::opt_symbols = 0;
334 $main::opt_gv = 0;
335 $main::opt_evince = 0;
336 $main::opt_web = 0;
337 $main::opt_dot = 0;
338 $main::opt_ps = 0;
339 $main::opt_pdf = 0;
340 $main::opt_gif = 0;
341 $main::opt_svg = 0;
342 $main::opt_raw = 0;
343 $main::opt_collapsed = 0;
344
345 $main::opt_nodecount = 80;
346 $main::opt_nodefraction = 0.005;
347 $main::opt_edgefraction = 0.001;
348 $main::opt_maxdegree = 8;
349 $main::opt_focus = '';
350 $main::opt_ignore = '';
351 $main::opt_scale = 0;
352 $main::opt_heapcheck = 0;
353 $main::opt_seconds = 30;
354 $main::opt_lib = "";
355
356 $main::opt_inuse_space = 0;
357 $main::opt_inuse_objects = 0;
358 $main::opt_alloc_space = 0;
359 $main::opt_alloc_objects = 0;
360 $main::opt_show_bytes = 0;
361 $main::opt_drop_negative = 0;
362 $main::opt_interactive = 0;
363
364 $main::opt_total_delay = 0;
365 $main::opt_contentions = 0;
366 $main::opt_mean_delay = 0;
367
368 $main::opt_tools = "";
369 $main::opt_debug = 0;
370 $main::opt_test = 0;
371
372 # Do not strip template argument in function names
373 $main::opt_no_strip_temp = 0;
374
375 # These are undocumented flags used only by unittests.
376 $main::opt_test_stride = 0;
377
378 # Are we using $SYMBOL_PAGE?
379 $main::use_symbol_page = 0;
380
381 # Files returned by TempName.
382 %main::tempnames = ();
383
384 # Type of profile we are dealing with
385 # Supported types:
386 # cpu
387 # heap
388 # growth
389 # contention
390 $main::profile_type = ''; # Empty type means "unknown"
391
392 GetOptions("help!" => \$main::opt_help,
393 "version!" => \$main::opt_version,
394 "show_addresses!"=> \$main::opt_show_addresses,
395 "no-auto-signal-frm!"=> \$main::opt_no_auto_signal_frames,
396 "cum!" => \$main::opt_cum,
397 "base=s" => \$main::opt_base,
398 "seconds=i" => \$main::opt_seconds,
399 "add_lib=s" => \$main::opt_lib,
400 "lib_prefix=s" => \$main::opt_lib_prefix,
401 "functions!" => \$main::opt_functions,
402 "lines!" => \$main::opt_lines,
403 "addresses!" => \$main::opt_addresses,
404 "files!" => \$main::opt_files,
405 "text!" => \$main::opt_text,
406 "stacks!" => \$main::opt_stacks,
407 "callgrind!" => \$main::opt_callgrind,
408 "list=s" => \$main::opt_list,
409 "disasm=s" => \$main::opt_disasm,
410 "symbols!" => \$main::opt_symbols,
411 "gv!" => \$main::opt_gv,
412 "evince!" => \$main::opt_evince,
413 "web!" => \$main::opt_web,
414 "dot!" => \$main::opt_dot,
415 "ps!" => \$main::opt_ps,
416 "pdf!" => \$main::opt_pdf,
417 "svg!" => \$main::opt_svg,
418 "gif!" => \$main::opt_gif,
419 "raw!" => \$main::opt_raw,
420 "collapsed!" => \$main::opt_collapsed,
421 "interactive!" => \$main::opt_interactive,
422 "nodecount=i" => \$main::opt_nodecount,
423 "nodefraction=f" => \$main::opt_nodefraction,
424 "edgefraction=f" => \$main::opt_edgefraction,
425 "maxdegree=i" => \$main::opt_maxdegree,
426 "focus=s" => \$main::opt_focus,
427 "ignore=s" => \$main::opt_ignore,
428 "scale=i" => \$main::opt_scale,
429 "heapcheck" => \$main::opt_heapcheck,
430 "inuse_space!" => \$main::opt_inuse_space,
431 "inuse_objects!" => \$main::opt_inuse_objects,
432 "alloc_space!" => \$main::opt_alloc_space,
433 "alloc_objects!" => \$main::opt_alloc_objects,
434 "show_bytes!" => \$main::opt_show_bytes,
435 "drop_negative!" => \$main::opt_drop_negative,
436 "total_delay!" => \$main::opt_total_delay,
437 "contentions!" => \$main::opt_contentions,
438 "mean_delay!" => \$main::opt_mean_delay,
439 "tools=s" => \$main::opt_tools,
440 "no_strip_temp!" => \$main::opt_no_strip_temp,
441 "test!" => \$main::opt_test,
442 "debug!" => \$main::opt_debug,
443 # Undocumented flags used only by unittests:
444 "test_stride=i" => \$main::opt_test_stride,
445 ) || usage("Invalid option(s)");
446
447 # Deal with the standard --help and --version
448 if ($main::opt_help) {
449 print usage_string();
450 exit(0);
451 }
452
453 if ($main::opt_version) {
454 print version_string();
455 exit(0);
456 }
457
458 # Disassembly/listing/symbols mode requires address-level info
459 if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {
460 $main::opt_functions = 0;
461 $main::opt_lines = 0;
462 $main::opt_addresses = 1;
463 $main::opt_files = 0;
464 }
465
466 # Check heap-profiling flags
467 if ($main::opt_inuse_space +
468 $main::opt_inuse_objects +
469 $main::opt_alloc_space +
470 $main::opt_alloc_objects > 1) {
471 usage("Specify at most on of --inuse/--alloc options");
472 }
473
474 # Check output granularities
475 my $grains =
476 $main::opt_functions +
477 $main::opt_lines +
478 $main::opt_addresses +
479 $main::opt_files +
480 0;
481 if ($grains > 1) {
482 usage("Only specify one output granularity option");
483 }
484 if ($grains == 0) {
485 $main::opt_functions = 1;
486 }
487
488 # Check output modes
489 my $modes =
490 $main::opt_text +
491 $main::opt_callgrind +
492 ($main::opt_list eq '' ? 0 : 1) +
493 ($main::opt_disasm eq '' ? 0 : 1) +
494 ($main::opt_symbols == 0 ? 0 : 1) +
495 $main::opt_gv +
496 $main::opt_evince +
497 $main::opt_web +
498 $main::opt_dot +
499 $main::opt_ps +
500 $main::opt_pdf +
501 $main::opt_svg +
502 $main::opt_gif +
503 $main::opt_raw +
504 $main::opt_collapsed +
505 $main::opt_interactive +
506 0;
507 if ($modes > 1) {
508 usage("Only specify one output mode");
509 }
510 if ($modes == 0) {
511 if (-t STDOUT) { # If STDOUT is a tty, activate interactive mode
512 $main::opt_interactive = 1;
513 } else {
514 $main::opt_text = 1;
515 }
516 }
517
518 if ($main::opt_test) {
519 RunUnitTests();
520 # Should not return
521 exit(1);
522 }
523
524 # Binary name and profile arguments list
525 $main::prog = "";
526 @main::pfile_args = ();
527
528 # Remote profiling without a binary (using $SYMBOL_PAGE instead)
529 if (@ARGV > 0) {
530 if (IsProfileURL($ARGV[0])) {
531 printf STDERR "Using remote profile at $ARGV[0].\n";
532 $main::use_symbol_page = 1;
533 } elsif (IsSymbolizedProfileFile($ARGV[0])) {
534 $main::use_symbolized_profile = 1;
535 $main::prog = $UNKNOWN_BINARY; # will be set later from the profile file
536 }
537 }
538
539 if ($main::use_symbol_page || $main::use_symbolized_profile) {
540 # We don't need a binary!
541 my %disabled = ('--lines' => $main::opt_lines,
542 '--disasm' => $main::opt_disasm);
543 for my $option (keys %disabled) {
544 usage("$option cannot be used without a binary") if $disabled{$option};
545 }
546 # Set $main::prog later...
547 scalar(@ARGV) || usage("Did not specify profile file");
548 } elsif ($main::opt_symbols) {
549 # --symbols needs a binary-name (to run nm on, etc) but not profiles
550 $main::prog = shift(@ARGV) || usage("Did not specify program");
551 } else {
552 $main::prog = shift(@ARGV) || usage("Did not specify program");
553 scalar(@ARGV) || usage("Did not specify profile file");
554 }
555
556 # Parse profile file/location arguments
557 foreach my $farg (@ARGV) {
558 if ($farg =~ m/(.*)\@([0-9]+)(|\/.*)$/ ) {
559 my $machine = $1;
560 my $num_machines = $2;
561 my $path = $3;
562 for (my $i = 0; $i < $num_machines; $i++) {
563 unshift(@main::pfile_args, "$i.$machine$path");
564 }
565 } else {
566 unshift(@main::pfile_args, $farg);
567 }
568 }
569
570 if ($main::use_symbol_page) {
571 unless (IsProfileURL($main::pfile_args[0])) {
572 error("The first profile should be a remote form to use $SYMBOL_PAGE\n");
573 }
574 CheckSymbolPage();
575 $main::prog = FetchProgramName();
576 } elsif (!$main::use_symbolized_profile) { # may not need objtools!
577 ConfigureObjTools($main::prog)
578 }
579
580 # Break the opt_lib_prefix into the prefix_list array
581 @prefix_list = split (',', $main::opt_lib_prefix);
582
583 # Remove trailing / from the prefixes, in the list to prevent
584 # searching things like /my/path//lib/mylib.so
585 foreach (@prefix_list) {
586 s|/+$||;
587 }
588}
589
590sub Main() {
591 Init();
592 $main::collected_profile = undef;
593 @main::profile_files = ();
594 $main::op_time = time();
595
596 # Printing symbols is special and requires a lot less info that most.
597 if ($main::opt_symbols) {
598 PrintSymbols(*STDIN); # Get /proc/maps and symbols output from stdin
599 return;
600 }
601
602 # Fetch all profile data
603 FetchDynamicProfiles();
604
605 # this will hold symbols that we read from the profile files
606 my $symbol_map = {};
607
608 # Read one profile, pick the last item on the list
609 my $data = ReadProfile($main::prog, pop(@main::profile_files));
610 my $profile = $data->{profile};
611 my $pcs = $data->{pcs};
612 my $libs = $data->{libs}; # Info about main program and shared libraries
613 $symbol_map = MergeSymbols($symbol_map, $data->{symbols});
614
615 # Add additional profiles, if available.
616 if (scalar(@main::profile_files) > 0) {
617 foreach my $pname (@main::profile_files) {
618 my $data2 = ReadProfile($main::prog, $pname);
619 $profile = AddProfile($profile, $data2->{profile});
620 $pcs = AddPcs($pcs, $data2->{pcs});
621 $symbol_map = MergeSymbols($symbol_map, $data2->{symbols});
622 }
623 }
624
625 # Subtract base from profile, if specified
626 if ($main::opt_base ne '') {
627 my $base = ReadProfile($main::prog, $main::opt_base);
628 $profile = SubtractProfile($profile, $base->{profile});
629 $pcs = AddPcs($pcs, $base->{pcs});
630 $symbol_map = MergeSymbols($symbol_map, $base->{symbols});
631 }
632
633 # Get total data in profile
634 my $total = TotalProfile($profile);
635
636 # Collect symbols
637 my $symbols;
638 if ($main::use_symbolized_profile) {
639 $symbols = FetchSymbols($pcs, $symbol_map);
640 } elsif ($main::use_symbol_page) {
641 $symbols = FetchSymbols($pcs);
642 } else {
643 # TODO(csilvers): $libs uses the /proc/self/maps data from profile1,
644 # which may differ from the data from subsequent profiles, especially
645 # if they were run on different machines. Use appropriate libs for
646 # each pc somehow.
647 $symbols = ExtractSymbols($libs, $pcs);
648 }
649
650 # Remove uniniteresting stack items
651 $profile = RemoveUninterestingFrames($symbols, $profile);
652
653 # Focus?
654 if ($main::opt_focus ne '') {
655 $profile = FocusProfile($symbols, $profile, $main::opt_focus);
656 }
657
658 # Ignore?
659 if ($main::opt_ignore ne '') {
660 $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
661 }
662
663 my $calls = ExtractCalls($symbols, $profile);
664
665 # Reduce profiles to required output granularity, and also clean
666 # each stack trace so a given entry exists at most once.
667 my $reduced = ReduceProfile($symbols, $profile);
668
669 # Get derived profiles
670 my $flat = FlatProfile($reduced);
671 my $cumulative = CumulativeProfile($reduced);
672
673 # Print
674 if (!$main::opt_interactive) {
675 if ($main::opt_disasm) {
676 PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);
677 } elsif ($main::opt_list) {
678 PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
679 } elsif ($main::opt_text) {
680 # Make sure the output is empty when have nothing to report
681 # (only matters when --heapcheck is given but we must be
682 # compatible with old branches that did not pass --heapcheck always):
683 if ($total != 0) {
684 printf("Total: %s %s\n", Unparse($total), Units());
685 }
686 if ($main::opt_stacks) {
687 printf("Stacks:\n\n");
688 PrintStacksForText($symbols, $profile);
689 }
690 PrintText($symbols, $flat, $cumulative, -1);
691 } elsif ($main::opt_raw) {
692 PrintSymbolizedProfile($symbols, $profile, $main::prog);
693 } elsif ($main::opt_collapsed) {
694 PrintCollapsedStacks($symbols, $profile);
695 } elsif ($main::opt_callgrind) {
696 PrintCallgrind($calls);
697 } else {
698 if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
699 if ($main::opt_gv) {
700 RunGV(TempName($main::next_tmpfile, "ps"), "");
701 } elsif ($main::opt_evince) {
702 RunEvince(TempName($main::next_tmpfile, "pdf"), "");
703 } elsif ($main::opt_web) {
704 my $tmp = TempName($main::next_tmpfile, "svg");
705 RunWeb($tmp);
706 # The command we run might hand the file name off
707 # to an already running browser instance and then exit.
708 # Normally, we'd remove $tmp on exit (right now),
709 # but fork a child to remove $tmp a little later, so that the
710 # browser has time to load it first.
711 delete $main::tempnames{$tmp};
712 if (fork() == 0) {
713 sleep 5;
714 unlink($tmp);
715 exit(0);
716 }
717 }
718 } else {
719 cleanup();
720 exit(1);
721 }
722 }
723 } else {
724 InteractiveMode($profile, $symbols, $libs, $total);
725 }
726
727 cleanup();
728 exit(0);
729}
730
731##### Entry Point #####
732
733Main();
734
735# Temporary code to detect if we're running on a Goobuntu system.
736# These systems don't have the right stuff installed for the special
737# Readline libraries to work, so as a temporary workaround, we default
738# to using the normal stdio code, rather than the fancier readline-based
739# code
740sub ReadlineMightFail {
741 if (-e '/lib/libtermcap.so.2') {
742 return 0; # libtermcap exists, so readline should be okay
743 } else {
744 return 1;
745 }
746}
747
748sub RunGV {
749 my $fname = shift;
750 my $bg = shift; # "" or " &" if we should run in background
751 if (!system(ShellEscape(@GV, "--version") . " >$dev_null 2>&1")) {
752 # Options using double dash are supported by this gv version.
753 # Also, turn on noantialias to better handle bug in gv for
754 # postscript files with large dimensions.
755 # TODO: Maybe we should not pass the --noantialias flag
756 # if the gv version is known to work properly without the flag.
757 system(ShellEscape(@GV, "--scale=$main::opt_scale", "--noantialias", $fname)
758 . $bg);
759 } else {
760 # Old gv version - only supports options that use single dash.
761 print STDERR ShellEscape(@GV, "-scale", $main::opt_scale) . "\n";
762 system(ShellEscape(@GV, "-scale", "$main::opt_scale", $fname) . $bg);
763 }
764}
765
766sub RunEvince {
767 my $fname = shift;
768 my $bg = shift; # "" or " &" if we should run in background
769 system(ShellEscape(@EVINCE, $fname) . $bg);
770}
771
772sub RunWeb {
773 my $fname = shift;
774 print STDERR "Loading web page file:///$fname\n";
775
776 if (`uname` =~ /Darwin/) {
777 # OS X: open will use standard preference for SVG files.
778 system("/usr/bin/open", $fname);
779 return;
780 }
781
782 if (`uname` =~ /MINGW/) {
783 # Windows(MinGW): open will use standard preference for SVG files.
784 system("cmd", "/c", "start", $fname);
785 return;
786 }
787
788 # Some kind of Unix; try generic symlinks, then specific browsers.
789 # (Stop once we find one.)
790 # Works best if the browser is already running.
791 my @alt = (
792 "/etc/alternatives/gnome-www-browser",
793 "/etc/alternatives/x-www-browser",
794 "google-chrome",
795 "firefox",
796 );
797 foreach my $b (@alt) {
798 if (system($b, $fname) == 0) {
799 return;
800 }
801 }
802
803 print STDERR "Could not load web browser.\n";
804}
805
806sub RunKcachegrind {
807 my $fname = shift;
808 my $bg = shift; # "" or " &" if we should run in background
809 print STDERR "Starting '@KCACHEGRIND " . $fname . $bg . "'\n";
810 system(ShellEscape(@KCACHEGRIND, $fname) . $bg);
811}
812
813
814##### Interactive helper routines #####
815
816sub InteractiveMode {
817 $| = 1; # Make output unbuffered for interactive mode
818 my ($orig_profile, $symbols, $libs, $total) = @_;
819
820 print STDERR "Welcome to pprof! For help, type 'help'.\n";
821
822 # Use ReadLine if it's installed and input comes from a console.
823 if ( -t STDIN &&
824 !ReadlineMightFail() &&
825 defined(eval {require Term::ReadLine}) ) {
826 my $term = new Term::ReadLine 'pprof';
827 while ( defined ($_ = $term->readline('(pprof) '))) {
828 $term->addhistory($_) if /\S/;
829 if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
830 last; # exit when we get an interactive command to quit
831 }
832 }
833 } else { # don't have readline
834 while (1) {
835 print STDERR "(pprof) ";
836 $_ = <STDIN>;
837 last if ! defined $_ ;
838 s/\r//g; # turn windows-looking lines into unix-looking lines
839
840 # Save some flags that might be reset by InteractiveCommand()
841 my $save_opt_lines = $main::opt_lines;
842
843 if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
844 last; # exit when we get an interactive command to quit
845 }
846
847 # Restore flags
848 $main::opt_lines = $save_opt_lines;
849 }
850 }
851}
852
853# Takes two args: orig profile, and command to run.
854# Returns 1 if we should keep going, or 0 if we were asked to quit
855sub InteractiveCommand {
856 my($orig_profile, $symbols, $libs, $total, $command) = @_;
857 $_ = $command; # just to make future m//'s easier
858 if (!defined($_)) {
859 print STDERR "\n";
860 return 0;
861 }
862 if (m/^\s*quit/) {
863 return 0;
864 }
865 if (m/^\s*help/) {
866 InteractiveHelpMessage();
867 return 1;
868 }
869 # Clear all the mode options -- mode is controlled by "$command"
870 $main::opt_text = 0;
871 $main::opt_callgrind = 0;
872 $main::opt_disasm = 0;
873 $main::opt_list = 0;
874 $main::opt_gv = 0;
875 $main::opt_evince = 0;
876 $main::opt_cum = 0;
877
878 if (m/^\s*(text|top)(\d*)\s*(.*)/) {
879 $main::opt_text = 1;
880
881 my $line_limit = ($2 ne "") ? int($2) : 10;
882
883 my $routine;
884 my $ignore;
885 ($routine, $ignore) = ParseInteractiveArgs($3);
886
887 my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
888 my $reduced = ReduceProfile($symbols, $profile);
889
890 # Get derived profiles
891 my $flat = FlatProfile($reduced);
892 my $cumulative = CumulativeProfile($reduced);
893
894 PrintText($symbols, $flat, $cumulative, $line_limit);
895 return 1;
896 }
897 if (m/^\s*callgrind\s*([^ \n]*)/) {
898 $main::opt_callgrind = 1;
899
900 # Get derived profiles
901 my $calls = ExtractCalls($symbols, $orig_profile);
902 my $filename = $1;
903 if ( $1 eq '' ) {
904 $filename = TempName($main::next_tmpfile, "callgrind");
905 }
906 PrintCallgrind($calls, $filename);
907 if ( $1 eq '' ) {
908 RunKcachegrind($filename, " & ");
909 $main::next_tmpfile++;
910 }
911
912 return 1;
913 }
914 if (m/^\s*(web)?list\s*(.+)/) {
915 my $html = (defined($1) && ($1 eq "web"));
916 $main::opt_list = 1;
917
918 my $routine;
919 my $ignore;
920 ($routine, $ignore) = ParseInteractiveArgs($2);
921
922 my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
923 my $reduced = ReduceProfile($symbols, $profile);
924
925 # Get derived profiles
926 my $flat = FlatProfile($reduced);
927 my $cumulative = CumulativeProfile($reduced);
928
929 PrintListing($total, $libs, $flat, $cumulative, $routine, $html);
930 return 1;
931 }
932 if (m/^\s*disasm\s*(.+)/) {
933 $main::opt_disasm = 1;
934
935 my $routine;
936 my $ignore;
937 ($routine, $ignore) = ParseInteractiveArgs($1);
938
939 # Process current profile to account for various settings
940 my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
941 my $reduced = ReduceProfile($symbols, $profile);
942
943 # Get derived profiles
944 my $flat = FlatProfile($reduced);
945 my $cumulative = CumulativeProfile($reduced);
946
947 PrintDisassembly($libs, $flat, $cumulative, $routine);
948 return 1;
949 }
950 if (m/^\s*(gv|web|evince)\s*(.*)/) {
951 $main::opt_gv = 0;
952 $main::opt_evince = 0;
953 $main::opt_web = 0;
954 if ($1 eq "gv") {
955 $main::opt_gv = 1;
956 } elsif ($1 eq "evince") {
957 $main::opt_evince = 1;
958 } elsif ($1 eq "web") {
959 $main::opt_web = 1;
960 }
961
962 my $focus;
963 my $ignore;
964 ($focus, $ignore) = ParseInteractiveArgs($2);
965
966 # Process current profile to account for various settings
967 my $profile = ProcessProfile($total, $orig_profile, $symbols,
968 $focus, $ignore);
969 my $reduced = ReduceProfile($symbols, $profile);
970
971 # Get derived profiles
972 my $flat = FlatProfile($reduced);
973 my $cumulative = CumulativeProfile($reduced);
974
975 if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
976 if ($main::opt_gv) {
977 RunGV(TempName($main::next_tmpfile, "ps"), " &");
978 } elsif ($main::opt_evince) {
979 RunEvince(TempName($main::next_tmpfile, "pdf"), " &");
980 } elsif ($main::opt_web) {
981 RunWeb(TempName($main::next_tmpfile, "svg"));
982 }
983 $main::next_tmpfile++;
984 }
985 return 1;
986 }
987 if (m/^\s*$/) {
988 return 1;
989 }
990 print STDERR "Unknown command: try 'help'.\n";
991 return 1;
992}
993
994
995sub ProcessProfile {
996 my $total_count = shift;
997 my $orig_profile = shift;
998 my $symbols = shift;
999 my $focus = shift;
1000 my $ignore = shift;
1001
1002 # Process current profile to account for various settings
1003 my $profile = $orig_profile;
1004 printf("Total: %s %s\n", Unparse($total_count), Units());
1005 if ($focus ne '') {
1006 $profile = FocusProfile($symbols, $profile, $focus);
1007 my $focus_count = TotalProfile($profile);
1008 printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
1009 $focus,
1010 Unparse($focus_count), Units(),
1011 Unparse($total_count), ($focus_count*100.0) / $total_count);
1012 }
1013 if ($ignore ne '') {
1014 $profile = IgnoreProfile($symbols, $profile, $ignore);
1015 my $ignore_count = TotalProfile($profile);
1016 printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
1017 $ignore,
1018 Unparse($ignore_count), Units(),
1019 Unparse($total_count),
1020 ($ignore_count*100.0) / $total_count);
1021 }
1022
1023 return $profile;
1024}
1025
1026sub InteractiveHelpMessage {
1027 print STDERR <<ENDOFHELP;
1028Interactive pprof mode
1029
1030Commands:
1031 gv
1032 gv [focus] [-ignore1] [-ignore2]
1033 Show graphical hierarchical display of current profile. Without
1034 any arguments, shows all samples in the profile. With the optional
1035 "focus" argument, restricts the samples shown to just those where
1036 the "focus" regular expression matches a routine name on the stack
1037 trace.
1038
1039 web
1040 web [focus] [-ignore1] [-ignore2]
1041 Like GV, but displays profile in your web browser instead of using
1042 Ghostview. Works best if your web browser is already running.
1043 To change the browser that gets used:
1044 On Linux, set the /etc/alternatives/gnome-www-browser symlink.
1045 On OS X, change the Finder association for SVG files.
1046
1047 list [routine_regexp] [-ignore1] [-ignore2]
1048 Show source listing of routines whose names match "routine_regexp"
1049
1050 weblist [routine_regexp] [-ignore1] [-ignore2]
1051 Displays a source listing of routines whose names match "routine_regexp"
1052 in a web browser. You can click on source lines to view the
1053 corresponding disassembly.
1054
1055 top [--cum] [-ignore1] [-ignore2]
1056 top20 [--cum] [-ignore1] [-ignore2]
1057 top37 [--cum] [-ignore1] [-ignore2]
1058 Show top lines ordered by flat profile count, or cumulative count
1059 if --cum is specified. If a number is present after 'top', the
1060 top K routines will be shown (defaults to showing the top 10)
1061
1062 disasm [routine_regexp] [-ignore1] [-ignore2]
1063 Show disassembly of routines whose names match "routine_regexp",
1064 annotated with sample counts.
1065
1066 callgrind
1067 callgrind [filename]
1068 Generates callgrind file. If no filename is given, kcachegrind is called.
1069
1070 help - This listing
1071 quit or ^D - End pprof
1072
1073For commands that accept optional -ignore tags, samples where any routine in
1074the stack trace matches the regular expression in any of the -ignore
1075parameters will be ignored.
1076
1077Further pprof details are available at this location (or one similar):
1078
1079 /usr/doc/gperftools-$PPROF_VERSION/cpu_profiler.html
1080 /usr/doc/gperftools-$PPROF_VERSION/heap_profiler.html
1081
1082ENDOFHELP
1083}
1084sub ParseInteractiveArgs {
1085 my $args = shift;
1086 my $focus = "";
1087 my $ignore = "";
1088 my @x = split(/ +/, $args);
1089 foreach $a (@x) {
1090 if ($a =~ m/^(--|-)lines$/) {
1091 $main::opt_lines = 1;
1092 } elsif ($a =~ m/^(--|-)cum$/) {
1093 $main::opt_cum = 1;
1094 } elsif ($a =~ m/^-(.*)/) {
1095 $ignore .= (($ignore ne "") ? "|" : "" ) . $1;
1096 } else {
1097 $focus .= (($focus ne "") ? "|" : "" ) . $a;
1098 }
1099 }
1100 if ($ignore ne "") {
1101 print STDERR "Ignoring samples in call stacks that match '$ignore'\n";
1102 }
1103 return ($focus, $ignore);
1104}
1105
1106##### Output code #####
1107
1108sub TempName {
1109 my $fnum = shift;
1110 my $ext = shift;
1111 my $file = "$main::tmpfile_ps.$fnum.$ext";
1112 $main::tempnames{$file} = 1;
1113 return $file;
1114}
1115
1116# Print profile data in packed binary format (64-bit) to standard out
1117sub PrintProfileData {
1118 my $profile = shift;
1119 my $big_endian = pack("L", 1) eq pack("N", 1);
1120 # print header (64-bit style)
1121 # (zero) (header-size) (version) (sample-period) (zero)
1122 if ($big_endian) {
1123 print pack('L*', 0, 0, 0, 3, 0, 0, 0, 1, 0, 0);
1124 }
1125 else {
1126 print pack('L*', 0, 0, 3, 0, 0, 0, 1, 0, 0, 0);
1127 }
1128
1129 foreach my $k (keys(%{$profile})) {
1130 my $count = $profile->{$k};
1131 my @addrs = split(/\n/, $k);
1132 if ($#addrs >= 0) {
1133 my $depth = $#addrs + 1;
1134 # int(foo / 2**32) is the only reliable way to get rid of bottom
1135 # 32 bits on both 32- and 64-bit systems.
1136 if ($big_endian) {
1137 print pack('L*', int($count / 2**32), $count & 0xFFFFFFFF);
1138 print pack('L*', int($depth / 2**32), $depth & 0xFFFFFFFF);
1139 }
1140 else {
1141 print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));
1142 print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));
1143 }
1144
1145 foreach my $full_addr (@addrs) {
1146 my $addr = $full_addr;
1147 $addr =~ s/0x0*//; # strip off leading 0x, zeroes
1148 if (length($addr) > 16) {
1149 print STDERR "Invalid address in profile: $full_addr\n";
1150 next;
1151 }
1152 my $low_addr = substr($addr, -8); # get last 8 hex chars
1153 my $high_addr = substr($addr, -16, 8); # get up to 8 more hex chars
1154 if ($big_endian) {
1155 print pack('L*', hex('0x' . $high_addr), hex('0x' . $low_addr));
1156 }
1157 else {
1158 print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));
1159 }
1160 }
1161 }
1162 }
1163}
1164
1165# Print symbols and profile data
1166sub PrintSymbolizedProfile {
1167 my $symbols = shift;
1168 my $profile = shift;
1169 my $prog = shift;
1170
1171 $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash
1172 my $symbol_marker = $&;
1173
1174 print '--- ', $symbol_marker, "\n";
1175 if (defined($prog)) {
1176 print 'binary=', $prog, "\n";
1177 }
1178 while (my ($pc, $name) = each(%{$symbols})) {
1179 my $sep = ' ';
1180 print '0x', $pc;
1181 # We have a list of function names, which include the inlined
1182 # calls. They are separated (and terminated) by --, which is
1183 # illegal in function names.
1184 for (my $j = 2; $j <= $#{$name}; $j += 3) {
1185 print $sep, $name->[$j];
1186 $sep = '--';
1187 }
1188 print "\n";
1189 }
1190 print '---', "\n";
1191
1192 $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash
1193 my $profile_marker = $&;
1194 print '--- ', $profile_marker, "\n";
1195 if (defined($main::collected_profile)) {
1196 # if used with remote fetch, simply dump the collected profile to output.
1197 open(SRC, "<$main::collected_profile");
1198 while (<SRC>) {
1199 print $_;
1200 }
1201 close(SRC);
1202 } else {
1203 # dump a cpu-format profile to standard out
1204 PrintProfileData($profile);
1205 }
1206}
1207
1208# Print text output
1209sub PrintText {
1210 my $symbols = shift;
1211 my $flat = shift;
1212 my $cumulative = shift;
1213 my $line_limit = shift;
1214
1215 if ($main::opt_stacks && @stackTraces) {
1216 foreach (sort { (split " ", $b)[1] <=> (split " ", $a)[1]; } @stackTraces) {
1217 print "$_\n" if $main::opt_debug;
1218 my ($n1, $s1, $n2, $s2, @addrs) = split;
1219 print "Leak of $s1 bytes in $n1 objects allocated from:\n";
1220 foreach my $pcstr (@addrs) {
1221 $pcstr =~ s/^0x//;
1222 my $sym;
1223 if (! defined $symbols->{$pcstr}) {
1224 $sym = "unknown";
1225 } else {
1226 $sym = "$symbols->{$pcstr}[0] $symbols->{$pcstr}[1]";
1227 }
1228 print "\t@ $pcstr $sym\n";
1229 }
1230 }
1231 print "\n";
1232 }
1233
1234 my $total = TotalProfile($flat);
1235
1236 # Which profile to sort by?
1237 my $s = $main::opt_cum ? $cumulative : $flat;
1238
1239 my $running_sum = 0;
1240 my $lines = 0;
1241 foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }
1242 keys(%{$cumulative})) {
1243 my $f = GetEntry($flat, $k);
1244 my $c = GetEntry($cumulative, $k);
1245 $running_sum += $f;
1246
1247 my $sym = $k;
1248 if (exists($symbols->{$k})) {
1249 $sym = $symbols->{$k}->[0] . " " . $symbols->{$k}->[1];
1250 if ($main::opt_addresses) {
1251 $sym = $k . " " . $sym;
1252 }
1253 }
1254
1255 if ($f != 0 || $c != 0) {
1256 printf("%8s %6s %6s %8s %6s %s\n",
1257 Unparse($f),
1258 Percent($f, $total),
1259 Percent($running_sum, $total),
1260 Unparse($c),
1261 Percent($c, $total),
1262 $sym);
1263 }
1264 $lines++;
1265 last if ($line_limit >= 0 && $lines >= $line_limit);
1266 }
1267}
1268
1269# Callgrind format has a compression for repeated function and file
1270# names. You show the name the first time, and just use its number
1271# subsequently. This can cut down the file to about a third or a
1272# quarter of its uncompressed size. $key and $val are the key/value
1273# pair that would normally be printed by callgrind; $map is a map from
1274# value to number.
1275sub CompressedCGName {
1276 my($key, $val, $map) = @_;
1277 my $idx = $map->{$val};
1278 # For very short keys, providing an index hurts rather than helps.
1279 if (length($val) <= 3) {
1280 return "$key=$val\n";
1281 } elsif (defined($idx)) {
1282 return "$key=($idx)\n";
1283 } else {
1284 # scalar(keys $map) gives the number of items in the map.
1285 $idx = scalar(keys(%{$map})) + 1;
1286 $map->{$val} = $idx;
1287 return "$key=($idx) $val\n";
1288 }
1289}
1290
1291# Print the call graph in a way that's suiteable for callgrind.
1292sub PrintCallgrind {
1293 my $calls = shift;
1294 my $filename;
1295 my %filename_to_index_map;
1296 my %fnname_to_index_map;
1297
1298 if ($main::opt_interactive) {
1299 $filename = shift;
1300 print STDERR "Writing callgrind file to '$filename'.\n"
1301 } else {
1302 $filename = "&STDOUT";
1303 }
1304 open(CG, ">$filename");
1305 printf CG ("events: Hits\n\n");
1306 foreach my $call ( map { $_->[0] }
1307 sort { $a->[1] cmp $b ->[1] ||
1308 $a->[2] <=> $b->[2] }
1309 map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
1310 [$_, $1, $2] }
1311 keys %$calls ) {
1312 my $count = int($calls->{$call});
1313 $call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
1314 my ( $caller_file, $caller_line, $caller_function,
1315 $callee_file, $callee_line, $callee_function ) =
1316 ( $1, $2, $3, $5, $6, $7 );
1317
1318 # TODO(csilvers): for better compression, collect all the
1319 # caller/callee_files and functions first, before printing
1320 # anything, and only compress those referenced more than once.
1321 printf CG CompressedCGName("fl", $caller_file, \%filename_to_index_map);
1322 printf CG CompressedCGName("fn", $caller_function, \%fnname_to_index_map);
1323 if (defined $6) {
1324 printf CG CompressedCGName("cfl", $callee_file, \%filename_to_index_map);
1325 printf CG CompressedCGName("cfn", $callee_function, \%fnname_to_index_map);
1326 printf CG ("calls=$count $callee_line\n");
1327 }
1328 printf CG ("$caller_line $count\n\n");
1329 }
1330}
1331
1332# Print disassembly for all all routines that match $main::opt_disasm
1333sub PrintDisassembly {
1334 my $libs = shift;
1335 my $flat = shift;
1336 my $cumulative = shift;
1337 my $disasm_opts = shift;
1338
1339 my $total = TotalProfile($flat);
1340
1341 foreach my $lib (@{$libs}) {
1342 my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);
1343 my $offset = AddressSub($lib->[1], $lib->[3]);
1344 foreach my $routine (sort ByName keys(%{$symbol_table})) {
1345 my $start_addr = $symbol_table->{$routine}->[0];
1346 my $end_addr = $symbol_table->{$routine}->[1];
1347 # See if there are any samples in this routine
1348 my $length = hex(AddressSub($end_addr, $start_addr));
1349 my $addr = AddressAdd($start_addr, $offset);
1350 for (my $i = 0; $i < $length; $i++) {
1351 if (defined($cumulative->{$addr})) {
1352 PrintDisassembledFunction($lib->[0], $offset,
1353 $routine, $flat, $cumulative,
1354 $start_addr, $end_addr, $total);
1355 last;
1356 }
1357 $addr = AddressInc($addr);
1358 }
1359 }
1360 }
1361}
1362
1363# Return reference to array of tuples of the form:
1364# [start_address, filename, linenumber, instruction, limit_address]
1365# E.g.,
1366# ["0x806c43d", "/foo/bar.cc", 131, "ret", "0x806c440"]
1367sub Disassemble {
1368 my $prog = shift;
1369 my $offset = shift;
1370 my $start_addr = shift;
1371 my $end_addr = shift;
1372
1373 my $objdump = $obj_tool_map{"objdump"};
1374 my $cmd = ShellEscape($objdump, "-C", "-d", "-l", "--no-show-raw-insn",
1375 "--start-address=0x$start_addr",
1376 "--stop-address=0x$end_addr", $prog);
1377 open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
1378 my @result = ();
1379 my $filename = "";
1380 my $linenumber = -1;
1381 my $last = ["", "", "", ""];
1382 while (<OBJDUMP>) {
1383 s/\r//g; # turn windows-looking lines into unix-looking lines
1384 chop;
1385 if (m|\s*([^:\s]+):(\d+)\s*$|) {
1386 # Location line of the form:
1387 # <filename>:<linenumber>
1388 $filename = $1;
1389 $linenumber = $2;
1390 } elsif (m/^ +([0-9a-f]+):\s*(.*)/) {
1391 # Disassembly line -- zero-extend address to full length
1392 my $addr = HexExtend($1);
1393 my $k = AddressAdd($addr, $offset);
1394 $last->[4] = $k; # Store ending address for previous instruction
1395 $last = [$k, $filename, $linenumber, $2, $end_addr];
1396 push(@result, $last);
1397 }
1398 }
1399 close(OBJDUMP);
1400 return @result;
1401}
1402
1403# The input file should contain lines of the form /proc/maps-like
1404# output (same format as expected from the profiles) or that looks
1405# like hex addresses (like "0xDEADBEEF"). We will parse all
1406# /proc/maps output, and for all the hex addresses, we will output
1407# "short" symbol names, one per line, in the same order as the input.
1408sub PrintSymbols {
1409 my $maps_and_symbols_file = shift;
1410
1411 # ParseLibraries expects pcs to be in a set. Fine by us...
1412 my @pclist = (); # pcs in sorted order
1413 my $pcs = {};
1414 my $map = "";
1415 foreach my $line (<$maps_and_symbols_file>) {
1416 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
1417 if ($line =~ /\b(0x[0-9a-f]+)\b/i) {
1418 push(@pclist, HexExtend($1));
1419 $pcs->{$pclist[-1]} = 1;
1420 } else {
1421 $map .= $line;
1422 }
1423 }
1424
1425 my $libs = ParseLibraries($main::prog, $map, $pcs);
1426 my $symbols = ExtractSymbols($libs, $pcs);
1427
1428 foreach my $pc (@pclist) {
1429 # ->[0] is the shortname, ->[2] is the full name
1430 print(($symbols->{$pc}->[0] || "??") . "\n");
1431 }
1432}
1433
1434
1435# For sorting functions by name
1436sub ByName {
1437 return ShortFunctionName($a) cmp ShortFunctionName($b);
1438}
1439
1440# Print source-listing for all all routines that match $list_opts
1441sub PrintListing {
1442 my $total = shift;
1443 my $libs = shift;
1444 my $flat = shift;
1445 my $cumulative = shift;
1446 my $list_opts = shift;
1447 my $html = shift;
1448
1449 my $output = \*STDOUT;
1450 my $fname = "";
1451
1452 if ($html) {
1453 # Arrange to write the output to a temporary file
1454 $fname = TempName($main::next_tmpfile, "html");
1455 $main::next_tmpfile++;
1456 if (!open(TEMP, ">$fname")) {
1457 print STDERR "$fname: $!\n";
1458 return;
1459 }
1460 $output = \*TEMP;
1461 print $output HtmlListingHeader();
1462 printf $output ("<div class=\"legend\">%s<br>Total: %s %s</div>\n",
1463 $main::prog, Unparse($total), Units());
1464 }
1465
1466 my $listed = 0;
1467 foreach my $lib (@{$libs}) {
1468 my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);
1469 my $offset = AddressSub($lib->[1], $lib->[3]);
1470 foreach my $routine (sort ByName keys(%{$symbol_table})) {
1471 # Print if there are any samples in this routine
1472 my $start_addr = $symbol_table->{$routine}->[0];
1473 my $end_addr = $symbol_table->{$routine}->[1];
1474 my $length = hex(AddressSub($end_addr, $start_addr));
1475 my $addr = AddressAdd($start_addr, $offset);
1476 for (my $i = 0; $i < $length; $i++) {
1477 if (defined($cumulative->{$addr})) {
1478 $listed += PrintSource(
1479 $lib->[0], $offset,
1480 $routine, $flat, $cumulative,
1481 $start_addr, $end_addr,
1482 $html,
1483 $output);
1484 last;
1485 }
1486 $addr = AddressInc($addr);
1487 }
1488 }
1489 }
1490
1491 if ($html) {
1492 if ($listed > 0) {
1493 print $output HtmlListingFooter();
1494 close($output);
1495 RunWeb($fname);
1496 } else {
1497 close($output);
1498 unlink($fname);
1499 }
1500 }
1501}
1502
1503sub HtmlListingHeader {
1504 return <<'EOF';
1505<DOCTYPE html>
1506<html>
1507<head>
1508<title>Pprof listing</title>
1509<style type="text/css">
1510body {
1511 font-family: sans-serif;
1512}
1513h1 {
1514 font-size: 1.5em;
1515 margin-bottom: 4px;
1516}
1517.legend {
1518 font-size: 1.25em;
1519}
1520.line {
1521 color: #aaaaaa;
1522}
1523.nop {
1524 color: #aaaaaa;
1525}
1526.unimportant {
1527 color: #cccccc;
1528}
1529.disasmloc {
1530 color: #000000;
1531}
1532.deadsrc {
1533 cursor: pointer;
1534}
1535.deadsrc:hover {
1536 background-color: #eeeeee;
1537}
1538.livesrc {
1539 color: #0000ff;
1540 cursor: pointer;
1541}
1542.livesrc:hover {
1543 background-color: #eeeeee;
1544}
1545.asm {
1546 color: #008800;
1547 display: none;
1548}
1549</style>
1550<script type="text/javascript">
1551function pprof_toggle_asm(e) {
1552 var target;
1553 if (!e) e = window.event;
1554 if (e.target) target = e.target;
1555 else if (e.srcElement) target = e.srcElement;
1556
1557 if (target) {
1558 var asm = target.nextSibling;
1559 if (asm && asm.className == "asm") {
1560 asm.style.display = (asm.style.display == "block" ? "" : "block");
1561 e.preventDefault();
1562 return false;
1563 }
1564 }
1565}
1566</script>
1567</head>
1568<body>
1569EOF
1570}
1571
1572sub HtmlListingFooter {
1573 return <<'EOF';
1574</body>
1575</html>
1576EOF
1577}
1578
1579sub HtmlEscape {
1580 my $text = shift;
1581 $text =~ s/&/&amp;/g;
1582 $text =~ s/</&lt;/g;
1583 $text =~ s/>/&gt;/g;
1584 return $text;
1585}
1586
1587# Returns the indentation of the line, if it has any non-whitespace
1588# characters. Otherwise, returns -1.
1589sub Indentation {
1590 my $line = shift;
1591 if (m/^(\s*)\S/) {
1592 return length($1);
1593 } else {
1594 return -1;
1595 }
1596}
1597
1598# If the symbol table contains inlining info, Disassemble() may tag an
1599# instruction with a location inside an inlined function. But for
1600# source listings, we prefer to use the location in the function we
1601# are listing. So use MapToSymbols() to fetch full location
1602# information for each instruction and then pick out the first
1603# location from a location list (location list contains callers before
1604# callees in case of inlining).
1605#
1606# After this routine has run, each entry in $instructions contains:
1607# [0] start address
1608# [1] filename for function we are listing
1609# [2] line number for function we are listing
1610# [3] disassembly
1611# [4] limit address
1612# [5] most specific filename (may be different from [1] due to inlining)
1613# [6] most specific line number (may be different from [2] due to inlining)
1614sub GetTopLevelLineNumbers {
1615 my ($lib, $offset, $instructions) = @_;
1616 my $pcs = [];
1617 for (my $i = 0; $i <= $#{$instructions}; $i++) {
1618 push(@{$pcs}, $instructions->[$i]->[0]);
1619 }
1620 my $symbols = {};
1621 MapToSymbols($lib, $offset, $pcs, $symbols);
1622 for (my $i = 0; $i <= $#{$instructions}; $i++) {
1623 my $e = $instructions->[$i];
1624 push(@{$e}, $e->[1]);
1625 push(@{$e}, $e->[2]);
1626 my $addr = $e->[0];
1627 my $sym = $symbols->{$addr};
1628 if (defined($sym)) {
1629 if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\d+)$/) {
1630 $e->[1] = $1; # File name
1631 $e->[2] = $2; # Line number
1632 }
1633 }
1634 }
1635}
1636
1637# Print source-listing for one routine
1638sub PrintSource {
1639 my $prog = shift;
1640 my $offset = shift;
1641 my $routine = shift;
1642 my $flat = shift;
1643 my $cumulative = shift;
1644 my $start_addr = shift;
1645 my $end_addr = shift;
1646 my $html = shift;
1647 my $output = shift;
1648
1649 # Disassemble all instructions (just to get line numbers)
1650 my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
1651 GetTopLevelLineNumbers($prog, $offset, \@instructions);
1652
1653 # Hack 1: assume that the first source file encountered in the
1654 # disassembly contains the routine
1655 my $filename = undef;
1656 for (my $i = 0; $i <= $#instructions; $i++) {
1657 if ($instructions[$i]->[2] >= 0) {
1658 $filename = $instructions[$i]->[1];
1659 last;
1660 }
1661 }
1662 if (!defined($filename)) {
1663 print STDERR "no filename found in $routine\n";
1664 return 0;
1665 }
1666
1667 # Hack 2: assume that the largest line number from $filename is the
1668 # end of the procedure. This is typically safe since if P1 contains
1669 # an inlined call to P2, then P2 usually occurs earlier in the
1670 # source file. If this does not work, we might have to compute a
1671 # density profile or just print all regions we find.
1672 my $lastline = 0;
1673 for (my $i = 0; $i <= $#instructions; $i++) {
1674 my $f = $instructions[$i]->[1];
1675 my $l = $instructions[$i]->[2];
1676 if (($f eq $filename) && ($l > $lastline)) {
1677 $lastline = $l;
1678 }
1679 }
1680
1681 # Hack 3: assume the first source location from "filename" is the start of
1682 # the source code.
1683 my $firstline = 1;
1684 for (my $i = 0; $i <= $#instructions; $i++) {
1685 if ($instructions[$i]->[1] eq $filename) {
1686 $firstline = $instructions[$i]->[2];
1687 last;
1688 }
1689 }
1690
1691 # Hack 4: Extend last line forward until its indentation is less than
1692 # the indentation we saw on $firstline
1693 my $oldlastline = $lastline;
1694 {
1695 if (!open(FILE, "<$filename")) {
1696 print STDERR "$filename: $!\n";
1697 return 0;
1698 }
1699 my $l = 0;
1700 my $first_indentation = -1;
1701 while (<FILE>) {
1702 s/\r//g; # turn windows-looking lines into unix-looking lines
1703 $l++;
1704 my $indent = Indentation($_);
1705 if ($l >= $firstline) {
1706 if ($first_indentation < 0 && $indent >= 0) {
1707 $first_indentation = $indent;
1708 last if ($first_indentation == 0);
1709 }
1710 }
1711 if ($l >= $lastline && $indent >= 0) {
1712 if ($indent >= $first_indentation) {
1713 $lastline = $l+1;
1714 } else {
1715 last;
1716 }
1717 }
1718 }
1719 close(FILE);
1720 }
1721
1722 # Assign all samples to the range $firstline,$lastline,
1723 # Hack 4: If an instruction does not occur in the range, its samples
1724 # are moved to the next instruction that occurs in the range.
1725 my $samples1 = {}; # Map from line number to flat count
1726 my $samples2 = {}; # Map from line number to cumulative count
1727 my $running1 = 0; # Unassigned flat counts
1728 my $running2 = 0; # Unassigned cumulative counts
1729 my $total1 = 0; # Total flat counts
1730 my $total2 = 0; # Total cumulative counts
1731 my %disasm = (); # Map from line number to disassembly
1732 my $running_disasm = ""; # Unassigned disassembly
1733 my $skip_marker = "---\n";
1734 if ($html) {
1735 $skip_marker = "";
1736 for (my $l = $firstline; $l <= $lastline; $l++) {
1737 $disasm{$l} = "";
1738 }
1739 }
1740 my $last_dis_filename = '';
1741 my $last_dis_linenum = -1;
1742 my $last_touched_line = -1; # To detect gaps in disassembly for a line
1743 foreach my $e (@instructions) {
1744 # Add up counts for all address that fall inside this instruction
1745 my $c1 = 0;
1746 my $c2 = 0;
1747 for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
1748 $c1 += GetEntry($flat, $a);
1749 $c2 += GetEntry($cumulative, $a);
1750 }
1751
1752 if ($html) {
1753 my $dis = sprintf(" %6s %6s \t\t%8s: %s ",
1754 HtmlPrintNumber($c1),
1755 HtmlPrintNumber($c2),
1756 UnparseAddress($offset, $e->[0]),
1757 CleanDisassembly($e->[3]));
1758
1759 # Append the most specific source line associated with this instruction
1760 if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };
1761 $dis = HtmlEscape($dis);
1762 my $f = $e->[5];
1763 my $l = $e->[6];
1764 if ($f ne $last_dis_filename) {
1765 $dis .= sprintf("<span class=disasmloc>%s:%d</span>",
1766 HtmlEscape(CleanFileName($f)), $l);
1767 } elsif ($l ne $last_dis_linenum) {
1768 # De-emphasize the unchanged file name portion
1769 $dis .= sprintf("<span class=unimportant>%s</span>" .
1770 "<span class=disasmloc>:%d</span>",
1771 HtmlEscape(CleanFileName($f)), $l);
1772 } else {
1773 # De-emphasize the entire location
1774 $dis .= sprintf("<span class=unimportant>%s:%d</span>",
1775 HtmlEscape(CleanFileName($f)), $l);
1776 }
1777 $last_dis_filename = $f;
1778 $last_dis_linenum = $l;
1779 $running_disasm .= $dis;
1780 $running_disasm .= "\n";
1781 }
1782
1783 $running1 += $c1;
1784 $running2 += $c2;
1785 $total1 += $c1;
1786 $total2 += $c2;
1787 my $file = $e->[1];
1788 my $line = $e->[2];
1789 if (($file eq $filename) &&
1790 ($line >= $firstline) &&
1791 ($line <= $lastline)) {
1792 # Assign all accumulated samples to this line
1793 AddEntry($samples1, $line, $running1);
1794 AddEntry($samples2, $line, $running2);
1795 $running1 = 0;
1796 $running2 = 0;
1797 if ($html) {
1798 if ($line != $last_touched_line && $disasm{$line} ne '') {
1799 $disasm{$line} .= "\n";
1800 }
1801 $disasm{$line} .= $running_disasm;
1802 $running_disasm = '';
1803 $last_touched_line = $line;
1804 }
1805 }
1806 }
1807
1808 # Assign any leftover samples to $lastline
1809 AddEntry($samples1, $lastline, $running1);
1810 AddEntry($samples2, $lastline, $running2);
1811 if ($html) {
1812 if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {
1813 $disasm{$lastline} .= "\n";
1814 }
1815 $disasm{$lastline} .= $running_disasm;
1816 }
1817
1818 if ($html) {
1819 printf $output (
1820 "<h1>%s</h1>%s\n<pre onClick=\"pprof_toggle_asm()\">\n" .
1821 "Total:%6s %6s (flat / cumulative %s)\n",
1822 HtmlEscape(ShortFunctionName($routine)),
1823 HtmlEscape(CleanFileName($filename)),
1824 Unparse($total1),
1825 Unparse($total2),
1826 Units());
1827 } else {
1828 printf $output (
1829 "ROUTINE ====================== %s in %s\n" .
1830 "%6s %6s Total %s (flat / cumulative)\n",
1831 ShortFunctionName($routine),
1832 CleanFileName($filename),
1833 Unparse($total1),
1834 Unparse($total2),
1835 Units());
1836 }
1837 if (!open(FILE, "<$filename")) {
1838 print STDERR "$filename: $!\n";
1839 return 0;
1840 }
1841 my $l = 0;
1842 while (<FILE>) {
1843 s/\r//g; # turn windows-looking lines into unix-looking lines
1844 $l++;
1845 if ($l >= $firstline - 5 &&
1846 (($l <= $oldlastline + 5) || ($l <= $lastline))) {
1847 chop;
1848 my $text = $_;
1849 if ($l == $firstline) { print $output $skip_marker; }
1850 my $n1 = GetEntry($samples1, $l);
1851 my $n2 = GetEntry($samples2, $l);
1852 if ($html) {
1853 # Emit a span that has one of the following classes:
1854 # livesrc -- has samples
1855 # deadsrc -- has disassembly, but with no samples
1856 # nop -- has no matching disasembly
1857 # Also emit an optional span containing disassembly.
1858 my $dis = $disasm{$l};
1859 my $asm = "";
1860 if (defined($dis) && $dis ne '') {
1861 $asm = "<span class=\"asm\">" . $dis . "</span>";
1862 }
1863 my $source_class = (($n1 + $n2 > 0)
1864 ? "livesrc"
1865 : (($asm ne "") ? "deadsrc" : "nop"));
1866 printf $output (
1867 "<span class=\"line\">%5d</span> " .
1868 "<span class=\"%s\">%6s %6s %s</span>%s\n",
1869 $l, $source_class,
1870 HtmlPrintNumber($n1),
1871 HtmlPrintNumber($n2),
1872 HtmlEscape($text),
1873 $asm);
1874 } else {
1875 printf $output(
1876 "%6s %6s %4d: %s\n",
1877 UnparseAlt($n1),
1878 UnparseAlt($n2),
1879 $l,
1880 $text);
1881 }
1882 if ($l == $lastline) { print $output $skip_marker; }
1883 };
1884 }
1885 close(FILE);
1886 if ($html) {
1887 print $output "</pre>\n";
1888 }
1889 return 1;
1890}
1891
1892# Return the source line for the specified file/linenumber.
1893# Returns undef if not found.
1894sub SourceLine {
1895 my $file = shift;
1896 my $line = shift;
1897
1898 # Look in cache
1899 if (!defined($main::source_cache{$file})) {
1900 if (100 < scalar keys(%main::source_cache)) {
1901 # Clear the cache when it gets too big
1902 $main::source_cache = ();
1903 }
1904
1905 # Read all lines from the file
1906 if (!open(FILE, "<$file")) {
1907 print STDERR "$file: $!\n";
1908 $main::source_cache{$file} = []; # Cache the negative result
1909 return undef;
1910 }
1911 my $lines = [];
1912 push(@{$lines}, ""); # So we can use 1-based line numbers as indices
1913 while (<FILE>) {
1914 push(@{$lines}, $_);
1915 }
1916 close(FILE);
1917
1918 # Save the lines in the cache
1919 $main::source_cache{$file} = $lines;
1920 }
1921
1922 my $lines = $main::source_cache{$file};
1923 if (($line < 0) || ($line > $#{$lines})) {
1924 return undef;
1925 } else {
1926 return $lines->[$line];
1927 }
1928}
1929
1930# Print disassembly for one routine with interspersed source if available
1931sub PrintDisassembledFunction {
1932 my $prog = shift;
1933 my $offset = shift;
1934 my $routine = shift;
1935 my $flat = shift;
1936 my $cumulative = shift;
1937 my $start_addr = shift;
1938 my $end_addr = shift;
1939 my $total = shift;
1940
1941 # Disassemble all instructions
1942 my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
1943
1944 # Make array of counts per instruction
1945 my @flat_count = ();
1946 my @cum_count = ();
1947 my $flat_total = 0;
1948 my $cum_total = 0;
1949 foreach my $e (@instructions) {
1950 # Add up counts for all address that fall inside this instruction
1951 my $c1 = 0;
1952 my $c2 = 0;
1953 for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
1954 $c1 += GetEntry($flat, $a);
1955 $c2 += GetEntry($cumulative, $a);
1956 }
1957 push(@flat_count, $c1);
1958 push(@cum_count, $c2);
1959 $flat_total += $c1;
1960 $cum_total += $c2;
1961 }
1962
1963 # Print header with total counts
1964 printf("ROUTINE ====================== %s\n" .
1965 "%6s %6s %s (flat, cumulative) %.1f%% of total\n",
1966 ShortFunctionName($routine),
1967 Unparse($flat_total),
1968 Unparse($cum_total),
1969 Units(),
1970 ($cum_total * 100.0) / $total);
1971
1972 # Process instructions in order
1973 my $current_file = "";
1974 for (my $i = 0; $i <= $#instructions; ) {
1975 my $e = $instructions[$i];
1976
1977 # Print the new file name whenever we switch files
1978 if ($e->[1] ne $current_file) {
1979 $current_file = $e->[1];
1980 my $fname = $current_file;
1981 $fname =~ s|^\./||; # Trim leading "./"
1982
1983 # Shorten long file names
1984 if (length($fname) >= 58) {
1985 $fname = "..." . substr($fname, -55);
1986 }
1987 printf("-------------------- %s\n", $fname);
1988 }
1989
1990 # TODO: Compute range of lines to print together to deal with
1991 # small reorderings.
1992 my $first_line = $e->[2];
1993 my $last_line = $first_line;
1994 my %flat_sum = ();
1995 my %cum_sum = ();
1996 for (my $l = $first_line; $l <= $last_line; $l++) {
1997 $flat_sum{$l} = 0;
1998 $cum_sum{$l} = 0;
1999 }
2000
2001 # Find run of instructions for this range of source lines
2002 my $first_inst = $i;
2003 while (($i <= $#instructions) &&
2004 ($instructions[$i]->[2] >= $first_line) &&
2005 ($instructions[$i]->[2] <= $last_line)) {
2006 $e = $instructions[$i];
2007 $flat_sum{$e->[2]} += $flat_count[$i];
2008 $cum_sum{$e->[2]} += $cum_count[$i];
2009 $i++;
2010 }
2011 my $last_inst = $i - 1;
2012
2013 # Print source lines
2014 for (my $l = $first_line; $l <= $last_line; $l++) {
2015 my $line = SourceLine($current_file, $l);
2016 if (!defined($line)) {
2017 $line = "?\n";
2018 next;
2019 } else {
2020 $line =~ s/^\s+//;
2021 }
2022 printf("%6s %6s %5d: %s",
2023 UnparseAlt($flat_sum{$l}),
2024 UnparseAlt($cum_sum{$l}),
2025 $l,
2026 $line);
2027 }
2028
2029 # Print disassembly
2030 for (my $x = $first_inst; $x <= $last_inst; $x++) {
2031 my $e = $instructions[$x];
2032 printf("%6s %6s %8s: %6s\n",
2033 UnparseAlt($flat_count[$x]),
2034 UnparseAlt($cum_count[$x]),
2035 UnparseAddress($offset, $e->[0]),
2036 CleanDisassembly($e->[3]));
2037 }
2038 }
2039}
2040
2041# Print DOT graph
2042sub PrintDot {
2043 my $prog = shift;
2044 my $symbols = shift;
2045 my $raw = shift;
2046 my $flat = shift;
2047 my $cumulative = shift;
2048 my $overall_total = shift;
2049
2050 # Get total
2051 my $local_total = TotalProfile($flat);
2052 my $nodelimit = int($main::opt_nodefraction * $local_total);
2053 my $edgelimit = int($main::opt_edgefraction * $local_total);
2054 my $nodecount = $main::opt_nodecount;
2055
2056 # Find nodes to include
2057 my @list = (sort { abs(GetEntry($cumulative, $b)) <=>
2058 abs(GetEntry($cumulative, $a))
2059 || $a cmp $b }
2060 keys(%{$cumulative}));
2061 my $last = $nodecount - 1;
2062 if ($last > $#list) {
2063 $last = $#list;
2064 }
2065 while (($last >= 0) &&
2066 (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {
2067 $last--;
2068 }
2069 if ($last < 0) {
2070 print STDERR "No nodes to print\n";
2071 return 0;
2072 }
2073
2074 if ($nodelimit > 0 || $edgelimit > 0) {
2075 printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n",
2076 Unparse($nodelimit), Units(),
2077 Unparse($edgelimit), Units());
2078 }
2079
2080 # Open DOT output file
2081 my $output;
2082 my $escaped_dot = ShellEscape(@DOT);
2083 my $escaped_ps2pdf = ShellEscape(@PS2PDF);
2084 if ($main::opt_gv) {
2085 my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps"));
2086 $output = "| $escaped_dot -Tps2 >$escaped_outfile";
2087 } elsif ($main::opt_evince) {
2088 my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf"));
2089 $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile";
2090 } elsif ($main::opt_ps) {
2091 $output = "| $escaped_dot -Tps2";
2092 } elsif ($main::opt_pdf) {
2093 $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -";
2094 } elsif ($main::opt_web || $main::opt_svg) {
2095 # We need to post-process the SVG, so write to a temporary file always.
2096 my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg"));
2097 $output = "| $escaped_dot -Tsvg >$escaped_outfile";
2098 } elsif ($main::opt_gif) {
2099 $output = "| $escaped_dot -Tgif";
2100 } else {
2101 $output = ">&STDOUT";
2102 }
2103 open(DOT, $output) || error("$output: $!\n");
2104
2105 # Title
2106 printf DOT ("digraph \"%s; %s %s\" {\n",
2107 $prog,
2108 Unparse($overall_total),
2109 Units());
2110 if ($main::opt_pdf) {
2111 # The output is more printable if we set the page size for dot.
2112 printf DOT ("size=\"8,11\"\n");
2113 }
2114 printf DOT ("node [width=0.375,height=0.25];\n");
2115
2116 # Print legend
2117 printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," .
2118 "label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n",
2119 $prog,
2120 sprintf("Total %s: %s", Units(), Unparse($overall_total)),
2121 sprintf("Focusing on: %s", Unparse($local_total)),
2122 sprintf("Dropped nodes with <= %s abs(%s)",
2123 Unparse($nodelimit), Units()),
2124 sprintf("Dropped edges with <= %s %s",
2125 Unparse($edgelimit), Units())
2126 );
2127
2128 # Print nodes
2129 my %node = ();
2130 my $nextnode = 1;
2131 foreach my $a (@list[0..$last]) {
2132 # Pick font size
2133 my $f = GetEntry($flat, $a);
2134 my $c = GetEntry($cumulative, $a);
2135
2136 my $fs = 8;
2137 if ($local_total > 0) {
2138 $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));
2139 }
2140
2141 $node{$a} = $nextnode++;
2142 my $sym = $a;
2143 $sym =~ s/\s+/\\n/g;
2144 $sym =~ s/::/\\n/g;
2145
2146 # Extra cumulative info to print for non-leaves
2147 my $extra = "";
2148 if ($f != $c) {
2149 $extra = sprintf("\\rof %s (%s)",
2150 Unparse($c),
2151 Percent($c, $local_total));
2152 }
2153 my $style = "";
2154 if ($main::opt_heapcheck) {
2155 if ($f > 0) {
2156 # make leak-causing nodes more visible (add a background)
2157 $style = ",style=filled,fillcolor=gray"
2158 } elsif ($f < 0) {
2159 # make anti-leak-causing nodes (which almost never occur)
2160 # stand out as well (triple border)
2161 $style = ",peripheries=3"
2162 }
2163 }
2164
2165 printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
2166 "\",shape=box,fontsize=%.1f%s];\n",
2167 $node{$a},
2168 $sym,
2169 Unparse($f),
2170 Percent($f, $local_total),
2171 $extra,
2172 $fs,
2173 $style,
2174 );
2175 }
2176
2177 # Get edges and counts per edge
2178 my %edge = ();
2179 my $n;
2180 my $fullname_to_shortname_map = {};
2181 FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
2182 foreach my $k (keys(%{$raw})) {
2183 # TODO: omit low %age edges
2184 $n = $raw->{$k};
2185 my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
2186 for (my $i = 1; $i <= $#translated; $i++) {
2187 my $src = $translated[$i];
2188 my $dst = $translated[$i-1];
2189 #next if ($src eq $dst); # Avoid self-edges?
2190 if (exists($node{$src}) && exists($node{$dst})) {
2191 my $edge_label = "$src\001$dst";
2192 if (!exists($edge{$edge_label})) {
2193 $edge{$edge_label} = 0;
2194 }
2195 $edge{$edge_label} += $n;
2196 }
2197 }
2198 }
2199
2200 # Print edges (process in order of decreasing counts)
2201 my %indegree = (); # Number of incoming edges added per node so far
2202 my %outdegree = (); # Number of outgoing edges added per node so far
2203 foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
2204 my @x = split(/\001/, $e);
2205 $n = $edge{$e};
2206
2207 # Initialize degree of kept incoming and outgoing edges if necessary
2208 my $src = $x[0];
2209 my $dst = $x[1];
2210 if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }
2211 if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }
2212
2213 my $keep;
2214 if ($indegree{$dst} == 0) {
2215 # Keep edge if needed for reachability
2216 $keep = 1;
2217 } elsif (abs($n) <= $edgelimit) {
2218 # Drop if we are below --edgefraction
2219 $keep = 0;
2220 } elsif ($outdegree{$src} >= $main::opt_maxdegree ||
2221 $indegree{$dst} >= $main::opt_maxdegree) {
2222 # Keep limited number of in/out edges per node
2223 $keep = 0;
2224 } else {
2225 $keep = 1;
2226 }
2227
2228 if ($keep) {
2229 $outdegree{$src}++;
2230 $indegree{$dst}++;
2231
2232 # Compute line width based on edge count
2233 my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
2234 if ($fraction > 1) { $fraction = 1; }
2235 my $w = $fraction * 2;
2236 if ($w < 1 && ($main::opt_web || $main::opt_svg)) {
2237 # SVG output treats line widths < 1 poorly.
2238 $w = 1;
2239 }
2240
2241 # Dot sometimes segfaults if given edge weights that are too large, so
2242 # we cap the weights at a large value
2243 my $edgeweight = abs($n) ** 0.7;
2244 if ($edgeweight > 100000) { $edgeweight = 100000; }
2245 $edgeweight = int($edgeweight);
2246
2247 my $style = sprintf("setlinewidth(%f)", $w);
2248 if ($x[1] =~ m/\(inline\)/) {
2249 $style .= ",dashed";
2250 }
2251
2252 # Use a slightly squashed function of the edge count as the weight
2253 printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n",
2254 $node{$x[0]},
2255 $node{$x[1]},
2256 Unparse($n),
2257 $edgeweight,
2258 $style);
2259 }
2260 }
2261
2262 print DOT ("}\n");
2263 close(DOT);
2264
2265 if ($main::opt_web || $main::opt_svg) {
2266 # Rewrite SVG to be more usable inside web browser.
2267 RewriteSvg(TempName($main::next_tmpfile, "svg"));
2268 }
2269
2270 return 1;
2271}
2272
2273sub RewriteSvg {
2274 my $svgfile = shift;
2275
2276 open(SVG, $svgfile) || die "open temp svg: $!";
2277 my @svg = <SVG>;
2278 close(SVG);
2279 unlink $svgfile;
2280 my $svg = join('', @svg);
2281
2282 # Dot's SVG output is
2283 #
2284 # <svg width="___" height="___"
2285 # viewBox="___" xmlns=...>
2286 # <g id="graph0" transform="...">
2287 # ...
2288 # </g>
2289 # </svg>
2290 #
2291 # Change it to
2292 #
2293 # <svg width="100%" height="100%"
2294 # xmlns=...>
2295 # $svg_javascript
2296 # <g id="viewport" transform="translate(0,0)">
2297 # <g id="graph0" transform="...">
2298 # ...
2299 # </g>
2300 # </g>
2301 # </svg>
2302
2303 # Fix width, height; drop viewBox.
2304 $svg =~ s/(?s)<svg width="[^"]+" height="[^"]+"(.*?)viewBox="[^"]+"/<svg width="100%" height="100%"$1/;
2305
2306 # Insert script, viewport <g> above first <g>
2307 my $svg_javascript = SvgJavascript();
2308 my $viewport = "<g id=\"viewport\" transform=\"translate(0,0)\">\n";
2309 $svg =~ s/<g id="graph\d"/$svg_javascript$viewport$&/;
2310
2311 # Insert final </g> above </svg>.
2312 $svg =~ s/(.*)(<\/svg>)/$1<\/g>$2/;
2313 $svg =~ s/<g id="graph\d"(.*?)/<g id="viewport"$1/;
2314
2315 if ($main::opt_svg) {
2316 # --svg: write to standard output.
2317 print $svg;
2318 } else {
2319 # Write back to temporary file.
2320 open(SVG, ">$svgfile") || die "open $svgfile: $!";
2321 print SVG $svg;
2322 close(SVG);
2323 }
2324}
2325
2326sub SvgJavascript {
2327 return <<'EOF';
2328<script type="text/ecmascript"><![CDATA[
2329// SVGPan
2330// http://www.cyberz.org/blog/2009/12/08/svgpan-a-javascript-svg-panzoomdrag-library/
2331// Local modification: if(true || ...) below to force panning, never moving.
2332
2333/**
2334 * SVGPan library 1.2
2335 * ====================
2336 *
2337 * Given an unique existing element with id "viewport", including the
2338 * the library into any SVG adds the following capabilities:
2339 *
2340 * - Mouse panning
2341 * - Mouse zooming (using the wheel)
2342 * - Object dargging
2343 *
2344 * Known issues:
2345 *
2346 * - Zooming (while panning) on Safari has still some issues
2347 *
2348 * Releases:
2349 *
2350 * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
2351 * Fixed a bug with browser mouse handler interaction
2352 *
2353 * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui
2354 * Updated the zoom code to support the mouse wheel on Safari/Chrome
2355 *
2356 * 1.0, Andrea Leofreddi
2357 * First release
2358 *
2359 * This code is licensed under the following BSD license:
2360 *
2361 * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.
2362 *
2363 * Redistribution and use in source and binary forms, with or without modification, are
2364 * permitted provided that the following conditions are met:
2365 *
2366 * 1. Redistributions of source code must retain the above copyright notice, this list of
2367 * conditions and the following disclaimer.
2368 *
2369 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
2370 * of conditions and the following disclaimer in the documentation and/or other materials
2371 * provided with the distribution.
2372 *
2373 * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
2374 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
2375 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
2376 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2377 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2378 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
2379 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
2380 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2381 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2382 *
2383 * The views and conclusions contained in the software and documentation are those of the
2384 * authors and should not be interpreted as representing official policies, either expressed
2385 * or implied, of Andrea Leofreddi.
2386 */
2387
2388var root = document.documentElement;
2389
2390var state = 'none', stateTarget, stateOrigin, stateTf;
2391
2392setupHandlers(root);
2393
2394/**
2395 * Register handlers
2396 */
2397function setupHandlers(root){
2398 setAttributes(root, {
2399 "onmouseup" : "add(evt)",
2400 "onmousedown" : "handleMouseDown(evt)",
2401 "onmousemove" : "handleMouseMove(evt)",
2402 "onmouseup" : "handleMouseUp(evt)",
2403 //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
2404 });
2405
2406 if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
2407 window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
2408 else
2409 window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
2410
2411 var g = svgDoc.getElementById("svg");
2412 g.width = "100%";
2413 g.height = "100%";
2414}
2415
2416/**
2417 * Instance an SVGPoint object with given event coordinates.
2418 */
2419function getEventPoint(evt) {
2420 var p = root.createSVGPoint();
2421
2422 p.x = evt.clientX;
2423 p.y = evt.clientY;
2424
2425 return p;
2426}
2427
2428/**
2429 * Sets the current transform matrix of an element.
2430 */
2431function setCTM(element, matrix) {
2432 var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
2433
2434 element.setAttribute("transform", s);
2435}
2436
2437/**
2438 * Dumps a matrix to a string (useful for debug).
2439 */
2440function dumpMatrix(matrix) {
2441 var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]";
2442
2443 return s;
2444}
2445
2446/**
2447 * Sets attributes of an element.
2448 */
2449function setAttributes(element, attributes){
2450 for (i in attributes)
2451 element.setAttributeNS(null, i, attributes[i]);
2452}
2453
2454/**
2455 * Handle mouse move event.
2456 */
2457function handleMouseWheel(evt) {
2458 if(evt.preventDefault)
2459 evt.preventDefault();
2460
2461 evt.returnValue = false;
2462
2463 var svgDoc = evt.target.ownerDocument;
2464
2465 var delta;
2466
2467 if(evt.wheelDelta)
2468 delta = evt.wheelDelta / 3600; // Chrome/Safari
2469 else
2470 delta = evt.detail / -90; // Mozilla
2471
2472 var z = 1 + delta; // Zoom factor: 0.9/1.1
2473
2474 var g = svgDoc.getElementById("viewport");
2475
2476 var p = getEventPoint(evt);
2477
2478 p = p.matrixTransform(g.getCTM().inverse());
2479
2480 // Compute new scale matrix in current mouse position
2481 var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
2482
2483 setCTM(g, g.getCTM().multiply(k));
2484
2485 stateTf = stateTf.multiply(k.inverse());
2486}
2487
2488/**
2489 * Handle mouse move event.
2490 */
2491function handleMouseMove(evt) {
2492 if(evt.preventDefault)
2493 evt.preventDefault();
2494
2495 evt.returnValue = false;
2496
2497 var svgDoc = evt.target.ownerDocument;
2498
2499 var g = svgDoc.getElementById("viewport");
2500
2501 if(state == 'pan') {
2502 // Pan mode
2503 var p = getEventPoint(evt).matrixTransform(stateTf);
2504
2505 setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
2506 } else if(state == 'move') {
2507 // Move mode
2508 var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
2509
2510 setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
2511
2512 stateOrigin = p;
2513 }
2514}
2515
2516/**
2517 * Handle click event.
2518 */
2519function handleMouseDown(evt) {
2520 if(evt.preventDefault)
2521 evt.preventDefault();
2522
2523 evt.returnValue = false;
2524
2525 var svgDoc = evt.target.ownerDocument;
2526
2527 var g = svgDoc.getElementById("viewport");
2528
2529 if(true || evt.target.tagName == "svg") {
2530 // Pan mode
2531 state = 'pan';
2532
2533 stateTf = g.getCTM().inverse();
2534
2535 stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
2536 } else {
2537 // Move mode
2538 state = 'move';
2539
2540 stateTarget = evt.target;
2541
2542 stateTf = g.getCTM().inverse();
2543
2544 stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
2545 }
2546}
2547
2548/**
2549 * Handle mouse button release event.
2550 */
2551function handleMouseUp(evt) {
2552 if(evt.preventDefault)
2553 evt.preventDefault();
2554
2555 evt.returnValue = false;
2556
2557 var svgDoc = evt.target.ownerDocument;
2558
2559 if(state == 'pan' || state == 'move') {
2560 // Quit pan mode
2561 state = '';
2562 }
2563}
2564
2565]]></script>
2566EOF
2567}
2568
2569# Provides a map from fullname to shortname for cases where the
2570# shortname is ambiguous. The symlist has both the fullname and
2571# shortname for all symbols, which is usually fine, but sometimes --
2572# such as overloaded functions -- two different fullnames can map to
2573# the same shortname. In that case, we use the address of the
2574# function to disambiguate the two. This function fills in a map that
2575# maps fullnames to modified shortnames in such cases. If a fullname
2576# is not present in the map, the 'normal' shortname provided by the
2577# symlist is the appropriate one to use.
2578sub FillFullnameToShortnameMap {
2579 my $symbols = shift;
2580 my $fullname_to_shortname_map = shift;
2581 my $shortnames_seen_once = {};
2582 my $shortnames_seen_more_than_once = {};
2583
2584 foreach my $symlist (values(%{$symbols})) {
2585 # TODO(csilvers): deal with inlined symbols too.
2586 my $shortname = $symlist->[0];
2587 my $fullname = $symlist->[2];
2588 if ($fullname !~ /<[0-9a-fA-F]+>$/) { # fullname doesn't end in an address
2589 next; # the only collisions we care about are when addresses differ
2590 }
2591 if (defined($shortnames_seen_once->{$shortname}) &&
2592 $shortnames_seen_once->{$shortname} ne $fullname) {
2593 $shortnames_seen_more_than_once->{$shortname} = 1;
2594 } else {
2595 $shortnames_seen_once->{$shortname} = $fullname;
2596 }
2597 }
2598
2599 foreach my $symlist (values(%{$symbols})) {
2600 my $shortname = $symlist->[0];
2601 my $fullname = $symlist->[2];
2602 # TODO(csilvers): take in a list of addresses we care about, and only
2603 # store in the map if $symlist->[1] is in that list. Saves space.
2604 next if defined($fullname_to_shortname_map->{$fullname});
2605 if (defined($shortnames_seen_more_than_once->{$shortname})) {
2606 if ($fullname =~ /<0*([^>]*)>$/) { # fullname has address at end of it
2607 $fullname_to_shortname_map->{$fullname} = "$shortname\@$1";
2608 }
2609 }
2610 }
2611}
2612
2613# Return a small number that identifies the argument.
2614# Multiple calls with the same argument will return the same number.
2615# Calls with different arguments will return different numbers.
2616sub ShortIdFor {
2617 my $key = shift;
2618 my $id = $main::uniqueid{$key};
2619 if (!defined($id)) {
2620 $id = keys(%main::uniqueid) + 1;
2621 $main::uniqueid{$key} = $id;
2622 }
2623 return $id;
2624}
2625
2626# Translate a stack of addresses into a stack of symbols
2627sub TranslateStack {
2628 my $symbols = shift;
2629 my $fullname_to_shortname_map = shift;
2630 my $k = shift;
2631
2632 my @addrs = split(/\n/, $k);
2633 my @result = ();
2634 for (my $i = 0; $i <= $#addrs; $i++) {
2635 my $a = $addrs[$i];
2636
2637 # Skip large addresses since they sometimes show up as fake entries on RH9
2638 if (length($a) > 8 && $a gt "7fffffffffffffff") {
2639 next;
2640 }
2641
2642 if ($main::opt_disasm || $main::opt_list) {
2643 # We want just the address for the key
2644 push(@result, $a);
2645 next;
2646 }
2647
2648 my $symlist = $symbols->{$a};
2649 if (!defined($symlist)) {
2650 $symlist = [$a, "", $a];
2651 }
2652
2653 # We can have a sequence of symbols for a particular entry
2654 # (more than one symbol in the case of inlining). Callers
2655 # come before callees in symlist, so walk backwards since
2656 # the translated stack should contain callees before callers.
2657 for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {
2658 my $func = $symlist->[$j-2];
2659 my $fileline = $symlist->[$j-1];
2660 my $fullfunc = $symlist->[$j];
2661 if (defined($fullname_to_shortname_map->{$fullfunc})) {
2662 $func = $fullname_to_shortname_map->{$fullfunc};
2663 }
2664 if ($j > 2) {
2665 $func = "$func (inline)";
2666 }
2667
2668 # Do not merge nodes corresponding to Callback::Run since that
2669 # causes confusing cycles in dot display. Instead, we synthesize
2670 # a unique name for this frame per caller.
2671 if ($func =~ m/Callback.*::Run$/) {
2672 my $caller = ($i > 0) ? $addrs[$i-1] : 0;
2673 $func = "Run#" . ShortIdFor($caller);
2674 }
2675
2676 if ($main::opt_addresses) {
2677 push(@result, "$a $func $fileline");
2678 } elsif ($main::opt_lines) {
2679 if ($func eq '??' && $fileline eq '??:0') {
2680 push(@result, "$a");
2681 } elsif (!$main::opt_show_addresses) {
2682 push(@result, "$func $fileline");
2683 } else {
2684 push(@result, "$func $fileline ($a)");
2685 }
2686 } elsif ($main::opt_functions) {
2687 if ($func eq '??') {
2688 push(@result, "$a");
2689 } elsif (!$main::opt_show_addresses) {
2690 push(@result, $func);
2691 } else {
2692 push(@result, "$func ($a)");
2693 }
2694 } elsif ($main::opt_files) {
2695 if ($fileline eq '??:0' || $fileline eq '') {
2696 push(@result, "$a");
2697 } else {
2698 my $f = $fileline;
2699 $f =~ s/:\d+$//;
2700 push(@result, $f);
2701 }
2702 } else {
2703 push(@result, $a);
2704 last; # Do not print inlined info
2705 }
2706 }
2707 }
2708
2709 # print join(",", @addrs), " => ", join(",", @result), "\n";
2710 return @result;
2711}
2712
2713# Generate percent string for a number and a total
2714sub Percent {
2715 my $num = shift;
2716 my $tot = shift;
2717 if ($tot != 0) {
2718 return sprintf("%.1f%%", $num * 100.0 / $tot);
2719 } else {
2720 return ($num == 0) ? "nan" : (($num > 0) ? "+inf" : "-inf");
2721 }
2722}
2723
2724# Generate pretty-printed form of number
2725sub Unparse {
2726 my $num = shift;
2727 if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2728 if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
2729 return sprintf("%d", $num);
2730 } else {
2731 if ($main::opt_show_bytes) {
2732 return sprintf("%d", $num);
2733 } else {
2734 return sprintf("%.1f", $num / 1048576.0);
2735 }
2736 }
2737 } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
2738 return sprintf("%.3f", $num / 1e9); # Convert nanoseconds to seconds
2739 } else {
2740 return sprintf("%d", $num);
2741 }
2742}
2743
2744# Alternate pretty-printed form: 0 maps to "."
2745sub UnparseAlt {
2746 my $num = shift;
2747 if ($num == 0) {
2748 return ".";
2749 } else {
2750 return Unparse($num);
2751 }
2752}
2753
2754# Alternate pretty-printed form: 0 maps to ""
2755sub HtmlPrintNumber {
2756 my $num = shift;
2757 if ($num == 0) {
2758 return "";
2759 } else {
2760 return Unparse($num);
2761 }
2762}
2763
2764# Return output units
2765sub Units {
2766 if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2767 if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
2768 return "objects";
2769 } else {
2770 if ($main::opt_show_bytes) {
2771 return "B";
2772 } else {
2773 return "MB";
2774 }
2775 }
2776 } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
2777 return "seconds";
2778 } else {
2779 return "samples";
2780 }
2781}
2782
2783##### Profile manipulation code #####
2784
2785# Generate flattened profile:
2786# If count is charged to stack [a,b,c,d], in generated profile,
2787# it will be charged to [a]
2788sub FlatProfile {
2789 my $profile = shift;
2790 my $result = {};
2791 foreach my $k (keys(%{$profile})) {
2792 my $count = $profile->{$k};
2793 my @addrs = split(/\n/, $k);
2794 if ($#addrs >= 0) {
2795 AddEntry($result, $addrs[0], $count);
2796 }
2797 }
2798 return $result;
2799}
2800
2801# Generate cumulative profile:
2802# If count is charged to stack [a,b,c,d], in generated profile,
2803# it will be charged to [a], [b], [c], [d]
2804sub CumulativeProfile {
2805 my $profile = shift;
2806 my $result = {};
2807 foreach my $k (keys(%{$profile})) {
2808 my $count = $profile->{$k};
2809 my @addrs = split(/\n/, $k);
2810 foreach my $a (@addrs) {
2811 AddEntry($result, $a, $count);
2812 }
2813 }
2814 return $result;
2815}
2816
2817# If the second-youngest PC on the stack is always the same, returns
2818# that pc. Otherwise, returns undef.
2819sub IsSecondPcAlwaysTheSame {
2820 my $profile = shift;
2821
2822 my $second_pc = undef;
2823 foreach my $k (keys(%{$profile})) {
2824 my @addrs = split(/\n/, $k);
2825 if ($#addrs < 1) {
2826 return undef;
2827 }
2828 if (not defined $second_pc) {
2829 $second_pc = $addrs[1];
2830 } else {
2831 if ($second_pc ne $addrs[1]) {
2832 return undef;
2833 }
2834 }
2835 }
2836 return $second_pc;
2837}
2838
2839sub ExtractSymbolLocationInlineStack {
2840 my $symbols = shift;
2841 my $address = shift;
2842 my $stack = shift;
2843 # 'addr2line' outputs "??:0" for unknown locations; we do the
2844 # same to be consistent.
2845 if (exists $symbols->{$address}) {
2846 my @localinlinestack = @{$symbols->{$address}};
2847 for (my $i = $#localinlinestack; $i > 0; $i-=3) {
2848 my $file = $localinlinestack[$i-1];
2849 my $fn = $localinlinestack[$i-2];
2850 if ($file eq "?" || $file eq ":0") {
2851 $file = "??:0";
2852 }
2853 my $suffix = "[inline]";
2854 if ($i == 2) {
2855 $suffix = "";
2856 }
2857 push (@$stack, $file.":".$fn.$suffix);
2858 }
2859 }
2860 else {
2861 push (@$stack, "??:0:unknown");
2862 }
2863}
2864
2865sub ExtractSymbolNameInlineStack {
2866 my $symbols = shift;
2867 my $address = shift;
2868
2869 my @stack = ();
2870
2871 if (exists $symbols->{$address}) {
2872 my @localinlinestack = @{$symbols->{$address}};
2873 for (my $i = $#localinlinestack; $i > 0; $i-=3) {
2874 my $file = $localinlinestack[$i-1];
2875 my $fn = $localinlinestack[$i-0];
2876
2877 if ($file eq "?" || $file eq ":0") {
2878 $file = "??:0";
2879 }
2880 if ($fn eq '??') {
2881 # If we can't get the symbol name, at least use the file information.
2882 $fn = $file;
2883 }
2884 my $suffix = "[inline]";
2885 if ($i == 2) {
2886 $suffix = "";
2887 }
2888 push (@stack, $fn.$suffix);
2889 }
2890 }
2891 else {
2892 # If we can't get a symbol name, at least fill in the address.
2893 push (@stack, $address);
2894 }
2895
2896 return @stack;
2897}
2898
2899sub ExtractSymbolLocation {
2900 my $symbols = shift;
2901 my $address = shift;
2902 # 'addr2line' outputs "??:0" for unknown locations; we do the
2903 # same to be consistent.
2904 my $location = "??:0:unknown";
2905 if (exists $symbols->{$address}) {
2906 my $file = $symbols->{$address}->[1];
2907 if ($file eq "?" || $file eq ":0") {
2908 $file = "??:0"
2909 }
2910 $location = $file . ":" . $symbols->{$address}->[0];
2911 }
2912 return $location;
2913}
2914
2915# Extracts a graph of calls.
2916sub ExtractCalls {
2917 my $symbols = shift;
2918 my $profile = shift;
2919 my $calls = {};
2920 while( my ($stack_trace, $count) = each %$profile ) {
2921 my @address = split(/\n/, $stack_trace);
2922 my @stack = ();
2923 ExtractSymbolLocationInlineStack($symbols, $address[0], \@stack);
2924 for (my $i = 1; $i <= $#address; $i++) {
2925 ExtractSymbolLocationInlineStack($symbols, $address[$i], \@stack);
2926 }
2927 AddEntry($calls, $stack[0], $count);
2928 for (my $i = 1; $i < $#address; $i++) {
2929 AddEntry($calls, "$stack[$i] -> $stack[$i-1]", $count);
2930 }
2931 }
2932 return $calls;
2933}
2934
2935sub PrintStacksForText {
2936 my $symbols = shift;
2937 my $profile = shift;
2938
2939 while (my ($stack_trace, $count) = each %$profile) {
2940 my @address = split(/\n/, $stack_trace);
2941 for (my $i = 0; $i <= $#address; $i++) {
2942 $address[$i] = sprintf("(%s) %s", $address[$i], ExtractSymbolLocation($symbols, $address[$i]));
2943 }
2944 printf("%-8d %s\n\n", $count, join("\n ", @address));
2945 }
2946}
2947
2948sub PrintCollapsedStacks {
2949 my $symbols = shift;
2950 my $profile = shift;
2951
2952 while (my ($stack_trace, $count) = each %$profile) {
2953 my @address = split(/\n/, $stack_trace);
2954 my @names = reverse ( map { ExtractSymbolNameInlineStack($symbols, $_) } @address );
2955 printf("%s %d\n", join(";", @names), $count);
2956 }
2957}
2958
2959sub RemoveUninterestingFrames {
2960 my $symbols = shift;
2961 my $profile = shift;
2962
2963 # List of function names to skip
2964 my %skip = ();
2965 my $skip_regexp = 'NOMATCH';
2966 if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
2967 foreach my $name ('calloc',
2968 'cfree',
2969 'malloc',
2970 'free',
2971 'memalign',
2972 'posix_memalign',
2973 'pvalloc',
2974 'valloc',
2975 'realloc',
2976 'tc_calloc',
2977 'tc_cfree',
2978 'tc_malloc',
2979 'tc_free',
2980 'tc_memalign',
2981 'tc_posix_memalign',
2982 'tc_pvalloc',
2983 'tc_valloc',
2984 'tc_realloc',
2985 'tc_new',
2986 'tc_delete',
2987 'tc_newarray',
2988 'tc_deletearray',
2989 'tc_new_nothrow',
2990 'tc_newarray_nothrow',
2991 'do_malloc',
2992 '::do_malloc', # new name -- got moved to an unnamed ns
2993 '::do_malloc_or_cpp_alloc',
2994 'DoSampledAllocation',
2995 'simple_alloc::allocate',
2996 '__malloc_alloc_template::allocate',
2997 '__builtin_delete',
2998 '__builtin_new',
2999 '__builtin_vec_delete',
3000 '__builtin_vec_new',
3001 'operator new',
3002 'operator new[]',
3003 # The entry to our memory-allocation routines on OS X
3004 'malloc_zone_malloc',
3005 'malloc_zone_calloc',
3006 'malloc_zone_valloc',
3007 'malloc_zone_realloc',
3008 'malloc_zone_memalign',
3009 'malloc_zone_free',
3010 # These mark the beginning/end of our custom sections
3011 '__start_google_malloc',
3012 '__stop_google_malloc',
3013 '__start_malloc_hook',
3014 '__stop_malloc_hook') {
3015 $skip{$name} = 1;
3016 $skip{"_" . $name} = 1; # Mach (OS X) adds a _ prefix to everything
3017 }
3018 # TODO: Remove TCMalloc once everything has been
3019 # moved into the tcmalloc:: namespace and we have flushed
3020 # old code out of the system.
3021 $skip_regexp = "TCMalloc|^tcmalloc::";
3022 } elsif ($main::profile_type eq 'contention') {
3023 foreach my $vname ('base::RecordLockProfileData',
3024 'base::SubmitMutexProfileData',
3025 'base::SubmitSpinLockProfileData',
3026 'Mutex::Unlock',
3027 'Mutex::UnlockSlow',
3028 'Mutex::ReaderUnlock',
3029 'MutexLock::~MutexLock',
3030 'SpinLock::Unlock',
3031 'SpinLock::SlowUnlock',
3032 'SpinLockHolder::~SpinLockHolder') {
3033 $skip{$vname} = 1;
3034 }
3035 } elsif ($main::profile_type eq 'cpu' && !$main::opt_no_auto_signal_frames) {
3036 # Drop signal handlers used for CPU profile collection
3037 # TODO(dpeng): this should not be necessary; it's taken
3038 # care of by the general 2nd-pc mechanism below.
3039 foreach my $name ('ProfileData::Add', # historical
3040 'ProfileData::prof_handler', # historical
3041 'CpuProfiler::prof_handler',
3042 '__FRAME_END__',
3043 '__pthread_sighandler',
3044 '__restore') {
3045 $skip{$name} = 1;
3046 }
3047 } else {
3048 # Nothing skipped for unknown types
3049 }
3050
3051 if ($main::profile_type eq 'cpu') {
3052 # If all the second-youngest program counters are the same,
3053 # this STRONGLY suggests that it is an artifact of measurement,
3054 # i.e., stack frames pushed by the CPU profiler signal handler.
3055 # Hence, we delete them.
3056 # (The topmost PC is read from the signal structure, not from
3057 # the stack, so it does not get involved.)
3058 while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {
3059 my $result = {};
3060 my $func = '';
3061 if (exists($symbols->{$second_pc})) {
3062 $second_pc = $symbols->{$second_pc}->[0];
3063 }
3064 if ($main::opt_no_auto_signal_frames) {
3065 print STDERR "All second stack frames are same: `$second_pc'.\nMight be stack trace capturing bug.\n";
3066 last;
3067 }
3068 print STDERR "Removing $second_pc from all stack traces.\n";
3069 foreach my $k (keys(%{$profile})) {
3070 my $count = $profile->{$k};
3071 my @addrs = split(/\n/, $k);
3072 my $topaddr = POSIX::strtoul($addrs[0], 16);
3073 splice @addrs, 1, 1;
3074 if ($#addrs > 1) {
3075 my $subtopaddr = POSIX::strtoul($addrs[1], 16);
3076 if ($subtopaddr + 1 == $topaddr) {
3077 splice @addrs, 1, 1;
3078 }
3079 }
3080 my $reduced_path = join("\n", @addrs);
3081 AddEntry($result, $reduced_path, $count);
3082 }
3083 $profile = $result;
3084 }
3085 }
3086
3087 my $result = {};
3088 foreach my $k (keys(%{$profile})) {
3089 my $count = $profile->{$k};
3090 my @addrs = split(/\n/, $k);
3091 my @path = ();
3092 foreach my $a (@addrs) {
3093 if (exists($symbols->{$a})) {
3094 my $func = $symbols->{$a}->[0];
3095 if ($skip{$func} || ($func =~ m/$skip_regexp/)) {
3096 next;
3097 }
3098 }
3099 push(@path, $a);
3100 }
3101 my $reduced_path = join("\n", @path);
3102 AddEntry($result, $reduced_path, $count);
3103 }
3104 return $result;
3105}
3106
3107# Reduce profile to granularity given by user
3108sub ReduceProfile {
3109 my $symbols = shift;
3110 my $profile = shift;
3111 my $result = {};
3112 my $fullname_to_shortname_map = {};
3113 FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
3114 foreach my $k (keys(%{$profile})) {
3115 my $count = $profile->{$k};
3116 my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
3117 my @path = ();
3118 my %seen = ();
3119 $seen{''} = 1; # So that empty keys are skipped
3120 foreach my $e (@translated) {
3121 # To avoid double-counting due to recursion, skip a stack-trace
3122 # entry if it has already been seen
3123 if (!$seen{$e}) {
3124 $seen{$e} = 1;
3125 push(@path, $e);
3126 }
3127 }
3128 my $reduced_path = join("\n", @path);
3129 AddEntry($result, $reduced_path, $count);
3130 }
3131 return $result;
3132}
3133
3134# Does the specified symbol array match the regexp?
3135sub SymbolMatches {
3136 my $sym = shift;
3137 my $re = shift;
3138 if (defined($sym)) {
3139 for (my $i = 0; $i < $#{$sym}; $i += 3) {
3140 if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {
3141 return 1;
3142 }
3143 }
3144 }
3145 return 0;
3146}
3147
3148# Focus only on paths involving specified regexps
3149sub FocusProfile {
3150 my $symbols = shift;
3151 my $profile = shift;
3152 my $focus = shift;
3153 my $result = {};
3154 foreach my $k (keys(%{$profile})) {
3155 my $count = $profile->{$k};
3156 my @addrs = split(/\n/, $k);
3157 foreach my $a (@addrs) {
3158 # Reply if it matches either the address/shortname/fileline
3159 if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {
3160 AddEntry($result, $k, $count);
3161 last;
3162 }
3163 }
3164 }
3165 return $result;
3166}
3167
3168# Focus only on paths not involving specified regexps
3169sub IgnoreProfile {
3170 my $symbols = shift;
3171 my $profile = shift;
3172 my $ignore = shift;
3173 my $result = {};
3174 foreach my $k (keys(%{$profile})) {
3175 my $count = $profile->{$k};
3176 my @addrs = split(/\n/, $k);
3177 my $matched = 0;
3178 foreach my $a (@addrs) {
3179 # Reply if it matches either the address/shortname/fileline
3180 if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {
3181 $matched = 1;
3182 last;
3183 }
3184 }
3185 if (!$matched) {
3186 AddEntry($result, $k, $count);
3187 }
3188 }
3189 return $result;
3190}
3191
3192# Get total count in profile
3193sub TotalProfile {
3194 my $profile = shift;
3195 my $result = 0;
3196 foreach my $k (keys(%{$profile})) {
3197 $result += $profile->{$k};
3198 }
3199 return $result;
3200}
3201
3202# Add A to B
3203sub AddProfile {
3204 my $A = shift;
3205 my $B = shift;
3206
3207 my $R = {};
3208 # add all keys in A
3209 foreach my $k (keys(%{$A})) {
3210 my $v = $A->{$k};
3211 AddEntry($R, $k, $v);
3212 }
3213 # add all keys in B
3214 foreach my $k (keys(%{$B})) {
3215 my $v = $B->{$k};
3216 AddEntry($R, $k, $v);
3217 }
3218 return $R;
3219}
3220
3221# Merges symbol maps
3222sub MergeSymbols {
3223 my $A = shift;
3224 my $B = shift;
3225
3226 my $R = {};
3227 foreach my $k (keys(%{$A})) {
3228 $R->{$k} = $A->{$k};
3229 }
3230 if (defined($B)) {
3231 foreach my $k (keys(%{$B})) {
3232 $R->{$k} = $B->{$k};
3233 }
3234 }
3235 return $R;
3236}
3237
3238
3239# Add A to B
3240sub AddPcs {
3241 my $A = shift;
3242 my $B = shift;
3243
3244 my $R = {};
3245 # add all keys in A
3246 foreach my $k (keys(%{$A})) {
3247 $R->{$k} = 1
3248 }
3249 # add all keys in B
3250 foreach my $k (keys(%{$B})) {
3251 $R->{$k} = 1
3252 }
3253 return $R;
3254}
3255
3256# Subtract B from A
3257sub SubtractProfile {
3258 my $A = shift;
3259 my $B = shift;
3260
3261 my $R = {};
3262 foreach my $k (keys(%{$A})) {
3263 my $v = $A->{$k} - GetEntry($B, $k);
3264 if ($v < 0 && $main::opt_drop_negative) {
3265 $v = 0;
3266 }
3267 AddEntry($R, $k, $v);
3268 }
3269 if (!$main::opt_drop_negative) {
3270 # Take care of when subtracted profile has more entries
3271 foreach my $k (keys(%{$B})) {
3272 if (!exists($A->{$k})) {
3273 AddEntry($R, $k, 0 - $B->{$k});
3274 }
3275 }
3276 }
3277 return $R;
3278}
3279
3280# Get entry from profile; zero if not present
3281sub GetEntry {
3282 my $profile = shift;
3283 my $k = shift;
3284 if (exists($profile->{$k})) {
3285 return $profile->{$k};
3286 } else {
3287 return 0;
3288 }
3289}
3290
3291# Add entry to specified profile
3292sub AddEntry {
3293 my $profile = shift;
3294 my $k = shift;
3295 my $n = shift;
3296 if (!exists($profile->{$k})) {
3297 $profile->{$k} = 0;
3298 }
3299 $profile->{$k} += $n;
3300}
3301
3302# Add a stack of entries to specified profile, and add them to the $pcs
3303# list.
3304sub AddEntries {
3305 my $profile = shift;
3306 my $pcs = shift;
3307 my $stack = shift;
3308 my $count = shift;
3309 my @k = ();
3310
3311 foreach my $e (split(/\s+/, $stack)) {
3312 my $pc = HexExtend($e);
3313 $pcs->{$pc} = 1;
3314 push @k, $pc;
3315 }
3316 AddEntry($profile, (join "\n", @k), $count);
3317}
3318
3319##### Code to profile a server dynamically #####
3320
3321sub CheckSymbolPage {
3322 my $url = SymbolPageURL();
3323 my $command = ShellEscape(@URL_FETCHER, $url);
3324 open(SYMBOL, "$command |") or error($command);
3325 my $line = <SYMBOL>;
3326 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
3327 close(SYMBOL);
3328 unless (defined($line)) {
3329 error("$url doesn't exist\n");
3330 }
3331
3332 if ($line =~ /^num_symbols:\s+(\d+)$/) {
3333 if ($1 == 0) {
3334 error("Stripped binary. No symbols available.\n");
3335 }
3336 } else {
3337 error("Failed to get the number of symbols from $url\n");
3338 }
3339}
3340
3341sub IsProfileURL {
3342 my $profile_name = shift;
3343 if (-f $profile_name) {
3344 printf STDERR "Using local file $profile_name.\n";
3345 return 0;
3346 }
3347 return 1;
3348}
3349
3350sub ParseProfileURL {
3351 my $profile_name = shift;
3352
3353 if (!defined($profile_name) || $profile_name eq "") {
3354 return ();
3355 }
3356
3357 # Split profile URL - matches all non-empty strings, so no test.
3358 $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;
3359
3360 my $proto = $1 || "http://";
3361 my $hostport = $2;
3362 my $prefix = $3;
3363 my $profile = $4 || "/";
3364
3365 my $host = $hostport;
3366 $host =~ s/:.*//;
3367
3368 my $baseurl = "$proto$hostport$prefix";
3369 return ($host, $baseurl, $profile);
3370}
3371
3372# We fetch symbols from the first profile argument.
3373sub SymbolPageURL {
3374 my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
3375 return "$baseURL$SYMBOL_PAGE";
3376}
3377
3378sub FetchProgramName() {
3379 my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
3380 my $url = "$baseURL$PROGRAM_NAME_PAGE";
3381 my $command_line = ShellEscape(@URL_FETCHER, $url);
3382 open(CMDLINE, "$command_line |") or error($command_line);
3383 my $cmdline = <CMDLINE>;
3384 $cmdline =~ s/\r//g; # turn windows-looking lines into unix-looking lines
3385 close(CMDLINE);
3386 error("Failed to get program name from $url\n") unless defined($cmdline);
3387 $cmdline =~ s/\x00.+//; # Remove argv[1] and latters.
3388 $cmdline =~ s!\n!!g; # Remove LFs.
3389 return $cmdline;
3390}
3391
3392# Gee, curl's -L (--location) option isn't reliable at least
3393# with its 7.12.3 version. Curl will forget to post data if
3394# there is a redirection. This function is a workaround for
3395# curl. Redirection happens on borg hosts.
3396sub ResolveRedirectionForCurl {
3397 my $url = shift;
3398 my $command_line = ShellEscape(@URL_FETCHER, "--head", $url);
3399 open(CMDLINE, "$command_line |") or error($command_line);
3400 while (<CMDLINE>) {
3401 s/\r//g; # turn windows-looking lines into unix-looking lines
3402 if (/^Location: (.*)/) {
3403 $url = $1;
3404 }
3405 }
3406 close(CMDLINE);
3407 return $url;
3408}
3409
3410# Add a timeout flat to URL_FETCHER. Returns a new list.
3411sub AddFetchTimeout {
3412 my $timeout = shift;
3413 my @fetcher = shift;
3414 if (defined($timeout)) {
3415 if (join(" ", @fetcher) =~ m/\bcurl -s/) {
3416 push(@fetcher, "--max-time", sprintf("%d", $timeout));
3417 } elsif (join(" ", @fetcher) =~ m/\brpcget\b/) {
3418 push(@fetcher, sprintf("--deadline=%d", $timeout));
3419 }
3420 }
3421 return @fetcher;
3422}
3423
3424# Reads a symbol map from the file handle name given as $1, returning
3425# the resulting symbol map. Also processes variables relating to symbols.
3426# Currently, the only variable processed is 'binary=<value>' which updates
3427# $main::prog to have the correct program name.
3428sub ReadSymbols {
3429 my $in = shift;
3430 my $map = {};
3431 while (<$in>) {
3432 s/\r//g; # turn windows-looking lines into unix-looking lines
3433 # Removes all the leading zeroes from the symbols, see comment below.
3434 if (m/^0x0*([0-9a-f]+)\s+(.+)/) {
3435 $map->{$1} = $2;
3436 } elsif (m/^---/) {
3437 last;
3438 } elsif (m/^([a-z][^=]*)=(.*)$/ ) {
3439 my ($variable, $value) = ($1, $2);
3440 for ($variable, $value) {
3441 s/^\s+//;
3442 s/\s+$//;
3443 }
3444 if ($variable eq "binary") {
3445 if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {
3446 printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n",
3447 $main::prog, $value);
3448 }
3449 $main::prog = $value;
3450 } else {
3451 printf STDERR ("Ignoring unknown variable in symbols list: " .
3452 "'%s' = '%s'\n", $variable, $value);
3453 }
3454 }
3455 }
3456 return $map;
3457}
3458
3459# Fetches and processes symbols to prepare them for use in the profile output
3460# code. If the optional 'symbol_map' arg is not given, fetches symbols from
3461# $SYMBOL_PAGE for all PC values found in profile. Otherwise, the raw symbols
3462# are assumed to have already been fetched into 'symbol_map' and are simply
3463# extracted and processed.
3464sub FetchSymbols {
3465 my $pcset = shift;
3466 my $symbol_map = shift;
3467
3468 my %seen = ();
3469 my @pcs = grep { !$seen{$_}++ } keys(%$pcset); # uniq
3470
3471 if (!defined($symbol_map)) {
3472 my $post_data = join("+", sort((map {"0x" . "$_"} @pcs)));
3473
3474 open(POSTFILE, ">$main::tmpfile_sym");
3475 print POSTFILE $post_data;
3476 close(POSTFILE);
3477
3478 my $url = SymbolPageURL();
3479
3480 my $command_line;
3481 if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) {
3482 $url = ResolveRedirectionForCurl($url);
3483 $command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym",
3484 $url);
3485 } else {
3486 $command_line = (ShellEscape(@URL_FETCHER, "--post", $url)
3487 . " < " . ShellEscape($main::tmpfile_sym));
3488 }
3489 # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.
3490 my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"});
3491 open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line);
3492 $symbol_map = ReadSymbols(*SYMBOL{IO});
3493 close(SYMBOL);
3494 }
3495
3496 my $symbols = {};
3497 foreach my $pc (@pcs) {
3498 my $fullname;
3499 # For 64 bits binaries, symbols are extracted with 8 leading zeroes.
3500 # Then /symbol reads the long symbols in as uint64, and outputs
3501 # the result with a "0x%08llx" format which get rid of the zeroes.
3502 # By removing all the leading zeroes in both $pc and the symbols from
3503 # /symbol, the symbols match and are retrievable from the map.
3504 my $shortpc = $pc;
3505 $shortpc =~ s/^0*//;
3506 # Each line may have a list of names, which includes the function
3507 # and also other functions it has inlined. They are separated (in
3508 # PrintSymbolizedProfile), by --, which is illegal in function names.
3509 my $fullnames;
3510 if (defined($symbol_map->{$shortpc})) {
3511 $fullnames = $symbol_map->{$shortpc};
3512 } else {
3513 $fullnames = "0x" . $pc; # Just use addresses
3514 }
3515 my $sym = [];
3516 $symbols->{$pc} = $sym;
3517 foreach my $fullname (split("--", $fullnames)) {
3518 my $name = ShortFunctionName($fullname);
3519 push(@{$sym}, $name, "?", $fullname);
3520 }
3521 }
3522 return $symbols;
3523}
3524
3525sub BaseName {
3526 my $file_name = shift;
3527 $file_name =~ s!^.*/!!; # Remove directory name
3528 return $file_name;
3529}
3530
3531sub MakeProfileBaseName {
3532 my ($binary_name, $profile_name) = @_;
3533 my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
3534 my $binary_shortname = BaseName($binary_name);
3535 return sprintf("%s.%s.%s",
3536 $binary_shortname, $main::op_time, $host);
3537}
3538
3539sub FetchDynamicProfile {
3540 my $binary_name = shift;
3541 my $profile_name = shift;
3542 my $fetch_name_only = shift;
3543 my $encourage_patience = shift;
3544
3545 if (!IsProfileURL($profile_name)) {
3546 return $profile_name;
3547 } else {
3548 my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
3549 if ($path eq "" || $path eq "/") {
3550 # Missing type specifier defaults to cpu-profile
3551 $path = $PROFILE_PAGE;
3552 }
3553
3554 my $profile_file = MakeProfileBaseName($binary_name, $profile_name);
3555
3556 my $url = "$baseURL$path";
3557 my $fetch_timeout = undef;
3558 if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {
3559 if ($path =~ m/[?]/) {
3560 $url .= "&";
3561 } else {
3562 $url .= "?";
3563 }
3564 $url .= sprintf("seconds=%d", $main::opt_seconds);
3565 $fetch_timeout = $main::opt_seconds * 1.01 + 60;
3566 } else {
3567 # For non-CPU profiles, we add a type-extension to
3568 # the target profile file name.
3569 my $suffix = $path;
3570 $suffix =~ s,/,.,g;
3571 $profile_file .= $suffix;
3572 }
3573
3574 my $profile_dir = $ENV{"PPROF_TMPDIR"} || ($ENV{HOME} . "/pprof");
3575 if (! -d $profile_dir) {
3576 mkdir($profile_dir)
3577 || die("Unable to create profile directory $profile_dir: $!\n");
3578 }
3579 my $tmp_profile = "$profile_dir/.tmp.$profile_file";
3580 my $real_profile = "$profile_dir/$profile_file";
3581
3582 if ($fetch_name_only > 0) {
3583 return $real_profile;
3584 }
3585
3586 my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);
3587 my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile);
3588 if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){
3589 print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n";
3590 if ($encourage_patience) {
3591 print STDERR "Be patient...\n";
3592 }
3593 } else {
3594 print STDERR "Fetching $path profile from $url to\n ${real_profile}\n";
3595 }
3596
3597 (system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
3598 (system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n");
3599 print STDERR "Wrote profile to $real_profile\n";
3600 $main::collected_profile = $real_profile;
3601 return $main::collected_profile;
3602 }
3603}
3604
3605# Collect profiles in parallel
3606sub FetchDynamicProfiles {
3607 my $items = scalar(@main::pfile_args);
3608 my $levels = log($items) / log(2);
3609
3610 if ($items == 1) {
3611 $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1);
3612 } else {
3613 # math rounding issues
3614 if ((2 ** $levels) < $items) {
3615 $levels++;
3616 }
3617 my $count = scalar(@main::pfile_args);
3618 for (my $i = 0; $i < $count; $i++) {
3619 $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0);
3620 }
3621 print STDERR "Fetching $count profiles, Be patient...\n";
3622 FetchDynamicProfilesRecurse($levels, 0, 0);
3623 $main::collected_profile = join(" \\\n ", @main::profile_files);
3624 }
3625}
3626
3627# Recursively fork a process to get enough processes
3628# collecting profiles
3629sub FetchDynamicProfilesRecurse {
3630 my $maxlevel = shift;
3631 my $level = shift;
3632 my $position = shift;
3633
3634 if (my $pid = fork()) {
3635 $position = 0 | ($position << 1);
3636 TryCollectProfile($maxlevel, $level, $position);
3637 wait;
3638 } else {
3639 $position = 1 | ($position << 1);
3640 TryCollectProfile($maxlevel, $level, $position);
3641 cleanup();
3642 exit(0);
3643 }
3644}
3645
3646# Collect a single profile
3647sub TryCollectProfile {
3648 my $maxlevel = shift;
3649 my $level = shift;
3650 my $position = shift;
3651
3652 if ($level >= ($maxlevel - 1)) {
3653 if ($position < scalar(@main::pfile_args)) {
3654 FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);
3655 }
3656 } else {
3657 FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);
3658 }
3659}
3660
3661##### Parsing code #####
3662
3663# Provide a small streaming-read module to handle very large
3664# cpu-profile files. Stream in chunks along a sliding window.
3665# Provides an interface to get one 'slot', correctly handling
3666# endian-ness differences. A slot is one 32-bit or 64-bit word
3667# (depending on the input profile). We tell endianness and bit-size
3668# for the profile by looking at the first 8 bytes: in cpu profiles,
3669# the second slot is always 3 (we'll accept anything that's not 0).
3670BEGIN {
3671 package CpuProfileStream;
3672
3673 sub new {
3674 my ($class, $file, $fname) = @_;
3675 my $self = { file => $file,
3676 base => 0,
3677 stride => 512 * 1024, # must be a multiple of bitsize/8
3678 slots => [],
3679 unpack_code => "", # N for big-endian, V for little
3680 perl_is_64bit => 1, # matters if profile is 64-bit
3681 };
3682 bless $self, $class;
3683 # Let unittests adjust the stride
3684 if ($main::opt_test_stride > 0) {
3685 $self->{stride} = $main::opt_test_stride;
3686 }
3687 # Read the first two slots to figure out bitsize and endianness.
3688 my $slots = $self->{slots};
3689 my $str;
3690 read($self->{file}, $str, 8);
3691 # Set the global $address_length based on what we see here.
3692 # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars).
3693 $address_length = ($str eq (chr(0)x8)) ? 16 : 8;
3694 if ($address_length == 8) {
3695 if (substr($str, 6, 2) eq chr(0)x2) {
3696 $self->{unpack_code} = 'V'; # Little-endian.
3697 } elsif (substr($str, 4, 2) eq chr(0)x2) {
3698 $self->{unpack_code} = 'N'; # Big-endian
3699 } else {
3700 ::error("$fname: header size >= 2**16\n");
3701 }
3702 @$slots = unpack($self->{unpack_code} . "*", $str);
3703 } else {
3704 # If we're a 64-bit profile, check if we're a 64-bit-capable
3705 # perl. Otherwise, each slot will be represented as a float
3706 # instead of an int64, losing precision and making all the
3707 # 64-bit addresses wrong. We won't complain yet, but will
3708 # later if we ever see a value that doesn't fit in 32 bits.
3709 my $has_q = 0;
3710 eval { $has_q = pack("Q", "1") ? 1 : 1; };
3711 if (!$has_q) {
3712 $self->{perl_is_64bit} = 0;
3713 }
3714 read($self->{file}, $str, 8);
3715 if (substr($str, 4, 4) eq chr(0)x4) {
3716 # We'd love to use 'Q', but it's a) not universal, b) not endian-proof.
3717 $self->{unpack_code} = 'V'; # Little-endian.
3718 } elsif (substr($str, 0, 4) eq chr(0)x4) {
3719 $self->{unpack_code} = 'N'; # Big-endian
3720 } else {
3721 ::error("$fname: header size >= 2**32\n");
3722 }
3723 my @pair = unpack($self->{unpack_code} . "*", $str);
3724 # Since we know one of the pair is 0, it's fine to just add them.
3725 @$slots = (0, $pair[0] + $pair[1]);
3726 }
3727 return $self;
3728 }
3729
3730 # Load more data when we access slots->get(X) which is not yet in memory.
3731 sub overflow {
3732 my ($self) = @_;
3733 my $slots = $self->{slots};
3734 $self->{base} += $#$slots + 1; # skip over data we're replacing
3735 my $str;
3736 read($self->{file}, $str, $self->{stride});
3737 if ($address_length == 8) { # the 32-bit case
3738 # This is the easy case: unpack provides 32-bit unpacking primitives.
3739 @$slots = unpack($self->{unpack_code} . "*", $str);
3740 } else {
3741 # We need to unpack 32 bits at a time and combine.
3742 my @b32_values = unpack($self->{unpack_code} . "*", $str);
3743 my @b64_values = ();
3744 for (my $i = 0; $i < $#b32_values; $i += 2) {
3745 # TODO(csilvers): if this is a 32-bit perl, the math below
3746 # could end up in a too-large int, which perl will promote
3747 # to a double, losing necessary precision. Deal with that.
3748 # Right now, we just die.
3749 my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);
3750 if ($self->{unpack_code} eq 'N') { # big-endian
3751 ($lo, $hi) = ($hi, $lo);
3752 }
3753 my $value = $lo + $hi * (2**32);
3754 if (!$self->{perl_is_64bit} && # check value is exactly represented
3755 (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {
3756 ::error("Need a 64-bit perl to process this 64-bit profile.\n");
3757 }
3758 push(@b64_values, $value);
3759 }
3760 @$slots = @b64_values;
3761 }
3762 }
3763
3764 # Access the i-th long in the file (logically), or -1 at EOF.
3765 sub get {
3766 my ($self, $idx) = @_;
3767 my $slots = $self->{slots};
3768 while ($#$slots >= 0) {
3769 if ($idx < $self->{base}) {
3770 # The only time we expect a reference to $slots[$i - something]
3771 # after referencing $slots[$i] is reading the very first header.
3772 # Since $stride > |header|, that shouldn't cause any lookback
3773 # errors. And everything after the header is sequential.
3774 print STDERR "Unexpected look-back reading CPU profile";
3775 return -1; # shrug, don't know what better to return
3776 } elsif ($idx > $self->{base} + $#$slots) {
3777 $self->overflow();
3778 } else {
3779 return $slots->[$idx - $self->{base}];
3780 }
3781 }
3782 # If we get here, $slots is [], which means we've reached EOF
3783 return -1; # unique since slots is supposed to hold unsigned numbers
3784 }
3785}
3786
3787# Reads the top, 'header' section of a profile, and returns the last
3788# line of the header, commonly called a 'header line'. The header
3789# section of a profile consists of zero or more 'command' lines that
3790# are instructions to pprof, which pprof executes when reading the
3791# header. All 'command' lines start with a %. After the command
3792# lines is the 'header line', which is a profile-specific line that
3793# indicates what type of profile it is, and perhaps other global
3794# information about the profile. For instance, here's a header line
3795# for a heap profile:
3796# heap profile: 53: 38236 [ 5525: 1284029] @ heapprofile
3797# For historical reasons, the CPU profile does not contain a text-
3798# readable header line. If the profile looks like a CPU profile,
3799# this function returns "". If no header line could be found, this
3800# function returns undef.
3801#
3802# The following commands are recognized:
3803# %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'
3804#
3805# The input file should be in binmode.
3806sub ReadProfileHeader {
3807 local *PROFILE = shift;
3808 my $firstchar = "";
3809 my $line = "";
3810 read(PROFILE, $firstchar, 1);
3811 seek(PROFILE, -1, 1); # unread the firstchar
3812 if ($firstchar !~ /[[:print:]]/) { # is not a text character
3813 return "";
3814 }
3815 while (defined($line = <PROFILE>)) {
3816 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
3817 if ($line =~ /^%warn\s+(.*)/) { # 'warn' command
3818 # Note this matches both '%warn blah\n' and '%warn\n'.
3819 print STDERR "WARNING: $1\n"; # print the rest of the line
3820 } elsif ($line =~ /^%/) {
3821 print STDERR "Ignoring unknown command from profile header: $line";
3822 } else {
3823 # End of commands, must be the header line.
3824 return $line;
3825 }
3826 }
3827 return undef; # got to EOF without seeing a header line
3828}
3829
3830sub IsSymbolizedProfileFile {
3831 my $file_name = shift;
3832 if (!(-e $file_name) || !(-r $file_name)) {
3833 return 0;
3834 }
3835 # Check if the file contains a symbol-section marker.
3836 open(TFILE, "<$file_name");
3837 binmode TFILE;
3838 my $firstline = ReadProfileHeader(*TFILE);
3839 close(TFILE);
3840 if (!$firstline) {
3841 return 0;
3842 }
3843 $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash
3844 my $symbol_marker = $&;
3845 return $firstline =~ /^--- *$symbol_marker/;
3846}
3847
3848# Parse profile generated by common/profiler.cc and return a reference
3849# to a map:
3850# $result->{version} Version number of profile file
3851# $result->{period} Sampling period (in microseconds)
3852# $result->{profile} Profile object
3853# $result->{map} Memory map info from profile
3854# $result->{pcs} Hash of all PC values seen, key is hex address
3855sub ReadProfile {
3856 my $prog = shift;
3857 my $fname = shift;
3858 my $result; # return value
3859
3860 $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash
3861 my $contention_marker = $&;
3862 $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash
3863 my $growth_marker = $&;
3864 $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash
3865 my $symbol_marker = $&;
3866 $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash
3867 my $profile_marker = $&;
3868
3869 # Look at first line to see if it is a heap or a CPU profile.
3870 # CPU profile may start with no header at all, and just binary data
3871 # (starting with \0\0\0\0) -- in that case, don't try to read the
3872 # whole firstline, since it may be gigabytes(!) of data.
3873 open(PROFILE, "<$fname") || error("$fname: $!\n");
3874 binmode PROFILE; # New perls do UTF-8 processing
3875 my $header = ReadProfileHeader(*PROFILE);
3876 if (!defined($header)) { # means "at EOF"
3877 error("Profile is empty.\n");
3878 }
3879
3880 my $symbols;
3881 if ($header =~ m/^--- *$symbol_marker/o) {
3882 # Verify that the user asked for a symbolized profile
3883 if (!$main::use_symbolized_profile) {
3884 # we have both a binary and symbolized profiles, abort
3885 error("FATAL ERROR: Symbolized profile\n $fname\ncannot be used with " .
3886 "a binary arg. Try again without passing\n $prog\n");
3887 }
3888 # Read the symbol section of the symbolized profile file.
3889 $symbols = ReadSymbols(*PROFILE{IO});
3890 # Read the next line to get the header for the remaining profile.
3891 $header = ReadProfileHeader(*PROFILE) || "";
3892 }
3893
3894 $main::profile_type = '';
3895 if ($header =~ m/^heap profile:.*$growth_marker/o) {
3896 $main::profile_type = 'growth';
3897 $result = ReadHeapProfile($prog, *PROFILE, $header);
3898 } elsif ($header =~ m/^heap profile:/) {
3899 $main::profile_type = 'heap';
3900 $result = ReadHeapProfile($prog, *PROFILE, $header);
3901 } elsif ($header =~ m/^--- *$contention_marker/o) {
3902 $main::profile_type = 'contention';
3903 $result = ReadSynchProfile($prog, *PROFILE);
3904 } elsif ($header =~ m/^--- *Stacks:/) {
3905 print STDERR
3906 "Old format contention profile: mistakenly reports " .
3907 "condition variable signals as lock contentions.\n";
3908 $main::profile_type = 'contention';
3909 $result = ReadSynchProfile($prog, *PROFILE);
3910 } elsif ($header =~ m/^--- *$profile_marker/) {
3911 # the binary cpu profile data starts immediately after this line
3912 $main::profile_type = 'cpu';
3913 $result = ReadCPUProfile($prog, $fname, *PROFILE);
3914 } else {
3915 if (defined($symbols)) {
3916 # a symbolized profile contains a format we don't recognize, bail out
3917 error("$fname: Cannot recognize profile section after symbols.\n");
3918 }
3919 # no ascii header present -- must be a CPU profile
3920 $main::profile_type = 'cpu';
3921 $result = ReadCPUProfile($prog, $fname, *PROFILE);
3922 }
3923
3924 close(PROFILE);
3925
3926 # if we got symbols along with the profile, return those as well
3927 if (defined($symbols)) {
3928 $result->{symbols} = $symbols;
3929 }
3930
3931 return $result;
3932}
3933
3934# Subtract one from caller pc so we map back to call instr.
3935# However, don't do this if we're reading a symbolized profile
3936# file, in which case the subtract-one was done when the file
3937# was written.
3938#
3939# We apply the same logic to all readers, though ReadCPUProfile uses an
3940# independent implementation.
3941sub FixCallerAddresses {
3942 my $stack = shift;
3943 if ($main::use_symbolized_profile) {
3944 return $stack;
3945 } else {
3946 $stack =~ /(\s)/;
3947 my $delimiter = $1;
3948 my @addrs = split(' ', $stack);
3949 my @fixedaddrs;
3950 $#fixedaddrs = $#addrs;
3951 if ($#addrs >= 0) {
3952 $fixedaddrs[0] = $addrs[0];
3953 }
3954 for (my $i = 1; $i <= $#addrs; $i++) {
3955 $fixedaddrs[$i] = AddressSub($addrs[$i], "0x1");
3956 }
3957 return join $delimiter, @fixedaddrs;
3958 }
3959}
3960
3961# CPU profile reader
3962sub ReadCPUProfile {
3963 my $prog = shift;
3964 my $fname = shift; # just used for logging
3965 local *PROFILE = shift;
3966 my $version;
3967 my $period;
3968 my $i;
3969 my $profile = {};
3970 my $pcs = {};
3971
3972 # Parse string into array of slots.
3973 my $slots = CpuProfileStream->new(*PROFILE, $fname);
3974
3975 # Read header. The current header version is a 5-element structure
3976 # containing:
3977 # 0: header count (always 0)
3978 # 1: header "words" (after this one: 3)
3979 # 2: format version (0)
3980 # 3: sampling period (usec)
3981 # 4: unused padding (always 0)
3982 if ($slots->get(0) != 0 ) {
3983 error("$fname: not a profile file, or old format profile file\n");
3984 }
3985 $i = 2 + $slots->get(1);
3986 $version = $slots->get(2);
3987 $period = $slots->get(3);
3988 # Do some sanity checking on these header values.
3989 if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {
3990 error("$fname: not a profile file, or corrupted profile file\n");
3991 }
3992
3993 # Parse profile
3994 while ($slots->get($i) != -1) {
3995 my $n = $slots->get($i++);
3996 my $d = $slots->get($i++);
3997 if ($d > (2**16)) { # TODO(csilvers): what's a reasonable max-stack-depth?
3998 my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8));
3999 print STDERR "At index $i (address $addr):\n";
4000 error("$fname: stack trace depth >= 2**32\n");
4001 }
4002 if ($slots->get($i) == 0) {
4003 # End of profile data marker
4004 $i += $d;
4005 last;
4006 }
4007
4008 # Make key out of the stack entries
4009 my @k = ();
4010 for (my $j = 0; $j < $d; $j++) {
4011 my $pc = $slots->get($i+$j);
4012 # Subtract one from caller pc so we map back to call instr.
4013 # However, don't do this if we're reading a symbolized profile
4014 # file, in which case the subtract-one was done when the file
4015 # was written.
4016 if ($j > 0 && !$main::use_symbolized_profile) {
4017 $pc--;
4018 }
4019 $pc = sprintf("%0*x", $address_length, $pc);
4020 $pcs->{$pc} = 1;
4021 push @k, $pc;
4022 }
4023
4024 AddEntry($profile, (join "\n", @k), $n);
4025 $i += $d;
4026 }
4027
4028 # Parse map
4029 my $map = '';
4030 seek(PROFILE, $i * ($address_length / 2), 0);
4031 read(PROFILE, $map, (stat PROFILE)[7]);
4032
4033 my $r = {};
4034 $r->{version} = $version;
4035 $r->{period} = $period;
4036 $r->{profile} = $profile;
4037 $r->{libs} = ParseLibraries($prog, $map, $pcs);
4038 $r->{pcs} = $pcs;
4039
4040 return $r;
4041}
4042
4043sub ReadHeapProfile {
4044 my $prog = shift;
4045 local *PROFILE = shift;
4046 my $header = shift;
4047
4048 my $index = 1;
4049 if ($main::opt_inuse_space) {
4050 $index = 1;
4051 } elsif ($main::opt_inuse_objects) {
4052 $index = 0;
4053 } elsif ($main::opt_alloc_space) {
4054 $index = 3;
4055 } elsif ($main::opt_alloc_objects) {
4056 $index = 2;
4057 }
4058
4059 # Find the type of this profile. The header line looks like:
4060 # heap profile: 1246: 8800744 [ 1246: 8800744] @ <heap-url>/266053
4061 # There are two pairs <count: size>, the first inuse objects/space, and the
4062 # second allocated objects/space. This is followed optionally by a profile
4063 # type, and if that is present, optionally by a sampling frequency.
4064 # For remote heap profiles (v1):
4065 # The interpretation of the sampling frequency is that the profiler, for
4066 # each sample, calculates a uniformly distributed random integer less than
4067 # the given value, and records the next sample after that many bytes have
4068 # been allocated. Therefore, the expected sample interval is half of the
4069 # given frequency. By default, if not specified, the expected sample
4070 # interval is 128KB. Only remote-heap-page profiles are adjusted for
4071 # sample size.
4072 # For remote heap profiles (v2):
4073 # The sampling frequency is the rate of a Poisson process. This means that
4074 # the probability of sampling an allocation of size X with sampling rate Y
4075 # is 1 - exp(-X/Y)
4076 # For version 2, a typical header line might look like this:
4077 # heap profile: 1922: 127792360 [ 1922: 127792360] @ <heap-url>_v2/524288
4078 # the trailing number (524288) is the sampling rate. (Version 1 showed
4079 # double the 'rate' here)
4080 my $sampling_algorithm = 0;
4081 my $sample_adjustment = 0;
4082 chomp($header);
4083 my $type = "unknown";
4084 if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") {
4085 if (defined($6) && ($6 ne '')) {
4086 $type = $6;
4087 my $sample_period = $8;
4088 # $type is "heapprofile" for profiles generated by the
4089 # heap-profiler, and either "heap" or "heap_v2" for profiles
4090 # generated by sampling directly within tcmalloc. It can also
4091 # be "growth" for heap-growth profiles. The first is typically
4092 # found for profiles generated locally, and the others for
4093 # remote profiles.
4094 if (($type eq "heapprofile") || ($type !~ /heap/) ) {
4095 # No need to adjust for the sampling rate with heap-profiler-derived data
4096 $sampling_algorithm = 0;
4097 } elsif ($type =~ /_v2/) {
4098 $sampling_algorithm = 2; # version 2 sampling
4099 if (defined($sample_period) && ($sample_period ne '')) {
4100 $sample_adjustment = int($sample_period);
4101 }
4102 } else {
4103 $sampling_algorithm = 1; # version 1 sampling
4104 if (defined($sample_period) && ($sample_period ne '')) {
4105 $sample_adjustment = int($sample_period)/2;
4106 }
4107 }
4108 } else {
4109 # We detect whether or not this is a remote-heap profile by checking
4110 # that the total-allocated stats ($n2,$s2) are exactly the
4111 # same as the in-use stats ($n1,$s1). It is remotely conceivable
4112 # that a non-remote-heap profile may pass this check, but it is hard
4113 # to imagine how that could happen.
4114 # In this case it's so old it's guaranteed to be remote-heap version 1.
4115 my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
4116 if (($n1 == $n2) && ($s1 == $s2)) {
4117 # This is likely to be a remote-heap based sample profile
4118 $sampling_algorithm = 1;
4119 }
4120 }
4121 }
4122
4123 if ($sampling_algorithm > 0) {
4124 # For remote-heap generated profiles, adjust the counts and sizes to
4125 # account for the sample rate (we sample once every 128KB by default).
4126 if ($sample_adjustment == 0) {
4127 # Turn on profile adjustment.
4128 $sample_adjustment = 128*1024;
4129 print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n";
4130 } else {
4131 printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n",
4132 $sample_adjustment);
4133 }
4134 if ($sampling_algorithm > 1) {
4135 # We don't bother printing anything for the original version (version 1)
4136 printf STDERR "Heap version $sampling_algorithm\n";
4137 }
4138 }
4139
4140 my $profile = {};
4141 my $pcs = {};
4142 my $map = "";
4143
4144 while (<PROFILE>) {
4145 s/\r//g; # turn windows-looking lines into unix-looking lines
4146 if (/^MAPPED_LIBRARIES:/) {
4147 # Read the /proc/self/maps data
4148 while (<PROFILE>) {
4149 s/\r//g; # turn windows-looking lines into unix-looking lines
4150 $map .= $_;
4151 }
4152 last;
4153 }
4154
4155 if (/^--- Memory map:/) {
4156 # Read /proc/self/maps data as formatted by DumpAddressMap()
4157 my $buildvar = "";
4158 while (<PROFILE>) {
4159 s/\r//g; # turn windows-looking lines into unix-looking lines
4160 # Parse "build=<dir>" specification if supplied
4161 if (m/^\s*build=(.*)\n/) {
4162 $buildvar = $1;
4163 }
4164
4165 # Expand "$build" variable if available
4166 $_ =~ s/\$build\b/$buildvar/g;
4167
4168 $map .= $_;
4169 }
4170 last;
4171 }
4172
4173 # Read entry of the form:
4174 # <count1>: <bytes1> [<count2>: <bytes2>] @ a1 a2 a3 ... an
4175 s/^\s*//;
4176 s/\s*$//;
4177 if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) {
4178 my $stack = $5;
4179 my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
4180
4181 if ($sample_adjustment) {
4182 if ($sampling_algorithm == 2) {
4183 # Remote-heap version 2
4184 # The sampling frequency is the rate of a Poisson process.
4185 # This means that the probability of sampling an allocation of
4186 # size X with sampling rate Y is 1 - exp(-X/Y)
4187 if ($n1 != 0) {
4188 my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
4189 my $scale_factor = 1/(1 - exp(-$ratio));
4190 $n1 *= $scale_factor;
4191 $s1 *= $scale_factor;
4192 }
4193 if ($n2 != 0) {
4194 my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
4195 my $scale_factor = 1/(1 - exp(-$ratio));
4196 $n2 *= $scale_factor;
4197 $s2 *= $scale_factor;
4198 }
4199 } else {
4200 # Remote-heap version 1
4201 my $ratio;
4202 $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
4203 if ($ratio < 1) {
4204 $n1 /= $ratio;
4205 $s1 /= $ratio;
4206 }
4207 $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
4208 if ($ratio < 1) {
4209 $n2 /= $ratio;
4210 $s2 /= $ratio;
4211 }
4212 }
4213 }
4214
4215 my @counts = ($n1, $s1, $n2, $s2);
4216 $stack = FixCallerAddresses($stack);
4217 push @stackTraces, "$n1 $s1 $n2 $s2 $stack";
4218 AddEntries($profile, $pcs, $stack, $counts[$index]);
4219 }
4220 }
4221
4222 my $r = {};
4223 $r->{version} = "heap";
4224 $r->{period} = 1;
4225 $r->{profile} = $profile;
4226 $r->{libs} = ParseLibraries($prog, $map, $pcs);
4227 $r->{pcs} = $pcs;
4228 return $r;
4229}
4230
4231sub ReadSynchProfile {
4232 my $prog = shift;
4233 local *PROFILE = shift;
4234 my $header = shift;
4235
4236 my $map = '';
4237 my $profile = {};
4238 my $pcs = {};
4239 my $sampling_period = 1;
4240 my $cyclespernanosec = 2.8; # Default assumption for old binaries
4241 my $seen_clockrate = 0;
4242 my $line;
4243
4244 my $index = 0;
4245 if ($main::opt_total_delay) {
4246 $index = 0;
4247 } elsif ($main::opt_contentions) {
4248 $index = 1;
4249 } elsif ($main::opt_mean_delay) {
4250 $index = 2;
4251 }
4252
4253 while ( $line = <PROFILE> ) {
4254 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
4255 if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) {
4256 my ($cycles, $count, $stack) = ($1, $2, $3);
4257
4258 # Convert cycles to nanoseconds
4259 $cycles /= $cyclespernanosec;
4260
4261 # Adjust for sampling done by application
4262 $cycles *= $sampling_period;
4263 $count *= $sampling_period;
4264
4265 my @values = ($cycles, $count, $cycles / $count);
4266 AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);
4267
4268 } elsif ( $line =~ /^(slow release).*thread \d+ \@\s*(.*?)\s*$/ ||
4269 $line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) {
4270 my ($cycles, $stack) = ($1, $2);
4271 if ($cycles !~ /^\d+$/) {
4272 next;
4273 }
4274
4275 # Convert cycles to nanoseconds
4276 $cycles /= $cyclespernanosec;
4277
4278 # Adjust for sampling done by application
4279 $cycles *= $sampling_period;
4280
4281 AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);
4282
4283 } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {
4284 my ($variable, $value) = ($1,$2);
4285 for ($variable, $value) {
4286 s/^\s+//;
4287 s/\s+$//;
4288 }
4289 if ($variable eq "cycles/second") {
4290 $cyclespernanosec = $value / 1e9;
4291 $seen_clockrate = 1;
4292 } elsif ($variable eq "sampling period") {
4293 $sampling_period = $value;
4294 } elsif ($variable eq "ms since reset") {
4295 # Currently nothing is done with this value in pprof
4296 # So we just silently ignore it for now
4297 } elsif ($variable eq "discarded samples") {
4298 # Currently nothing is done with this value in pprof
4299 # So we just silently ignore it for now
4300 } else {
4301 printf STDERR ("Ignoring unnknown variable in /contention output: " .
4302 "'%s' = '%s'\n",$variable,$value);
4303 }
4304 } else {
4305 # Memory map entry
4306 $map .= $line;
4307 }
4308 }
4309
4310 if (!$seen_clockrate) {
4311 printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n",
4312 $cyclespernanosec);
4313 }
4314
4315 my $r = {};
4316 $r->{version} = 0;
4317 $r->{period} = $sampling_period;
4318 $r->{profile} = $profile;
4319 $r->{libs} = ParseLibraries($prog, $map, $pcs);
4320 $r->{pcs} = $pcs;
4321 return $r;
4322}
4323
4324# Given a hex value in the form "0x1abcd" or "1abcd", return either
4325# "0001abcd" or "000000000001abcd", depending on the current (global)
4326# address length.
4327sub HexExtend {
4328 my $addr = shift;
4329
4330 $addr =~ s/^(0x)?0*//;
4331 my $zeros_needed = $address_length - length($addr);
4332 if ($zeros_needed < 0) {
4333 printf STDERR "Warning: address $addr is longer than address length $address_length\n";
4334 return $addr;
4335 }
4336 return ("0" x $zeros_needed) . $addr;
4337}
4338
4339##### Symbol extraction #####
4340
4341# Aggressively search the lib_prefix values for the given library
4342# If all else fails, just return the name of the library unmodified.
4343# If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so"
4344# it will search the following locations in this order, until it finds a file:
4345# /my/path/lib/dir/mylib.so
4346# /other/path/lib/dir/mylib.so
4347# /my/path/dir/mylib.so
4348# /other/path/dir/mylib.so
4349# /my/path/mylib.so
4350# /other/path/mylib.so
4351# /lib/dir/mylib.so (returned as last resort)
4352sub FindLibrary {
4353 my $file = shift;
4354 my $suffix = $file;
4355
4356 # Search for the library as described above
4357 do {
4358 foreach my $prefix (@prefix_list) {
4359 my $fullpath = $prefix . $suffix;
4360 if (-e $fullpath) {
4361 return $fullpath;
4362 }
4363 }
4364 } while ($suffix =~ s|^/[^/]+/|/|);
4365 return $file;
4366}
4367
4368# Return path to library with debugging symbols.
4369# For libc libraries, the copy in /usr/lib/debug contains debugging symbols
4370sub DebuggingLibrary {
4371 my $file = shift;
4372 if ($file =~ m|^/| && -f "/usr/lib/debug$file") {
4373 return "/usr/lib/debug$file";
4374 }
4375 if ($file =~ m|^/| && -f "/usr/lib/debug$file.debug") {
4376 return "/usr/lib/debug$file.debug";
4377 }
4378 return undef;
4379}
4380
4381# Parse text section header of a library using objdump
4382sub ParseTextSectionHeaderFromObjdump {
4383 my $lib = shift;
4384
4385 my $size = undef;
4386 my $vma;
4387 my $file_offset;
4388 # Get objdump output from the library file to figure out how to
4389 # map between mapped addresses and addresses in the library.
4390 my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib);
4391 open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
4392 while (<OBJDUMP>) {
4393 s/\r//g; # turn windows-looking lines into unix-looking lines
4394 # Idx Name Size VMA LMA File off Algn
4395 # 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4
4396 # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file
4397 # offset may still be 8. But AddressSub below will still handle that.
4398 my @x = split;
4399 if (($#x >= 6) && ($x[1] eq '.text')) {
4400 $size = $x[2];
4401 $vma = $x[3];
4402 $file_offset = $x[5];
4403 last;
4404 }
4405 }
4406 close(OBJDUMP);
4407
4408 if (!defined($size)) {
4409 return undef;
4410 }
4411
4412 my $r = {};
4413 $r->{size} = $size;
4414 $r->{vma} = $vma;
4415 $r->{file_offset} = $file_offset;
4416
4417 return $r;
4418}
4419
4420# Parse text section header of a library using otool (on OS X)
4421sub ParseTextSectionHeaderFromOtool {
4422 my $lib = shift;
4423
4424 my $size = undef;
4425 my $vma = undef;
4426 my $file_offset = undef;
4427 # Get otool output from the library file to figure out how to
4428 # map between mapped addresses and addresses in the library.
4429 my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib);
4430 open(OTOOL, "$command |") || error("$command: $!\n");
4431 my $cmd = "";
4432 my $sectname = "";
4433 my $segname = "";
4434 foreach my $line (<OTOOL>) {
4435 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
4436 # Load command <#>
4437 # cmd LC_SEGMENT
4438 # [...]
4439 # Section
4440 # sectname __text
4441 # segname __TEXT
4442 # addr 0x000009f8
4443 # size 0x00018b9e
4444 # offset 2552
4445 # align 2^2 (4)
4446 # We will need to strip off the leading 0x from the hex addresses,
4447 # and convert the offset into hex.
4448 if ($line =~ /Load command/) {
4449 $cmd = "";
4450 $sectname = "";
4451 $segname = "";
4452 } elsif ($line =~ /Section/) {
4453 $sectname = "";
4454 $segname = "";
4455 } elsif ($line =~ /cmd (\w+)/) {
4456 $cmd = $1;
4457 } elsif ($line =~ /sectname (\w+)/) {
4458 $sectname = $1;
4459 } elsif ($line =~ /segname (\w+)/) {
4460 $segname = $1;
4461 } elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") &&
4462 $sectname eq "__text" &&
4463 $segname eq "__TEXT")) {
4464 next;
4465 } elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) {
4466 $vma = $1;
4467 } elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) {
4468 $size = $1;
4469 } elsif ($line =~ /\boffset ([0-9]+)/) {
4470 $file_offset = sprintf("%016x", $1);
4471 }
4472 if (defined($vma) && defined($size) && defined($file_offset)) {
4473 last;
4474 }
4475 }
4476 close(OTOOL);
4477
4478 if (!defined($vma) || !defined($size) || !defined($file_offset)) {
4479 return undef;
4480 }
4481
4482 my $r = {};
4483 $r->{size} = $size;
4484 $r->{vma} = $vma;
4485 $r->{file_offset} = $file_offset;
4486
4487 return $r;
4488}
4489
4490sub ParseTextSectionHeader {
4491 # obj_tool_map("otool") is only defined if we're in a Mach-O environment
4492 if (defined($obj_tool_map{"otool"})) {
4493 my $r = ParseTextSectionHeaderFromOtool(@_);
4494 if (defined($r)){
4495 return $r;
4496 }
4497 }
4498 # If otool doesn't work, or we don't have it, fall back to objdump
4499 return ParseTextSectionHeaderFromObjdump(@_);
4500}
4501
4502# Split /proc/pid/maps dump into a list of libraries
4503sub ParseLibraries {
4504 return if $main::use_symbol_page; # We don't need libraries info.
4505 my $prog = Cwd::abs_path(shift);
4506 my $map = shift;
4507 my $pcs = shift;
4508
4509 my $result = [];
4510 my $h = "[a-f0-9]+";
4511 my $zero_offset = HexExtend("0");
4512
4513 my $buildvar = "";
4514 my $priorlib = "";
4515 foreach my $l (split("\n", $map)) {
4516 if ($l =~ m/^\s*build=(.*)$/) {
4517 $buildvar = $1;
4518 }
4519
4520 my $start;
4521 my $finish;
4522 my $offset;
4523 my $lib;
4524 if ($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(.+\.(so|dll|dylib|bundle)((\.\d+)+\w*(\.\d+){0,3})?)$/i) {
4525 # Full line from /proc/self/maps. Example:
4526 # 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so
4527 $start = HexExtend($1);
4528 $finish = HexExtend($2);
4529 $offset = HexExtend($3);
4530 $lib = $4;
4531 $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths
4532 } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) {
4533 # Cooked line from DumpAddressMap. Example:
4534 # 40000000-40015000: /lib/ld-2.3.2.so
4535 $start = HexExtend($1);
4536 $finish = HexExtend($2);
4537 $offset = $zero_offset;
4538 $lib = $3;
4539 } elsif (($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+)$/i) && ($4 eq $prog)) {
4540 # PIEs and address space randomization do not play well with our
4541 # default assumption that main executable is at lowest
4542 # addresses. So we're detecting main executable in
4543 # /proc/self/maps as well.
4544 $start = HexExtend($1);
4545 $finish = HexExtend($2);
4546 $offset = HexExtend($3);
4547 $lib = $4;
4548 $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths
4549 } else {
4550 next;
4551 }
4552
4553 # Expand "$build" variable if available
4554 $lib =~ s/\$build\b/$buildvar/g;
4555
4556 $lib = FindLibrary($lib);
4557
4558 # Check for pre-relocated libraries, which use pre-relocated symbol tables
4559 # and thus require adjusting the offset that we'll use to translate
4560 # VM addresses into symbol table addresses.
4561 # Only do this if we're not going to fetch the symbol table from a
4562 # debugging copy of the library.
4563 if (!DebuggingLibrary($lib)) {
4564 my $text = ParseTextSectionHeader($lib);
4565 if (defined($text)) {
4566 my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});
4567 $offset = AddressAdd($offset, $vma_offset);
4568 }
4569 }
4570
4571 # If we find multiple executable segments for a single library, merge them
4572 # into a single entry that spans the complete address range.
4573 if ($lib eq $priorlib) {
4574 my $prior = pop(@{$result});
4575 $start = @$prior[1];
4576 # TODO $offset may be wrong if .text is not in the final segment.
4577 }
4578
4579 push(@{$result}, [$lib, $start, $finish, $offset]);
4580 $priorlib = $lib;
4581 }
4582
4583 # Append special entry for additional library (not relocated)
4584 if ($main::opt_lib ne "") {
4585 my $text = ParseTextSectionHeader($main::opt_lib);
4586 if (defined($text)) {
4587 my $start = $text->{vma};
4588 my $finish = AddressAdd($start, $text->{size});
4589
4590 push(@{$result}, [$main::opt_lib, $start, $finish, $start]);
4591 }
4592 }
4593
4594 # Append special entry for the main program. This covers
4595 # 0..max_pc_value_seen, so that we assume pc values not found in one
4596 # of the library ranges will be treated as coming from the main
4597 # program binary.
4598 my $min_pc = HexExtend("0");
4599 my $max_pc = $min_pc; # find the maximal PC value in any sample
4600 foreach my $pc (keys(%{$pcs})) {
4601 if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }
4602 }
4603 push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);
4604
4605 return $result;
4606}
4607
4608# Add two hex addresses of length $address_length.
4609# Run pprof --test for unit test if this is changed.
4610sub AddressAdd {
4611 my $addr1 = shift;
4612 my $addr2 = shift;
4613 my $sum;
4614
4615 if ($address_length == 8) {
4616 # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4617 $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);
4618 return sprintf("%08x", $sum);
4619
4620 } else {
4621 # Do the addition in 7-nibble chunks to trivialize carry handling.
4622
4623 if ($main::opt_debug and $main::opt_test) {
4624 print STDERR "AddressAdd $addr1 + $addr2 = ";
4625 }
4626
4627 my $a1 = substr($addr1,-7);
4628 $addr1 = substr($addr1,0,-7);
4629 my $a2 = substr($addr2,-7);
4630 $addr2 = substr($addr2,0,-7);
4631 $sum = hex($a1) + hex($a2);
4632 my $c = 0;
4633 if ($sum > 0xfffffff) {
4634 $c = 1;
4635 $sum -= 0x10000000;
4636 }
4637 my $r = sprintf("%07x", $sum);
4638
4639 $a1 = substr($addr1,-7);
4640 $addr1 = substr($addr1,0,-7);
4641 $a2 = substr($addr2,-7);
4642 $addr2 = substr($addr2,0,-7);
4643 $sum = hex($a1) + hex($a2) + $c;
4644 $c = 0;
4645 if ($sum > 0xfffffff) {
4646 $c = 1;
4647 $sum -= 0x10000000;
4648 }
4649 $r = sprintf("%07x", $sum) . $r;
4650
4651 $sum = hex($addr1) + hex($addr2) + $c;
4652 if ($sum > 0xff) { $sum -= 0x100; }
4653 $r = sprintf("%02x", $sum) . $r;
4654
4655 if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; }
4656
4657 return $r;
4658 }
4659}
4660
4661
4662# Subtract two hex addresses of length $address_length.
4663# Run pprof --test for unit test if this is changed.
4664sub AddressSub {
4665 my $addr1 = shift;
4666 my $addr2 = shift;
4667 my $diff;
4668
4669 if ($address_length == 8) {
4670 # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4671 $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);
4672 return sprintf("%08x", $diff);
4673
4674 } else {
4675 # Do the addition in 7-nibble chunks to trivialize borrow handling.
4676 # if ($main::opt_debug) { print STDERR "AddressSub $addr1 - $addr2 = "; }
4677
4678 my $a1 = hex(substr($addr1,-7));
4679 $addr1 = substr($addr1,0,-7);
4680 my $a2 = hex(substr($addr2,-7));
4681 $addr2 = substr($addr2,0,-7);
4682 my $b = 0;
4683 if ($a2 > $a1) {
4684 $b = 1;
4685 $a1 += 0x10000000;
4686 }
4687 $diff = $a1 - $a2;
4688 my $r = sprintf("%07x", $diff);
4689
4690 $a1 = hex(substr($addr1,-7));
4691 $addr1 = substr($addr1,0,-7);
4692 $a2 = hex(substr($addr2,-7)) + $b;
4693 $addr2 = substr($addr2,0,-7);
4694 $b = 0;
4695 if ($a2 > $a1) {
4696 $b = 1;
4697 $a1 += 0x10000000;
4698 }
4699 $diff = $a1 - $a2;
4700 $r = sprintf("%07x", $diff) . $r;
4701
4702 $a1 = hex($addr1);
4703 $a2 = hex($addr2) + $b;
4704 if ($a2 > $a1) { $a1 += 0x100; }
4705 $diff = $a1 - $a2;
4706 $r = sprintf("%02x", $diff) . $r;
4707
4708 # if ($main::opt_debug) { print STDERR "$r\n"; }
4709
4710 return $r;
4711 }
4712}
4713
4714# Increment a hex addresses of length $address_length.
4715# Run pprof --test for unit test if this is changed.
4716sub AddressInc {
4717 my $addr = shift;
4718 my $sum;
4719
4720 if ($address_length == 8) {
4721 # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
4722 $sum = (hex($addr)+1) % (0x10000000 * 16);
4723 return sprintf("%08x", $sum);
4724
4725 } else {
4726 # Do the addition in 7-nibble chunks to trivialize carry handling.
4727 # We are always doing this to step through the addresses in a function,
4728 # and will almost never overflow the first chunk, so we check for this
4729 # case and exit early.
4730
4731 # if ($main::opt_debug) { print STDERR "AddressInc $addr1 = "; }
4732
4733 my $a1 = substr($addr,-7);
4734 $addr = substr($addr,0,-7);
4735 $sum = hex($a1) + 1;
4736 my $r = sprintf("%07x", $sum);
4737 if ($sum <= 0xfffffff) {
4738 $r = $addr . $r;
4739 # if ($main::opt_debug) { print STDERR "$r\n"; }
4740 return HexExtend($r);
4741 } else {
4742 $r = "0000000";
4743 }
4744
4745 $a1 = substr($addr,-7);
4746 $addr = substr($addr,0,-7);
4747 $sum = hex($a1) + 1;
4748 $r = sprintf("%07x", $sum) . $r;
4749 if ($sum <= 0xfffffff) {
4750 $r = $addr . $r;
4751 # if ($main::opt_debug) { print STDERR "$r\n"; }
4752 return HexExtend($r);
4753 } else {
4754 $r = "00000000000000";
4755 }
4756
4757 $sum = hex($addr) + 1;
4758 if ($sum > 0xff) { $sum -= 0x100; }
4759 $r = sprintf("%02x", $sum) . $r;
4760
4761 # if ($main::opt_debug) { print STDERR "$r\n"; }
4762 return $r;
4763 }
4764}
4765
4766# Extract symbols for all PC values found in profile
4767sub ExtractSymbols {
4768 my $libs = shift;
4769 my $pcset = shift;
4770
4771 my $symbols = {};
4772
4773 # Map each PC value to the containing library. To make this faster,
4774 # we sort libraries by their starting pc value (highest first), and
4775 # advance through the libraries as we advance the pc. Sometimes the
4776 # addresses of libraries may overlap with the addresses of the main
4777 # binary, so to make sure the libraries 'win', we iterate over the
4778 # libraries in reverse order (which assumes the binary doesn't start
4779 # in the middle of a library, which seems a fair assumption).
4780 my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # pcset is 0-extended strings
4781 foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {
4782 my $libname = $lib->[0];
4783 my $start = $lib->[1];
4784 my $finish = $lib->[2];
4785 my $offset = $lib->[3];
4786
4787 # Get list of pcs that belong in this library.
4788 my $contained = [];
4789 my ($start_pc_index, $finish_pc_index);
4790 # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].
4791 for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;
4792 $finish_pc_index--) {
4793 last if $pcs[$finish_pc_index - 1] le $finish;
4794 }
4795 # Find smallest start_pc_index such that $start <= $pc[$start_pc_index].
4796 for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;
4797 $start_pc_index--) {
4798 last if $pcs[$start_pc_index - 1] lt $start;
4799 }
4800 # This keeps PC values higher than $pc[$finish_pc_index] in @pcs,
4801 # in case there are overlaps in libraries and the main binary.
4802 @{$contained} = splice(@pcs, $start_pc_index,
4803 $finish_pc_index - $start_pc_index);
4804 # Map to symbols
4805 MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);
4806 }
4807
4808 return $symbols;
4809}
4810
4811# Map list of PC values to symbols for a given image
4812sub MapToSymbols {
4813 my $image = shift;
4814 my $offset = shift;
4815 my $pclist = shift;
4816 my $symbols = shift;
4817
4818 my $debug = 0;
4819
4820 # For libc (and other) libraries, the copy in /usr/lib/debug contains debugging symbols
4821 my $debugging = DebuggingLibrary($image);
4822 if ($debugging) {
4823 $image = $debugging;
4824 }
4825
4826 # Ignore empty binaries
4827 if ($#{$pclist} < 0) { return; }
4828
4829 # Figure out the addr2line command to use
4830 my $addr2line = $obj_tool_map{"addr2line"};
4831 my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image);
4832 if (exists $obj_tool_map{"addr2line_pdb"}) {
4833 $addr2line = $obj_tool_map{"addr2line_pdb"};
4834 $cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image);
4835 }
4836
4837 # If "addr2line" isn't installed on the system at all, just use
4838 # nm to get what info we can (function names, but not line numbers).
4839 if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) {
4840 MapSymbolsWithNM($image, $offset, $pclist, $symbols);
4841 return;
4842 }
4843
4844 # "addr2line -i" can produce a variable number of lines per input
4845 # address, with no separator that allows us to tell when data for
4846 # the next address starts. So we find the address for a special
4847 # symbol (_fini) and interleave this address between all real
4848 # addresses passed to addr2line. The name of this special symbol
4849 # can then be used as a separator.
4850 $sep_address = undef; # May be filled in by MapSymbolsWithNM()
4851 my $nm_symbols = {};
4852 MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);
4853 if (defined($sep_address)) {
4854 # Only add " -i" to addr2line if the binary supports it.
4855 # addr2line --help returns 0, but not if it sees an unknown flag first.
4856 if (system("$cmd -i --help >$dev_null 2>&1") == 0) {
4857 $cmd .= " -i";
4858 } else {
4859 $sep_address = undef; # no need for sep_address if we don't support -i
4860 }
4861 }
4862
4863 # Make file with all PC values with intervening 'sep_address' so
4864 # that we can reliably detect the end of inlined function list
4865 open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n");
4866 if ($debug) { print("---- $image ---\n"); }
4867 for (my $i = 0; $i <= $#{$pclist}; $i++) {
4868 # addr2line always reads hex addresses, and does not need '0x' prefix.
4869 if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); }
4870 printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset));
4871 if (defined($sep_address)) {
4872 printf ADDRESSES ("%s\n", $sep_address);
4873 }
4874 }
4875 close(ADDRESSES);
4876 if ($debug) {
4877 print("----\n");
4878 system("cat", $main::tmpfile_sym);
4879 print("---- $cmd ---\n");
4880 system("$cmd < " . ShellEscape($main::tmpfile_sym));
4881 print("----\n");
4882 }
4883
4884 open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |")
4885 || error("$cmd: $!\n");
4886 my $count = 0; # Index in pclist
4887 while (<SYMBOLS>) {
4888 # Read fullfunction and filelineinfo from next pair of lines
4889 s/\r?\n$//g;
4890 my $fullfunction = $_;
4891 $_ = <SYMBOLS>;
4892 s/\r?\n$//g;
4893 my $filelinenum = $_;
4894
4895 if (defined($sep_address) && $fullfunction eq $sep_symbol) {
4896 # Terminating marker for data for this address
4897 $count++;
4898 next;
4899 }
4900
4901 $filelinenum =~ s|\\|/|g; # turn windows-style paths into unix-style paths
4902
4903 # Remove discriminator markers as this comes after the line number and
4904 # confuses the rest of this script.
4905 $filelinenum =~ s/ \(discriminator \d+\)$//;
4906 # Convert unknown line numbers into line 0.
4907 $filelinenum =~ s/:\?$/:0/;
4908
4909 my $pcstr = $pclist->[$count];
4910 my $function = ShortFunctionName($fullfunction);
4911 my $nms = $nm_symbols->{$pcstr};
4912 if (defined($nms)) {
4913 if ($fullfunction eq '??') {
4914 # nm found a symbol for us.
4915 $function = $nms->[0];
4916 $fullfunction = $nms->[2];
4917 } else {
4918 # MapSymbolsWithNM tags each routine with its starting address,
4919 # useful in case the image has multiple occurrences of this
4920 # routine. (It uses a syntax that resembles template paramters,
4921 # that are automatically stripped out by ShortFunctionName().)
4922 # addr2line does not provide the same information. So we check
4923 # if nm disambiguated our symbol, and if so take the annotated
4924 # (nm) version of the routine-name. TODO(csilvers): this won't
4925 # catch overloaded, inlined symbols, which nm doesn't see.
4926 # Better would be to do a check similar to nm's, in this fn.
4927 if ($nms->[2] =~ m/^\Q$function\E/) { # sanity check it's the right fn
4928 $function = $nms->[0];
4929 $fullfunction = $nms->[2];
4930 }
4931 }
4932 }
4933
4934 # Prepend to accumulated symbols for pcstr
4935 # (so that caller comes before callee)
4936 my $sym = $symbols->{$pcstr};
4937 if (!defined($sym)) {
4938 $sym = [];
4939 $symbols->{$pcstr} = $sym;
4940 }
4941 unshift(@{$sym}, $function, $filelinenum, $fullfunction);
4942 if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); }
4943 if (!defined($sep_address)) {
4944 # Inlining is off, so this entry ends immediately
4945 $count++;
4946 }
4947 }
4948 close(SYMBOLS);
4949}
4950
4951# Use nm to map the list of referenced PCs to symbols. Return true iff we
4952# are able to read procedure information via nm.
4953sub MapSymbolsWithNM {
4954 my $image = shift;
4955 my $offset = shift;
4956 my $pclist = shift;
4957 my $symbols = shift;
4958
4959 # Get nm output sorted by increasing address
4960 my $symbol_table = GetProcedureBoundaries($image, ".");
4961 if (!%{$symbol_table}) {
4962 return 0;
4963 }
4964 # Start addresses are already the right length (8 or 16 hex digits).
4965 my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }
4966 keys(%{$symbol_table});
4967
4968 if ($#names < 0) {
4969 # No symbols: just use addresses
4970 foreach my $pc (@{$pclist}) {
4971 my $pcstr = "0x" . $pc;
4972 $symbols->{$pc} = [$pcstr, "?", $pcstr];
4973 }
4974 return 0;
4975 }
4976
4977 # Sort addresses so we can do a join against nm output
4978 my $index = 0;
4979 my $fullname = $names[0];
4980 my $name = ShortFunctionName($fullname);
4981 foreach my $pc (sort { $a cmp $b } @{$pclist}) {
4982 # Adjust for mapped offset
4983 my $mpc = AddressSub($pc, $offset);
4984 while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){
4985 $index++;
4986 $fullname = $names[$index];
4987 $name = ShortFunctionName($fullname);
4988 }
4989 if ($mpc lt $symbol_table->{$fullname}->[1]) {
4990 $symbols->{$pc} = [$name, "?", $fullname];
4991 } else {
4992 my $pcstr = "0x" . $pc;
4993 $symbols->{$pc} = [$pcstr, "?", $pcstr];
4994 }
4995 }
4996 return 1;
4997}
4998
4999sub ShortFunctionName {
5000 my $function = shift;
5001 while ($function =~ s/\([^()]*\)(\s*const)?//g) { } # Argument types
5002 $function =~ s/<[0-9a-f]*>$//g; # Remove Address
5003 if (!$main::opt_no_strip_temp) {
5004 while ($function =~ s/<[^<>]*>//g) { } # Remove template arguments
5005 }
5006 $function =~ s/^.*\s+(\w+::)/$1/; # Remove leading type
5007 return $function;
5008}
5009
5010# Trim overly long symbols found in disassembler output
5011sub CleanDisassembly {
5012 my $d = shift;
5013 while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax)
5014 while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } # Remove template arguments
5015 return $d;
5016}
5017
5018# Clean file name for display
5019sub CleanFileName {
5020 my ($f) = @_;
5021 $f =~ s|^/proc/self/cwd/||;
5022 $f =~ s|^\./||;
5023 return $f;
5024}
5025
5026# Make address relative to section and clean up for display
5027sub UnparseAddress {
5028 my ($offset, $address) = @_;
5029 $address = AddressSub($address, $offset);
5030 $address =~ s/^0x//;
5031 $address =~ s/^0*//;
5032 return $address;
5033}
5034
5035##### Miscellaneous #####
5036
5037# Find the right versions of the above object tools to use. The
5038# argument is the program file being analyzed, and should be an ELF
5039# 32-bit or ELF 64-bit executable file. The location of the tools
5040# is determined by considering the following options in this order:
5041# 1) --tools option, if set
5042# 2) PPROF_TOOLS environment variable, if set
5043# 3) the environment
5044sub ConfigureObjTools {
5045 my $prog_file = shift;
5046
5047 # Check for the existence of $prog_file because /usr/bin/file does not
5048 # predictably return error status in prod.
5049 (-e $prog_file) || error("$prog_file does not exist.\n");
5050
5051 my $file_type = undef;
5052 if (-e "/usr/bin/file") {
5053 # Follow symlinks (at least for systems where "file" supports that).
5054 my $escaped_prog_file = ShellEscape($prog_file);
5055 $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||
5056 /usr/bin/file $escaped_prog_file`;
5057 } elsif ($^O == "MSWin32") {
5058 $file_type = "MS Windows";
5059 } else {
5060 print STDERR "WARNING: Can't determine the file type of $prog_file";
5061 }
5062
5063 if ($file_type =~ /64-bit/) {
5064 # Change $address_length to 16 if the program file is ELF 64-bit.
5065 # We can't detect this from many (most?) heap or lock contention
5066 # profiles, since the actual addresses referenced are generally in low
5067 # memory even for 64-bit programs.
5068 $address_length = 16;
5069 }
5070
5071 if ($file_type =~ /MS Windows/) {
5072 # For windows, we provide a version of nm and addr2line as part of
5073 # the opensource release, which is capable of parsing
5074 # Windows-style PDB executables. It should live in the path, or
5075 # in the same directory as pprof.
5076 $obj_tool_map{"nm_pdb"} = "nm-pdb";
5077 $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
5078 }
5079
5080 if ($file_type =~ /Mach-O/) {
5081 # OS X uses otool to examine Mach-O files, rather than objdump.
5082 $obj_tool_map{"otool"} = "otool";
5083 $obj_tool_map{"addr2line"} = "false"; # no addr2line
5084 $obj_tool_map{"objdump"} = "false"; # no objdump
5085 }
5086
5087 # Go fill in %obj_tool_map with the pathnames to use:
5088 foreach my $tool (keys %obj_tool_map) {
5089 $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});
5090 }
5091}
5092
5093# Returns the path of a caller-specified object tool. If --tools or
5094# PPROF_TOOLS are specified, then returns the full path to the tool
5095# with that prefix. Otherwise, returns the path unmodified (which
5096# means we will look for it on PATH).
5097sub ConfigureTool {
5098 my $tool = shift;
5099 my $path;
5100
5101 # --tools (or $PPROF_TOOLS) is a comma separated list, where each
5102 # item is either a) a pathname prefix, or b) a map of the form
5103 # <tool>:<path>. First we look for an entry of type (b) for our
5104 # tool. If one is found, we use it. Otherwise, we consider all the
5105 # pathname prefixes in turn, until one yields an existing file. If
5106 # none does, we use a default path.
5107 my $tools = $main::opt_tools || $ENV{"PPROF_TOOLS"} || "";
5108 if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) {
5109 $path = $2;
5110 # TODO(csilvers): sanity-check that $path exists? Hard if it's relative.
5111 } elsif ($tools ne '') {
5112 foreach my $prefix (split(',', $tools)) {
5113 next if ($prefix =~ /:/); # ignore "tool:fullpath" entries in the list
5114 if (-x $prefix . $tool) {
5115 $path = $prefix . $tool;
5116 last;
5117 }
5118 }
5119 if (!$path) {
5120 error("No '$tool' found with prefix specified by " .
5121 "--tools (or \$PPROF_TOOLS) '$tools'\n");
5122 }
5123 } else {
5124 # ... otherwise use the version that exists in the same directory as
5125 # pprof. If there's nothing there, use $PATH.
5126 $0 =~ m,[^/]*$,; # this is everything after the last slash
5127 my $dirname = $`; # this is everything up to and including the last slash
5128 if (-x "$dirname$tool") {
5129 $path = "$dirname$tool";
5130 } else {
5131 $path = $tool;
5132 }
5133 }
5134 if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; }
5135 return $path;
5136}
5137
5138sub ShellEscape {
5139 my @escaped_words = ();
5140 foreach my $word (@_) {
5141 my $escaped_word = $word;
5142 if ($word =~ m![^a-zA-Z0-9/.,_=-]!) { # check for anything not in whitelist
5143 $escaped_word =~ s/'/'\\''/;
5144 $escaped_word = "'$escaped_word'";
5145 }
5146 push(@escaped_words, $escaped_word);
5147 }
5148 return join(" ", @escaped_words);
5149}
5150
5151sub cleanup {
5152 unlink($main::tmpfile_sym);
5153 unlink(keys %main::tempnames);
5154
5155 # We leave any collected profiles in $HOME/pprof in case the user wants
5156 # to look at them later. We print a message informing them of this.
5157 if ((scalar(@main::profile_files) > 0) &&
5158 defined($main::collected_profile)) {
5159 if (scalar(@main::profile_files) == 1) {
5160 print STDERR "Dynamically gathered profile is in $main::collected_profile\n";
5161 }
5162 print STDERR "If you want to investigate this profile further, you can do:\n";
5163 print STDERR "\n";
5164 print STDERR " pprof \\\n";
5165 print STDERR " $main::prog \\\n";
5166 print STDERR " $main::collected_profile\n";
5167 print STDERR "\n";
5168 }
5169}
5170
5171sub sighandler {
5172 cleanup();
5173 exit(1);
5174}
5175
5176sub error {
5177 my $msg = shift;
5178 print STDERR $msg;
5179 cleanup();
5180 exit(1);
5181}
5182
5183
5184# Run $nm_command and get all the resulting procedure boundaries whose
5185# names match "$regexp" and returns them in a hashtable mapping from
5186# procedure name to a two-element vector of [start address, end address]
5187sub GetProcedureBoundariesViaNm {
5188 my $escaped_nm_command = shift; # shell-escaped
5189 my $regexp = shift;
5190 my $image = shift;
5191
5192 my $symbol_table = {};
5193 open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n");
5194 my $last_start = "0";
5195 my $routine = "";
5196 while (<NM>) {
5197 s/\r//g; # turn windows-looking lines into unix-looking lines
5198 if (m/^\s*([0-9a-f]+) (.) (..*)/) {
5199 my $start_val = $1;
5200 my $type = $2;
5201 my $this_routine = $3;
5202
5203 # It's possible for two symbols to share the same address, if
5204 # one is a zero-length variable (like __start_google_malloc) or
5205 # one symbol is a weak alias to another (like __libc_malloc).
5206 # In such cases, we want to ignore all values except for the
5207 # actual symbol, which in nm-speak has type "T". The logic
5208 # below does this, though it's a bit tricky: what happens when
5209 # we have a series of lines with the same address, is the first
5210 # one gets queued up to be processed. However, it won't
5211 # *actually* be processed until later, when we read a line with
5212 # a different address. That means that as long as we're reading
5213 # lines with the same address, we have a chance to replace that
5214 # item in the queue, which we do whenever we see a 'T' entry --
5215 # that is, a line with type 'T'. If we never see a 'T' entry,
5216 # we'll just go ahead and process the first entry (which never
5217 # got touched in the queue), and ignore the others.
5218 if ($start_val eq $last_start && $type =~ /t/i) {
5219 # We are the 'T' symbol at this address, replace previous symbol.
5220 $routine = $this_routine;
5221 next;
5222 } elsif ($start_val eq $last_start) {
5223 # We're not the 'T' symbol at this address, so ignore us.
5224 next;
5225 }
5226
5227 if ($this_routine eq $sep_symbol) {
5228 $sep_address = HexExtend($start_val);
5229 }
5230
5231 # Tag this routine with the starting address in case the image
5232 # has multiple occurrences of this routine. We use a syntax
5233 # that resembles template paramters that are automatically
5234 # stripped out by ShortFunctionName()
5235 $this_routine .= "<$start_val>";
5236
5237 if (defined($routine) && $routine =~ m/$regexp/) {
5238 $symbol_table->{$routine} = [HexExtend($last_start),
5239 HexExtend($start_val)];
5240 }
5241 $last_start = $start_val;
5242 $routine = $this_routine;
5243 } elsif (m/^Loaded image name: (.+)/) {
5244 # The win32 nm workalike emits information about the binary it is using.
5245 if ($main::opt_debug) { print STDERR "Using Image $1\n"; }
5246 } elsif (m/^PDB file name: (.+)/) {
5247 # The win32 nm workalike emits information about the pdb it is using.
5248 if ($main::opt_debug) { print STDERR "Using PDB $1\n"; }
5249 }
5250 }
5251 close(NM);
5252 # Handle the last line in the nm output. Unfortunately, we don't know
5253 # how big this last symbol is, because we don't know how big the file
5254 # is. For now, we just give it a size of 0.
5255 # TODO(csilvers): do better here.
5256 if (defined($routine) && $routine =~ m/$regexp/) {
5257 $symbol_table->{$routine} = [HexExtend($last_start),
5258 HexExtend($last_start)];
5259 }
5260
5261 # Verify if addr2line can find the $sep_symbol. If not, we use objdump
5262 # to find the address for the $sep_symbol on code section which addr2line
5263 # can find.
5264 if (defined($sep_address)){
5265 my $start_val = $sep_address;
5266 my $addr2line = $obj_tool_map{"addr2line"};
5267 my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image, "-i");
5268 open(FINI, "echo $start_val | $cmd |")
5269 || error("echo $start_val | $cmd: $!\n");
5270 $_ = <FINI>;
5271 s/\r?\n$//g;
5272 my $fini = $_;
5273 close(FINI);
5274 if ($fini ne $sep_symbol){
5275 my $objdump = $obj_tool_map{"objdump"};
5276 $cmd = ShellEscape($objdump, "-d", $image);
5277 my $grep = ShellEscape("grep", $sep_symbol);
5278 my $tail = ShellEscape("tail", "-n", "1");
5279 open(FINI, "$cmd | $grep | $tail |")
5280 || error("$cmd | $grep | $tail: $!\n");
5281 s/\r//g; # turn windows-looking lines into unix-looking lines
5282 my $data = <FINI>;
5283 if (defined($data)){
5284 ($start_val, $fini) = split(/ </,$data);
5285 }
5286 close(FINI);
5287 }
5288 $sep_address = HexExtend($start_val);
5289 }
5290
5291 return $symbol_table;
5292}
5293
5294# Gets the procedure boundaries for all routines in "$image" whose names
5295# match "$regexp" and returns them in a hashtable mapping from procedure
5296# name to a two-element vector of [start address, end address].
5297# Will return an empty map if nm is not installed or not working properly.
5298sub GetProcedureBoundaries {
5299 my $image = shift;
5300 my $regexp = shift;
5301
5302 # If $image doesn't start with /, then put ./ in front of it. This works
5303 # around an obnoxious bug in our probing of nm -f behavior.
5304 # "nm -f $image" is supposed to fail on GNU nm, but if:
5305 #
5306 # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND
5307 # b. you have a.out in your current directory (a not uncommon occurrence)
5308 #
5309 # then "nm -f $image" succeeds because -f only looks at the first letter of
5310 # the argument, which looks valid because it's [BbSsPp], and then since
5311 # there's no image provided, it looks for a.out and finds it.
5312 #
5313 # This regex makes sure that $image starts with . or /, forcing the -f
5314 # parsing to fail since . and / are not valid formats.
5315 $image =~ s#^[^/]#./$&#;
5316
5317 # For libc libraries, the copy in /usr/lib/debug contains debugging symbols
5318 my $debugging = DebuggingLibrary($image);
5319 if ($debugging) {
5320 $image = $debugging;
5321 }
5322
5323 my $nm = $obj_tool_map{"nm"};
5324 my $cppfilt = $obj_tool_map{"c++filt"};
5325
5326 # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm
5327 # binary doesn't support --demangle. In addition, for OS X we need
5328 # to use the -f flag to get 'flat' nm output (otherwise we don't sort
5329 # properly and get incorrect results). Unfortunately, GNU nm uses -f
5330 # in an incompatible way. So first we test whether our nm supports
5331 # --demangle and -f.
5332 my $demangle_flag = "";
5333 my $cppfilt_flag = "";
5334 my $to_devnull = ">$dev_null 2>&1";
5335 if (system(ShellEscape($nm, "--demangle", "image") . $to_devnull) == 0) {
5336 # In this mode, we do "nm --demangle <foo>"
5337 $demangle_flag = "--demangle";
5338 $cppfilt_flag = "";
5339 } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {
5340 # In this mode, we do "nm <foo> | c++filt"
5341 $cppfilt_flag = " | " . ShellEscape($cppfilt);
5342 };
5343 my $flatten_flag = "";
5344 if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) {
5345 $flatten_flag = "-f";
5346 }
5347
5348 # Finally, in the case $imagie isn't a debug library, we try again with
5349 # -D to at least get *exported* symbols. If we can't use --demangle,
5350 # we use c++filt instead, if it exists on this system.
5351 my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag,
5352 $image) . " 2>$dev_null $cppfilt_flag",
5353 ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag,
5354 $image) . " 2>$dev_null $cppfilt_flag",
5355 # 6nm is for Go binaries
5356 ShellEscape("6nm", "$image") . " 2>$dev_null | sort",
5357 );
5358
5359 # If the executable is an MS Windows PDB-format executable, we'll
5360 # have set up obj_tool_map("nm_pdb"). In this case, we actually
5361 # want to use both unix nm and windows-specific nm_pdb, since
5362 # PDB-format executables can apparently include dwarf .o files.
5363 if (exists $obj_tool_map{"nm_pdb"}) {
5364 push(@nm_commands,
5365 ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image)
5366 . " 2>$dev_null");
5367 }
5368
5369 foreach my $nm_command (@nm_commands) {
5370 my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp, $image);
5371 return $symbol_table if (%{$symbol_table});
5372 }
5373 my $symbol_table = {};
5374 return $symbol_table;
5375}
5376
5377
5378# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.
5379# To make them more readable, we add underscores at interesting places.
5380# This routine removes the underscores, producing the canonical representation
5381# used by pprof to represent addresses, particularly in the tested routines.
5382sub CanonicalHex {
5383 my $arg = shift;
5384 return join '', (split '_',$arg);
5385}
5386
5387
5388# Unit test for AddressAdd:
5389sub AddressAddUnitTest {
5390 my $test_data_8 = shift;
5391 my $test_data_16 = shift;
5392 my $error_count = 0;
5393 my $fail_count = 0;
5394 my $pass_count = 0;
5395 # print STDERR "AddressAddUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5396
5397 # First a few 8-nibble addresses. Note that this implementation uses
5398 # plain old arithmetic, so a quick sanity check along with verifying what
5399 # happens to overflow (we want it to wrap):
5400 $address_length = 8;
5401 foreach my $row (@{$test_data_8}) {
5402 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5403 my $sum = AddressAdd ($row->[0], $row->[1]);
5404 if ($sum ne $row->[2]) {
5405 printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
5406 $row->[0], $row->[1], $row->[2];
5407 ++$fail_count;
5408 } else {
5409 ++$pass_count;
5410 }
5411 }
5412 printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n",
5413 $pass_count, $fail_count;
5414 $error_count = $fail_count;
5415 $fail_count = 0;
5416 $pass_count = 0;
5417
5418 # Now 16-nibble addresses.
5419 $address_length = 16;
5420 foreach my $row (@{$test_data_16}) {
5421 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5422 my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
5423 my $expected = join '', (split '_',$row->[2]);
5424 if ($sum ne CanonicalHex($row->[2])) {
5425 printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
5426 $row->[0], $row->[1], $row->[2];
5427 ++$fail_count;
5428 } else {
5429 ++$pass_count;
5430 }
5431 }
5432 printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n",
5433 $pass_count, $fail_count;
5434 $error_count += $fail_count;
5435
5436 return $error_count;
5437}
5438
5439
5440# Unit test for AddressSub:
5441sub AddressSubUnitTest {
5442 my $test_data_8 = shift;
5443 my $test_data_16 = shift;
5444 my $error_count = 0;
5445 my $fail_count = 0;
5446 my $pass_count = 0;
5447 # print STDERR "AddressSubUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5448
5449 # First a few 8-nibble addresses. Note that this implementation uses
5450 # plain old arithmetic, so a quick sanity check along with verifying what
5451 # happens to overflow (we want it to wrap):
5452 $address_length = 8;
5453 foreach my $row (@{$test_data_8}) {
5454 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5455 my $sum = AddressSub ($row->[0], $row->[1]);
5456 if ($sum ne $row->[3]) {
5457 printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
5458 $row->[0], $row->[1], $row->[3];
5459 ++$fail_count;
5460 } else {
5461 ++$pass_count;
5462 }
5463 }
5464 printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n",
5465 $pass_count, $fail_count;
5466 $error_count = $fail_count;
5467 $fail_count = 0;
5468 $pass_count = 0;
5469
5470 # Now 16-nibble addresses.
5471 $address_length = 16;
5472 foreach my $row (@{$test_data_16}) {
5473 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5474 my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
5475 if ($sum ne CanonicalHex($row->[3])) {
5476 printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
5477 $row->[0], $row->[1], $row->[3];
5478 ++$fail_count;
5479 } else {
5480 ++$pass_count;
5481 }
5482 }
5483 printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n",
5484 $pass_count, $fail_count;
5485 $error_count += $fail_count;
5486
5487 return $error_count;
5488}
5489
5490
5491# Unit test for AddressInc:
5492sub AddressIncUnitTest {
5493 my $test_data_8 = shift;
5494 my $test_data_16 = shift;
5495 my $error_count = 0;
5496 my $fail_count = 0;
5497 my $pass_count = 0;
5498 # print STDERR "AddressIncUnitTest: ", 1+$#{$test_data_8}, " tests\n";
5499
5500 # First a few 8-nibble addresses. Note that this implementation uses
5501 # plain old arithmetic, so a quick sanity check along with verifying what
5502 # happens to overflow (we want it to wrap):
5503 $address_length = 8;
5504 foreach my $row (@{$test_data_8}) {
5505 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5506 my $sum = AddressInc ($row->[0]);
5507 if ($sum ne $row->[4]) {
5508 printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
5509 $row->[0], $row->[4];
5510 ++$fail_count;
5511 } else {
5512 ++$pass_count;
5513 }
5514 }
5515 printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n",
5516 $pass_count, $fail_count;
5517 $error_count = $fail_count;
5518 $fail_count = 0;
5519 $pass_count = 0;
5520
5521 # Now 16-nibble addresses.
5522 $address_length = 16;
5523 foreach my $row (@{$test_data_16}) {
5524 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
5525 my $sum = AddressInc (CanonicalHex($row->[0]));
5526 if ($sum ne CanonicalHex($row->[4])) {
5527 printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
5528 $row->[0], $row->[4];
5529 ++$fail_count;
5530 } else {
5531 ++$pass_count;
5532 }
5533 }
5534 printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n",
5535 $pass_count, $fail_count;
5536 $error_count += $fail_count;
5537
5538 return $error_count;
5539}
5540
5541
5542# Driver for unit tests.
5543# Currently just the address add/subtract/increment routines for 64-bit.
5544sub RunUnitTests {
5545 my $error_count = 0;
5546
5547 # This is a list of tuples [a, b, a+b, a-b, a+1]
5548 my $unit_test_data_8 = [
5549 [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],
5550 [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],
5551 [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],
5552 [qw(00000001 ffffffff 00000000 00000002 00000002)],
5553 [qw(00000001 fffffff0 fffffff1 00000011 00000002)],
5554 ];
5555 my $unit_test_data_16 = [
5556 # The implementation handles data in 7-nibble chunks, so those are the
5557 # interesting boundaries.
5558 [qw(aaaaaaaa 50505050
5559 00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],
5560 [qw(50505050 aaaaaaaa
5561 00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],
5562 [qw(ffffffff aaaaaaaa
5563 00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],
5564 [qw(00000001 ffffffff
5565 00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],
5566 [qw(00000001 fffffff0
5567 00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],
5568
5569 [qw(00_a00000a_aaaaaaa 50505050
5570 00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],
5571 [qw(0f_fff0005_0505050 aaaaaaaa
5572 0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],
5573 [qw(00_000000f_fffffff 01_800000a_aaaaaaa
5574 01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],
5575 [qw(00_0000000_0000001 ff_fffffff_fffffff
5576 00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],
5577 [qw(00_0000000_0000001 ff_fffffff_ffffff0
5578 ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],
5579 ];
5580
5581 $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);
5582 $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);
5583 $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);
5584 if ($error_count > 0) {
5585 print STDERR $error_count, " errors: FAILED\n";
5586 } else {
5587 print STDERR "PASS\n";
5588 }
5589 exit ($error_count);
5590}