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