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