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