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