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 = "0.0.0-0-g0000000000000000000000000000000000000000"; 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 ('je_calloc', 2896 'cfree', 2897 'je_malloc', 2898 'newImpl', 2899 'void* newImpl', 2900 'je_free', 2901 'je_memalign', 2902 'je_posix_memalign', 2903 'je_aligned_alloc', 2904 'pvalloc', 2905 'je_valloc', 2906 'je_realloc', 2907 'je_mallocx', 2908 'je_rallocx', 2909 'je_xallocx', 2910 'je_dallocx', 2911 'je_sdallocx', 2912 'tc_calloc', 2913 'tc_cfree', 2914 'tc_malloc', 2915 'tc_free', 2916 'tc_memalign', 2917 'tc_posix_memalign', 2918 'tc_pvalloc', 2919 'tc_valloc', 2920 'tc_realloc', 2921 'tc_new', 2922 'tc_delete', 2923 'tc_newarray', 2924 'tc_deletearray', 2925 'tc_new_nothrow', 2926 'tc_newarray_nothrow', 2927 'do_malloc', 2928 '::do_malloc', # new name -- got moved to an unnamed ns 2929 '::do_malloc_or_cpp_alloc', 2930 'DoSampledAllocation', 2931 'simple_alloc::allocate', 2932 '__malloc_alloc_template::allocate', 2933 '__builtin_delete', 2934 '__builtin_new', 2935 '__builtin_vec_delete', 2936 '__builtin_vec_new', 2937 'operator new', 2938 'operator new[]', 2939 # The entry to our memory-allocation routines on OS X 2940 'malloc_zone_malloc', 2941 'malloc_zone_calloc', 2942 'malloc_zone_valloc', 2943 'malloc_zone_realloc', 2944 'malloc_zone_memalign', 2945 'malloc_zone_free', 2946 # These mark the beginning/end of our custom sections 2947 '__start_google_malloc', 2948 '__stop_google_malloc', 2949 '__start_malloc_hook', 2950 '__stop_malloc_hook') { 2951 $skip{$name} = 1; 2952 $skip{"_" . $name} = 1; # Mach (OS X) adds a _ prefix to everything 2953 } 2954 # TODO: Remove TCMalloc once everything has been 2955 # moved into the tcmalloc:: namespace and we have flushed 2956 # old code out of the system. 2957 $skip_regexp = "TCMalloc|^tcmalloc::"; 2958 } elsif ($main::profile_type eq 'contention') { 2959 foreach my $vname ('base::RecordLockProfileData', 2960 'base::SubmitMutexProfileData', 2961 'base::SubmitSpinLockProfileData', 2962 'Mutex::Unlock', 2963 'Mutex::UnlockSlow', 2964 'Mutex::ReaderUnlock', 2965 'MutexLock::~MutexLock', 2966 'SpinLock::Unlock', 2967 'SpinLock::SlowUnlock', 2968 'SpinLockHolder::~SpinLockHolder') { 2969 $skip{$vname} = 1; 2970 } 2971 } elsif ($main::profile_type eq 'cpu') { 2972 # Drop signal handlers used for CPU profile collection 2973 # TODO(dpeng): this should not be necessary; it's taken 2974 # care of by the general 2nd-pc mechanism below. 2975 foreach my $name ('ProfileData::Add', # historical 2976 'ProfileData::prof_handler', # historical 2977 'CpuProfiler::prof_handler', 2978 '__FRAME_END__', 2979 '__pthread_sighandler', 2980 '__restore') { 2981 $skip{$name} = 1; 2982 } 2983 } else { 2984 # Nothing skipped for unknown types 2985 } 2986 2987 if ($main::profile_type eq 'cpu') { 2988 # If all the second-youngest program counters are the same, 2989 # this STRONGLY suggests that it is an artifact of measurement, 2990 # i.e., stack frames pushed by the CPU profiler signal handler. 2991 # Hence, we delete them. 2992 # (The topmost PC is read from the signal structure, not from 2993 # the stack, so it does not get involved.) 2994 while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) { 2995 my $result = {}; 2996 my $func = ''; 2997 if (exists($symbols->{$second_pc})) { 2998 $second_pc = $symbols->{$second_pc}->[0]; 2999 } 3000 print STDERR "Removing $second_pc from all stack traces.\n"; 3001 foreach my $k (keys(%{$profile})) { 3002 my $count = $profile->{$k}; 3003 my @addrs = split(/\n/, $k); 3004 splice @addrs, 1, 1; 3005 my $reduced_path = join("\n", @addrs); 3006 AddEntry($result, $reduced_path, $count); 3007 } 3008 $profile = $result; 3009 } 3010 } 3011 3012 my $result = {}; 3013 foreach my $k (keys(%{$profile})) { 3014 my $count = $profile->{$k}; 3015 my @addrs = split(/\n/, $k); 3016 my @path = (); 3017 foreach my $a (@addrs) { 3018 if (exists($symbols->{$a})) { 3019 my $func = $symbols->{$a}->[0]; 3020 if ($skip{$func} || ($func =~ m/$skip_regexp/)) { 3021 # Throw away the portion of the backtrace seen so far, under the 3022 # assumption that previous frames were for functions internal to the 3023 # allocator. 3024 @path = (); 3025 next; 3026 } 3027 } 3028 push(@path, $a); 3029 } 3030 my $reduced_path = join("\n", @path); 3031 AddEntry($result, $reduced_path, $count); 3032 } 3033 3034 $result = FilterFrames($symbols, $result); 3035 3036 return $result; 3037} 3038 3039# Reduce profile to granularity given by user 3040sub ReduceProfile { 3041 my $symbols = shift; 3042 my $profile = shift; 3043 my $result = {}; 3044 my $fullname_to_shortname_map = {}; 3045 FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map); 3046 foreach my $k (keys(%{$profile})) { 3047 my $count = $profile->{$k}; 3048 my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k); 3049 my @path = (); 3050 my %seen = (); 3051 $seen{''} = 1; # So that empty keys are skipped 3052 foreach my $e (@translated) { 3053 # To avoid double-counting due to recursion, skip a stack-trace 3054 # entry if it has already been seen 3055 if (!$seen{$e}) { 3056 $seen{$e} = 1; 3057 push(@path, $e); 3058 } 3059 } 3060 my $reduced_path = join("\n", @path); 3061 AddEntry($result, $reduced_path, $count); 3062 } 3063 return $result; 3064} 3065 3066# Does the specified symbol array match the regexp? 3067sub SymbolMatches { 3068 my $sym = shift; 3069 my $re = shift; 3070 if (defined($sym)) { 3071 for (my $i = 0; $i < $#{$sym}; $i += 3) { 3072 if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) { 3073 return 1; 3074 } 3075 } 3076 } 3077 return 0; 3078} 3079 3080# Focus only on paths involving specified regexps 3081sub FocusProfile { 3082 my $symbols = shift; 3083 my $profile = shift; 3084 my $focus = shift; 3085 my $result = {}; 3086 foreach my $k (keys(%{$profile})) { 3087 my $count = $profile->{$k}; 3088 my @addrs = split(/\n/, $k); 3089 foreach my $a (@addrs) { 3090 # Reply if it matches either the address/shortname/fileline 3091 if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) { 3092 AddEntry($result, $k, $count); 3093 last; 3094 } 3095 } 3096 } 3097 return $result; 3098} 3099 3100# Focus only on paths not involving specified regexps 3101sub IgnoreProfile { 3102 my $symbols = shift; 3103 my $profile = shift; 3104 my $ignore = shift; 3105 my $result = {}; 3106 foreach my $k (keys(%{$profile})) { 3107 my $count = $profile->{$k}; 3108 my @addrs = split(/\n/, $k); 3109 my $matched = 0; 3110 foreach my $a (@addrs) { 3111 # Reply if it matches either the address/shortname/fileline 3112 if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) { 3113 $matched = 1; 3114 last; 3115 } 3116 } 3117 if (!$matched) { 3118 AddEntry($result, $k, $count); 3119 } 3120 } 3121 return $result; 3122} 3123 3124# Get total count in profile 3125sub TotalProfile { 3126 my $profile = shift; 3127 my $result = 0; 3128 foreach my $k (keys(%{$profile})) { 3129 $result += $profile->{$k}; 3130 } 3131 return $result; 3132} 3133 3134# Add A to B 3135sub AddProfile { 3136 my $A = shift; 3137 my $B = shift; 3138 3139 my $R = {}; 3140 # add all keys in A 3141 foreach my $k (keys(%{$A})) { 3142 my $v = $A->{$k}; 3143 AddEntry($R, $k, $v); 3144 } 3145 # add all keys in B 3146 foreach my $k (keys(%{$B})) { 3147 my $v = $B->{$k}; 3148 AddEntry($R, $k, $v); 3149 } 3150 return $R; 3151} 3152 3153# Merges symbol maps 3154sub MergeSymbols { 3155 my $A = shift; 3156 my $B = shift; 3157 3158 my $R = {}; 3159 foreach my $k (keys(%{$A})) { 3160 $R->{$k} = $A->{$k}; 3161 } 3162 if (defined($B)) { 3163 foreach my $k (keys(%{$B})) { 3164 $R->{$k} = $B->{$k}; 3165 } 3166 } 3167 return $R; 3168} 3169 3170 3171# Add A to B 3172sub AddPcs { 3173 my $A = shift; 3174 my $B = shift; 3175 3176 my $R = {}; 3177 # add all keys in A 3178 foreach my $k (keys(%{$A})) { 3179 $R->{$k} = 1 3180 } 3181 # add all keys in B 3182 foreach my $k (keys(%{$B})) { 3183 $R->{$k} = 1 3184 } 3185 return $R; 3186} 3187 3188# Subtract B from A 3189sub SubtractProfile { 3190 my $A = shift; 3191 my $B = shift; 3192 3193 my $R = {}; 3194 foreach my $k (keys(%{$A})) { 3195 my $v = $A->{$k} - GetEntry($B, $k); 3196 if ($v < 0 && $main::opt_drop_negative) { 3197 $v = 0; 3198 } 3199 AddEntry($R, $k, $v); 3200 } 3201 if (!$main::opt_drop_negative) { 3202 # Take care of when subtracted profile has more entries 3203 foreach my $k (keys(%{$B})) { 3204 if (!exists($A->{$k})) { 3205 AddEntry($R, $k, 0 - $B->{$k}); 3206 } 3207 } 3208 } 3209 return $R; 3210} 3211 3212# Get entry from profile; zero if not present 3213sub GetEntry { 3214 my $profile = shift; 3215 my $k = shift; 3216 if (exists($profile->{$k})) { 3217 return $profile->{$k}; 3218 } else { 3219 return 0; 3220 } 3221} 3222 3223# Add entry to specified profile 3224sub AddEntry { 3225 my $profile = shift; 3226 my $k = shift; 3227 my $n = shift; 3228 if (!exists($profile->{$k})) { 3229 $profile->{$k} = 0; 3230 } 3231 $profile->{$k} += $n; 3232} 3233 3234# Add a stack of entries to specified profile, and add them to the $pcs 3235# list. 3236sub AddEntries { 3237 my $profile = shift; 3238 my $pcs = shift; 3239 my $stack = shift; 3240 my $count = shift; 3241 my @k = (); 3242 3243 foreach my $e (split(/\s+/, $stack)) { 3244 my $pc = HexExtend($e); 3245 $pcs->{$pc} = 1; 3246 push @k, $pc; 3247 } 3248 AddEntry($profile, (join "\n", @k), $count); 3249} 3250 3251##### Code to profile a server dynamically ##### 3252 3253sub CheckSymbolPage { 3254 my $url = SymbolPageURL(); 3255 my $command = ShellEscape(@URL_FETCHER, $url); 3256 open(SYMBOL, "$command |") or error($command); 3257 my $line = <SYMBOL>; 3258 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines 3259 close(SYMBOL); 3260 unless (defined($line)) { 3261 error("$url doesn't exist\n"); 3262 } 3263 3264 if ($line =~ /^num_symbols:\s+(\d+)$/) { 3265 if ($1 == 0) { 3266 error("Stripped binary. No symbols available.\n"); 3267 } 3268 } else { 3269 error("Failed to get the number of symbols from $url\n"); 3270 } 3271} 3272 3273sub IsProfileURL { 3274 my $profile_name = shift; 3275 if (-f $profile_name) { 3276 printf STDERR "Using local file $profile_name.\n"; 3277 return 0; 3278 } 3279 return 1; 3280} 3281 3282sub ParseProfileURL { 3283 my $profile_name = shift; 3284 3285 if (!defined($profile_name) || $profile_name eq "") { 3286 return (); 3287 } 3288 3289 # Split profile URL - matches all non-empty strings, so no test. 3290 $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,; 3291 3292 my $proto = $1 || "http://"; 3293 my $hostport = $2; 3294 my $prefix = $3; 3295 my $profile = $4 || "/"; 3296 3297 my $host = $hostport; 3298 $host =~ s/:.*//; 3299 3300 my $baseurl = "$proto$hostport$prefix"; 3301 return ($host, $baseurl, $profile); 3302} 3303 3304# We fetch symbols from the first profile argument. 3305sub SymbolPageURL { 3306 my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]); 3307 return "$baseURL$SYMBOL_PAGE"; 3308} 3309 3310sub FetchProgramName() { 3311 my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]); 3312 my $url = "$baseURL$PROGRAM_NAME_PAGE"; 3313 my $command_line = ShellEscape(@URL_FETCHER, $url); 3314 open(CMDLINE, "$command_line |") or error($command_line); 3315 my $cmdline = <CMDLINE>; 3316 $cmdline =~ s/\r//g; # turn windows-looking lines into unix-looking lines 3317 close(CMDLINE); 3318 error("Failed to get program name from $url\n") unless defined($cmdline); 3319 $cmdline =~ s/\x00.+//; # Remove argv[1] and latters. 3320 $cmdline =~ s!\n!!g; # Remove LFs. 3321 return $cmdline; 3322} 3323 3324# Gee, curl's -L (--location) option isn't reliable at least 3325# with its 7.12.3 version. Curl will forget to post data if 3326# there is a redirection. This function is a workaround for 3327# curl. Redirection happens on borg hosts. 3328sub ResolveRedirectionForCurl { 3329 my $url = shift; 3330 my $command_line = ShellEscape(@URL_FETCHER, "--head", $url); 3331 open(CMDLINE, "$command_line |") or error($command_line); 3332 while (<CMDLINE>) { 3333 s/\r//g; # turn windows-looking lines into unix-looking lines 3334 if (/^Location: (.*)/) { 3335 $url = $1; 3336 } 3337 } 3338 close(CMDLINE); 3339 return $url; 3340} 3341 3342# Add a timeout flat to URL_FETCHER. Returns a new list. 3343sub AddFetchTimeout { 3344 my $timeout = shift; 3345 my @fetcher = @_; 3346 if (defined($timeout)) { 3347 if (join(" ", @fetcher) =~ m/\bcurl -s/) { 3348 push(@fetcher, "--max-time", sprintf("%d", $timeout)); 3349 } elsif (join(" ", @fetcher) =~ m/\brpcget\b/) { 3350 push(@fetcher, sprintf("--deadline=%d", $timeout)); 3351 } 3352 } 3353 return @fetcher; 3354} 3355 3356# Reads a symbol map from the file handle name given as $1, returning 3357# the resulting symbol map. Also processes variables relating to symbols. 3358# Currently, the only variable processed is 'binary=<value>' which updates 3359# $main::prog to have the correct program name. 3360sub ReadSymbols { 3361 my $in = shift; 3362 my $map = {}; 3363 while (<$in>) { 3364 s/\r//g; # turn windows-looking lines into unix-looking lines 3365 # Removes all the leading zeroes from the symbols, see comment below. 3366 if (m/^0x0*([0-9a-f]+)\s+(.+)/) { 3367 $map->{$1} = $2; 3368 } elsif (m/^---/) { 3369 last; 3370 } elsif (m/^([a-z][^=]*)=(.*)$/ ) { 3371 my ($variable, $value) = ($1, $2); 3372 for ($variable, $value) { 3373 s/^\s+//; 3374 s/\s+$//; 3375 } 3376 if ($variable eq "binary") { 3377 if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) { 3378 printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n", 3379 $main::prog, $value); 3380 } 3381 $main::prog = $value; 3382 } else { 3383 printf STDERR ("Ignoring unknown variable in symbols list: " . 3384 "'%s' = '%s'\n", $variable, $value); 3385 } 3386 } 3387 } 3388 return $map; 3389} 3390 3391sub URLEncode { 3392 my $str = shift; 3393 $str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg; 3394 return $str; 3395} 3396 3397sub AppendSymbolFilterParams { 3398 my $url = shift; 3399 my @params = (); 3400 if ($main::opt_retain ne '') { 3401 push(@params, sprintf("retain=%s", URLEncode($main::opt_retain))); 3402 } 3403 if ($main::opt_exclude ne '') { 3404 push(@params, sprintf("exclude=%s", URLEncode($main::opt_exclude))); 3405 } 3406 if (scalar @params > 0) { 3407 $url = sprintf("%s?%s", $url, join("&", @params)); 3408 } 3409 return $url; 3410} 3411 3412# Fetches and processes symbols to prepare them for use in the profile output 3413# code. If the optional 'symbol_map' arg is not given, fetches symbols from 3414# $SYMBOL_PAGE for all PC values found in profile. Otherwise, the raw symbols 3415# are assumed to have already been fetched into 'symbol_map' and are simply 3416# extracted and processed. 3417sub FetchSymbols { 3418 my $pcset = shift; 3419 my $symbol_map = shift; 3420 3421 my %seen = (); 3422 my @pcs = grep { !$seen{$_}++ } keys(%$pcset); # uniq 3423 3424 if (!defined($symbol_map)) { 3425 my $post_data = join("+", sort((map {"0x" . "$_"} @pcs))); 3426 3427 open(POSTFILE, ">$main::tmpfile_sym"); 3428 print POSTFILE $post_data; 3429 close(POSTFILE); 3430 3431 my $url = SymbolPageURL(); 3432 3433 my $command_line; 3434 if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) { 3435 $url = ResolveRedirectionForCurl($url); 3436 $url = AppendSymbolFilterParams($url); 3437 $command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym", 3438 $url); 3439 } else { 3440 $url = AppendSymbolFilterParams($url); 3441 $command_line = (ShellEscape(@URL_FETCHER, "--post", $url) 3442 . " < " . ShellEscape($main::tmpfile_sym)); 3443 } 3444 # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols. 3445 my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"}); 3446 open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line); 3447 $symbol_map = ReadSymbols(*SYMBOL{IO}); 3448 close(SYMBOL); 3449 } 3450 3451 my $symbols = {}; 3452 foreach my $pc (@pcs) { 3453 my $fullname; 3454 # For 64 bits binaries, symbols are extracted with 8 leading zeroes. 3455 # Then /symbol reads the long symbols in as uint64, and outputs 3456 # the result with a "0x%08llx" format which get rid of the zeroes. 3457 # By removing all the leading zeroes in both $pc and the symbols from 3458 # /symbol, the symbols match and are retrievable from the map. 3459 my $shortpc = $pc; 3460 $shortpc =~ s/^0*//; 3461 # Each line may have a list of names, which includes the function 3462 # and also other functions it has inlined. They are separated (in 3463 # PrintSymbolizedProfile), by --, which is illegal in function names. 3464 my $fullnames; 3465 if (defined($symbol_map->{$shortpc})) { 3466 $fullnames = $symbol_map->{$shortpc}; 3467 } else { 3468 $fullnames = "0x" . $pc; # Just use addresses 3469 } 3470 my $sym = []; 3471 $symbols->{$pc} = $sym; 3472 foreach my $fullname (split("--", $fullnames)) { 3473 my $name = ShortFunctionName($fullname); 3474 push(@{$sym}, $name, "?", $fullname); 3475 } 3476 } 3477 return $symbols; 3478} 3479 3480sub BaseName { 3481 my $file_name = shift; 3482 $file_name =~ s!^.*/!!; # Remove directory name 3483 return $file_name; 3484} 3485 3486sub MakeProfileBaseName { 3487 my ($binary_name, $profile_name) = @_; 3488 my ($host, $baseURL, $path) = ParseProfileURL($profile_name); 3489 my $binary_shortname = BaseName($binary_name); 3490 return sprintf("%s.%s.%s", 3491 $binary_shortname, $main::op_time, $host); 3492} 3493 3494sub FetchDynamicProfile { 3495 my $binary_name = shift; 3496 my $profile_name = shift; 3497 my $fetch_name_only = shift; 3498 my $encourage_patience = shift; 3499 3500 if (!IsProfileURL($profile_name)) { 3501 return $profile_name; 3502 } else { 3503 my ($host, $baseURL, $path) = ParseProfileURL($profile_name); 3504 if ($path eq "" || $path eq "/") { 3505 # Missing type specifier defaults to cpu-profile 3506 $path = $PROFILE_PAGE; 3507 } 3508 3509 my $profile_file = MakeProfileBaseName($binary_name, $profile_name); 3510 3511 my $url = "$baseURL$path"; 3512 my $fetch_timeout = undef; 3513 if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) { 3514 if ($path =~ m/[?]/) { 3515 $url .= "&"; 3516 } else { 3517 $url .= "?"; 3518 } 3519 $url .= sprintf("seconds=%d", $main::opt_seconds); 3520 $fetch_timeout = $main::opt_seconds * 1.01 + 60; 3521 # Set $profile_type for consumption by PrintSymbolizedProfile. 3522 $main::profile_type = 'cpu'; 3523 } else { 3524 # For non-CPU profiles, we add a type-extension to 3525 # the target profile file name. 3526 my $suffix = $path; 3527 $suffix =~ s,/,.,g; 3528 $profile_file .= $suffix; 3529 # Set $profile_type for consumption by PrintSymbolizedProfile. 3530 if ($path =~ m/$HEAP_PAGE/) { 3531 $main::profile_type = 'heap'; 3532 } elsif ($path =~ m/$GROWTH_PAGE/) { 3533 $main::profile_type = 'growth'; 3534 } elsif ($path =~ m/$CONTENTION_PAGE/) { 3535 $main::profile_type = 'contention'; 3536 } 3537 } 3538 3539 my $profile_dir = $ENV{"JEPROF_TMPDIR"} || ($ENV{HOME} . "/jeprof"); 3540 if (! -d $profile_dir) { 3541 mkdir($profile_dir) 3542 || die("Unable to create profile directory $profile_dir: $!\n"); 3543 } 3544 my $tmp_profile = "$profile_dir/.tmp.$profile_file"; 3545 my $real_profile = "$profile_dir/$profile_file"; 3546 3547 if ($fetch_name_only > 0) { 3548 return $real_profile; 3549 } 3550 3551 my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER); 3552 my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile); 3553 if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){ 3554 print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n"; 3555 if ($encourage_patience) { 3556 print STDERR "Be patient...\n"; 3557 } 3558 } else { 3559 print STDERR "Fetching $path profile from $url to\n ${real_profile}\n"; 3560 } 3561 3562 (system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n"); 3563 (system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n"); 3564 print STDERR "Wrote profile to $real_profile\n"; 3565 $main::collected_profile = $real_profile; 3566 return $main::collected_profile; 3567 } 3568} 3569 3570# Collect profiles in parallel 3571sub FetchDynamicProfiles { 3572 my $items = scalar(@main::pfile_args); 3573 my $levels = log($items) / log(2); 3574 3575 if ($items == 1) { 3576 $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1); 3577 } else { 3578 # math rounding issues 3579 if ((2 ** $levels) < $items) { 3580 $levels++; 3581 } 3582 my $count = scalar(@main::pfile_args); 3583 for (my $i = 0; $i < $count; $i++) { 3584 $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0); 3585 } 3586 print STDERR "Fetching $count profiles, Be patient...\n"; 3587 FetchDynamicProfilesRecurse($levels, 0, 0); 3588 $main::collected_profile = join(" \\\n ", @main::profile_files); 3589 } 3590} 3591 3592# Recursively fork a process to get enough processes 3593# collecting profiles 3594sub FetchDynamicProfilesRecurse { 3595 my $maxlevel = shift; 3596 my $level = shift; 3597 my $position = shift; 3598 3599 if (my $pid = fork()) { 3600 $position = 0 | ($position << 1); 3601 TryCollectProfile($maxlevel, $level, $position); 3602 wait; 3603 } else { 3604 $position = 1 | ($position << 1); 3605 TryCollectProfile($maxlevel, $level, $position); 3606 cleanup(); 3607 exit(0); 3608 } 3609} 3610 3611# Collect a single profile 3612sub TryCollectProfile { 3613 my $maxlevel = shift; 3614 my $level = shift; 3615 my $position = shift; 3616 3617 if ($level >= ($maxlevel - 1)) { 3618 if ($position < scalar(@main::pfile_args)) { 3619 FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0); 3620 } 3621 } else { 3622 FetchDynamicProfilesRecurse($maxlevel, $level+1, $position); 3623 } 3624} 3625 3626##### Parsing code ##### 3627 3628# Provide a small streaming-read module to handle very large 3629# cpu-profile files. Stream in chunks along a sliding window. 3630# Provides an interface to get one 'slot', correctly handling 3631# endian-ness differences. A slot is one 32-bit or 64-bit word 3632# (depending on the input profile). We tell endianness and bit-size 3633# for the profile by looking at the first 8 bytes: in cpu profiles, 3634# the second slot is always 3 (we'll accept anything that's not 0). 3635BEGIN { 3636 package CpuProfileStream; 3637 3638 sub new { 3639 my ($class, $file, $fname) = @_; 3640 my $self = { file => $file, 3641 base => 0, 3642 stride => 512 * 1024, # must be a multiple of bitsize/8 3643 slots => [], 3644 unpack_code => "", # N for big-endian, V for little 3645 perl_is_64bit => 1, # matters if profile is 64-bit 3646 }; 3647 bless $self, $class; 3648 # Let unittests adjust the stride 3649 if ($main::opt_test_stride > 0) { 3650 $self->{stride} = $main::opt_test_stride; 3651 } 3652 # Read the first two slots to figure out bitsize and endianness. 3653 my $slots = $self->{slots}; 3654 my $str; 3655 read($self->{file}, $str, 8); 3656 # Set the global $address_length based on what we see here. 3657 # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars). 3658 $address_length = ($str eq (chr(0)x8)) ? 16 : 8; 3659 if ($address_length == 8) { 3660 if (substr($str, 6, 2) eq chr(0)x2) { 3661 $self->{unpack_code} = 'V'; # Little-endian. 3662 } elsif (substr($str, 4, 2) eq chr(0)x2) { 3663 $self->{unpack_code} = 'N'; # Big-endian 3664 } else { 3665 ::error("$fname: header size >= 2**16\n"); 3666 } 3667 @$slots = unpack($self->{unpack_code} . "*", $str); 3668 } else { 3669 # If we're a 64-bit profile, check if we're a 64-bit-capable 3670 # perl. Otherwise, each slot will be represented as a float 3671 # instead of an int64, losing precision and making all the 3672 # 64-bit addresses wrong. We won't complain yet, but will 3673 # later if we ever see a value that doesn't fit in 32 bits. 3674 my $has_q = 0; 3675 eval { $has_q = pack("Q", "1") ? 1 : 1; }; 3676 if (!$has_q) { 3677 $self->{perl_is_64bit} = 0; 3678 } 3679 read($self->{file}, $str, 8); 3680 if (substr($str, 4, 4) eq chr(0)x4) { 3681 # We'd love to use 'Q', but it's a) not universal, b) not endian-proof. 3682 $self->{unpack_code} = 'V'; # Little-endian. 3683 } elsif (substr($str, 0, 4) eq chr(0)x4) { 3684 $self->{unpack_code} = 'N'; # Big-endian 3685 } else { 3686 ::error("$fname: header size >= 2**32\n"); 3687 } 3688 my @pair = unpack($self->{unpack_code} . "*", $str); 3689 # Since we know one of the pair is 0, it's fine to just add them. 3690 @$slots = (0, $pair[0] + $pair[1]); 3691 } 3692 return $self; 3693 } 3694 3695 # Load more data when we access slots->get(X) which is not yet in memory. 3696 sub overflow { 3697 my ($self) = @_; 3698 my $slots = $self->{slots}; 3699 $self->{base} += $#$slots + 1; # skip over data we're replacing 3700 my $str; 3701 read($self->{file}, $str, $self->{stride}); 3702 if ($address_length == 8) { # the 32-bit case 3703 # This is the easy case: unpack provides 32-bit unpacking primitives. 3704 @$slots = unpack($self->{unpack_code} . "*", $str); 3705 } else { 3706 # We need to unpack 32 bits at a time and combine. 3707 my @b32_values = unpack($self->{unpack_code} . "*", $str); 3708 my @b64_values = (); 3709 for (my $i = 0; $i < $#b32_values; $i += 2) { 3710 # TODO(csilvers): if this is a 32-bit perl, the math below 3711 # could end up in a too-large int, which perl will promote 3712 # to a double, losing necessary precision. Deal with that. 3713 # Right now, we just die. 3714 my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]); 3715 if ($self->{unpack_code} eq 'N') { # big-endian 3716 ($lo, $hi) = ($hi, $lo); 3717 } 3718 my $value = $lo + $hi * (2**32); 3719 if (!$self->{perl_is_64bit} && # check value is exactly represented 3720 (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) { 3721 ::error("Need a 64-bit perl to process this 64-bit profile.\n"); 3722 } 3723 push(@b64_values, $value); 3724 } 3725 @$slots = @b64_values; 3726 } 3727 } 3728 3729 # Access the i-th long in the file (logically), or -1 at EOF. 3730 sub get { 3731 my ($self, $idx) = @_; 3732 my $slots = $self->{slots}; 3733 while ($#$slots >= 0) { 3734 if ($idx < $self->{base}) { 3735 # The only time we expect a reference to $slots[$i - something] 3736 # after referencing $slots[$i] is reading the very first header. 3737 # Since $stride > |header|, that shouldn't cause any lookback 3738 # errors. And everything after the header is sequential. 3739 print STDERR "Unexpected look-back reading CPU profile"; 3740 return -1; # shrug, don't know what better to return 3741 } elsif ($idx > $self->{base} + $#$slots) { 3742 $self->overflow(); 3743 } else { 3744 return $slots->[$idx - $self->{base}]; 3745 } 3746 } 3747 # If we get here, $slots is [], which means we've reached EOF 3748 return -1; # unique since slots is supposed to hold unsigned numbers 3749 } 3750} 3751 3752# Reads the top, 'header' section of a profile, and returns the last 3753# line of the header, commonly called a 'header line'. The header 3754# section of a profile consists of zero or more 'command' lines that 3755# are instructions to jeprof, which jeprof executes when reading the 3756# header. All 'command' lines start with a %. After the command 3757# lines is the 'header line', which is a profile-specific line that 3758# indicates what type of profile it is, and perhaps other global 3759# information about the profile. For instance, here's a header line 3760# for a heap profile: 3761# heap profile: 53: 38236 [ 5525: 1284029] @ heapprofile 3762# For historical reasons, the CPU profile does not contain a text- 3763# readable header line. If the profile looks like a CPU profile, 3764# this function returns "". If no header line could be found, this 3765# function returns undef. 3766# 3767# The following commands are recognized: 3768# %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:' 3769# 3770# The input file should be in binmode. 3771sub ReadProfileHeader { 3772 local *PROFILE = shift; 3773 my $firstchar = ""; 3774 my $line = ""; 3775 read(PROFILE, $firstchar, 1); 3776 seek(PROFILE, -1, 1); # unread the firstchar 3777 if ($firstchar !~ /[[:print:]]/) { # is not a text character 3778 return ""; 3779 } 3780 while (defined($line = <PROFILE>)) { 3781 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines 3782 if ($line =~ /^%warn\s+(.*)/) { # 'warn' command 3783 # Note this matches both '%warn blah\n' and '%warn\n'. 3784 print STDERR "WARNING: $1\n"; # print the rest of the line 3785 } elsif ($line =~ /^%/) { 3786 print STDERR "Ignoring unknown command from profile header: $line"; 3787 } else { 3788 # End of commands, must be the header line. 3789 return $line; 3790 } 3791 } 3792 return undef; # got to EOF without seeing a header line 3793} 3794 3795sub IsSymbolizedProfileFile { 3796 my $file_name = shift; 3797 if (!(-e $file_name) || !(-r $file_name)) { 3798 return 0; 3799 } 3800 # Check if the file contains a symbol-section marker. 3801 open(TFILE, "<$file_name"); 3802 binmode TFILE; 3803 my $firstline = ReadProfileHeader(*TFILE); 3804 close(TFILE); 3805 if (!$firstline) { 3806 return 0; 3807 } 3808 $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3809 my $symbol_marker = $&; 3810 return $firstline =~ /^--- *$symbol_marker/; 3811} 3812 3813# Parse profile generated by common/profiler.cc and return a reference 3814# to a map: 3815# $result->{version} Version number of profile file 3816# $result->{period} Sampling period (in microseconds) 3817# $result->{profile} Profile object 3818# $result->{threads} Map of thread IDs to profile objects 3819# $result->{map} Memory map info from profile 3820# $result->{pcs} Hash of all PC values seen, key is hex address 3821sub ReadProfile { 3822 my $prog = shift; 3823 my $fname = shift; 3824 my $result; # return value 3825 3826 $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3827 my $contention_marker = $&; 3828 $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3829 my $growth_marker = $&; 3830 $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3831 my $symbol_marker = $&; 3832 $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3833 my $profile_marker = $&; 3834 $HEAP_PAGE =~ m,[^/]+$,; # matches everything after the last slash 3835 my $heap_marker = $&; 3836 3837 # Look at first line to see if it is a heap or a CPU profile. 3838 # CPU profile may start with no header at all, and just binary data 3839 # (starting with \0\0\0\0) -- in that case, don't try to read the 3840 # whole firstline, since it may be gigabytes(!) of data. 3841 open(PROFILE, "<$fname") || error("$fname: $!\n"); 3842 binmode PROFILE; # New perls do UTF-8 processing 3843 my $header = ReadProfileHeader(*PROFILE); 3844 if (!defined($header)) { # means "at EOF" 3845 error("Profile is empty.\n"); 3846 } 3847 3848 my $symbols; 3849 if ($header =~ m/^--- *$symbol_marker/o) { 3850 # Verify that the user asked for a symbolized profile 3851 if (!$main::use_symbolized_profile) { 3852 # we have both a binary and symbolized profiles, abort 3853 error("FATAL ERROR: Symbolized profile\n $fname\ncannot be used with " . 3854 "a binary arg. Try again without passing\n $prog\n"); 3855 } 3856 # Read the symbol section of the symbolized profile file. 3857 $symbols = ReadSymbols(*PROFILE{IO}); 3858 # Read the next line to get the header for the remaining profile. 3859 $header = ReadProfileHeader(*PROFILE) || ""; 3860 } 3861 3862 if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) { 3863 # Skip "--- ..." line for profile types that have their own headers. 3864 $header = ReadProfileHeader(*PROFILE) || ""; 3865 } 3866 3867 $main::profile_type = ''; 3868 3869 if ($header =~ m/^heap profile:.*$growth_marker/o) { 3870 $main::profile_type = 'growth'; 3871 $result = ReadHeapProfile($prog, *PROFILE, $header); 3872 } elsif ($header =~ m/^heap profile:/) { 3873 $main::profile_type = 'heap'; 3874 $result = ReadHeapProfile($prog, *PROFILE, $header); 3875 } elsif ($header =~ m/^heap/) { 3876 $main::profile_type = 'heap'; 3877 $result = ReadThreadedHeapProfile($prog, $fname, $header); 3878 } elsif ($header =~ m/^--- *$contention_marker/o) { 3879 $main::profile_type = 'contention'; 3880 $result = ReadSynchProfile($prog, *PROFILE); 3881 } elsif ($header =~ m/^--- *Stacks:/) { 3882 print STDERR 3883 "Old format contention profile: mistakenly reports " . 3884 "condition variable signals as lock contentions.\n"; 3885 $main::profile_type = 'contention'; 3886 $result = ReadSynchProfile($prog, *PROFILE); 3887 } elsif ($header =~ m/^--- *$profile_marker/) { 3888 # the binary cpu profile data starts immediately after this line 3889 $main::profile_type = 'cpu'; 3890 $result = ReadCPUProfile($prog, $fname, *PROFILE); 3891 } else { 3892 if (defined($symbols)) { 3893 # a symbolized profile contains a format we don't recognize, bail out 3894 error("$fname: Cannot recognize profile section after symbols.\n"); 3895 } 3896 # no ascii header present -- must be a CPU profile 3897 $main::profile_type = 'cpu'; 3898 $result = ReadCPUProfile($prog, $fname, *PROFILE); 3899 } 3900 3901 close(PROFILE); 3902 3903 # if we got symbols along with the profile, return those as well 3904 if (defined($symbols)) { 3905 $result->{symbols} = $symbols; 3906 } 3907 3908 return $result; 3909} 3910 3911# Subtract one from caller pc so we map back to call instr. 3912# However, don't do this if we're reading a symbolized profile 3913# file, in which case the subtract-one was done when the file 3914# was written. 3915# 3916# We apply the same logic to all readers, though ReadCPUProfile uses an 3917# independent implementation. 3918sub FixCallerAddresses { 3919 my $stack = shift; 3920 # --raw/http: Always subtract one from pc's, because PrintSymbolizedProfile() 3921 # dumps unadjusted profiles. 3922 { 3923 $stack =~ /(\s)/; 3924 my $delimiter = $1; 3925 my @addrs = split(' ', $stack); 3926 my @fixedaddrs; 3927 $#fixedaddrs = $#addrs; 3928 if ($#addrs >= 0) { 3929 $fixedaddrs[0] = $addrs[0]; 3930 } 3931 for (my $i = 1; $i <= $#addrs; $i++) { 3932 $fixedaddrs[$i] = AddressSub($addrs[$i], "0x1"); 3933 } 3934 return join $delimiter, @fixedaddrs; 3935 } 3936} 3937 3938# CPU profile reader 3939sub ReadCPUProfile { 3940 my $prog = shift; 3941 my $fname = shift; # just used for logging 3942 local *PROFILE = shift; 3943 my $version; 3944 my $period; 3945 my $i; 3946 my $profile = {}; 3947 my $pcs = {}; 3948 3949 # Parse string into array of slots. 3950 my $slots = CpuProfileStream->new(*PROFILE, $fname); 3951 3952 # Read header. The current header version is a 5-element structure 3953 # containing: 3954 # 0: header count (always 0) 3955 # 1: header "words" (after this one: 3) 3956 # 2: format version (0) 3957 # 3: sampling period (usec) 3958 # 4: unused padding (always 0) 3959 if ($slots->get(0) != 0 ) { 3960 error("$fname: not a profile file, or old format profile file\n"); 3961 } 3962 $i = 2 + $slots->get(1); 3963 $version = $slots->get(2); 3964 $period = $slots->get(3); 3965 # Do some sanity checking on these header values. 3966 if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) { 3967 error("$fname: not a profile file, or corrupted profile file\n"); 3968 } 3969 3970 # Parse profile 3971 while ($slots->get($i) != -1) { 3972 my $n = $slots->get($i++); 3973 my $d = $slots->get($i++); 3974 if ($d > (2**16)) { # TODO(csilvers): what's a reasonable max-stack-depth? 3975 my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8)); 3976 print STDERR "At index $i (address $addr):\n"; 3977 error("$fname: stack trace depth >= 2**32\n"); 3978 } 3979 if ($slots->get($i) == 0) { 3980 # End of profile data marker 3981 $i += $d; 3982 last; 3983 } 3984 3985 # Make key out of the stack entries 3986 my @k = (); 3987 for (my $j = 0; $j < $d; $j++) { 3988 my $pc = $slots->get($i+$j); 3989 # Subtract one from caller pc so we map back to call instr. 3990 $pc--; 3991 $pc = sprintf("%0*x", $address_length, $pc); 3992 $pcs->{$pc} = 1; 3993 push @k, $pc; 3994 } 3995 3996 AddEntry($profile, (join "\n", @k), $n); 3997 $i += $d; 3998 } 3999 4000 # Parse map 4001 my $map = ''; 4002 seek(PROFILE, $i * 4, 0); 4003 read(PROFILE, $map, (stat PROFILE)[7]); 4004 4005 my $r = {}; 4006 $r->{version} = $version; 4007 $r->{period} = $period; 4008 $r->{profile} = $profile; 4009 $r->{libs} = ParseLibraries($prog, $map, $pcs); 4010 $r->{pcs} = $pcs; 4011 4012 return $r; 4013} 4014 4015sub HeapProfileIndex { 4016 my $index = 1; 4017 if ($main::opt_inuse_space) { 4018 $index = 1; 4019 } elsif ($main::opt_inuse_objects) { 4020 $index = 0; 4021 } elsif ($main::opt_alloc_space) { 4022 $index = 3; 4023 } elsif ($main::opt_alloc_objects) { 4024 $index = 2; 4025 } 4026 return $index; 4027} 4028 4029sub ReadMappedLibraries { 4030 my $fh = shift; 4031 my $map = ""; 4032 # Read the /proc/self/maps data 4033 while (<$fh>) { 4034 s/\r//g; # turn windows-looking lines into unix-looking lines 4035 $map .= $_; 4036 } 4037 return $map; 4038} 4039 4040sub ReadMemoryMap { 4041 my $fh = shift; 4042 my $map = ""; 4043 # Read /proc/self/maps data as formatted by DumpAddressMap() 4044 my $buildvar = ""; 4045 while (<PROFILE>) { 4046 s/\r//g; # turn windows-looking lines into unix-looking lines 4047 # Parse "build=<dir>" specification if supplied 4048 if (m/^\s*build=(.*)\n/) { 4049 $buildvar = $1; 4050 } 4051 4052 # Expand "$build" variable if available 4053 $_ =~ s/\$build\b/$buildvar/g; 4054 4055 $map .= $_; 4056 } 4057 return $map; 4058} 4059 4060sub AdjustSamples { 4061 my ($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2) = @_; 4062 if ($sample_adjustment) { 4063 if ($sampling_algorithm == 2) { 4064 # Remote-heap version 2 4065 # The sampling frequency is the rate of a Poisson process. 4066 # This means that the probability of sampling an allocation of 4067 # size X with sampling rate Y is 1 - exp(-X/Y) 4068 if ($n1 != 0) { 4069 my $ratio = (($s1*1.0)/$n1)/($sample_adjustment); 4070 my $scale_factor = 1/(1 - exp(-$ratio)); 4071 $n1 *= $scale_factor; 4072 $s1 *= $scale_factor; 4073 } 4074 if ($n2 != 0) { 4075 my $ratio = (($s2*1.0)/$n2)/($sample_adjustment); 4076 my $scale_factor = 1/(1 - exp(-$ratio)); 4077 $n2 *= $scale_factor; 4078 $s2 *= $scale_factor; 4079 } 4080 } else { 4081 # Remote-heap version 1 4082 my $ratio; 4083 $ratio = (($s1*1.0)/$n1)/($sample_adjustment); 4084 if ($ratio < 1) { 4085 $n1 /= $ratio; 4086 $s1 /= $ratio; 4087 } 4088 $ratio = (($s2*1.0)/$n2)/($sample_adjustment); 4089 if ($ratio < 1) { 4090 $n2 /= $ratio; 4091 $s2 /= $ratio; 4092 } 4093 } 4094 } 4095 return ($n1, $s1, $n2, $s2); 4096} 4097 4098sub ReadHeapProfile { 4099 my $prog = shift; 4100 local *PROFILE = shift; 4101 my $header = shift; 4102 4103 my $index = HeapProfileIndex(); 4104 4105 # Find the type of this profile. The header line looks like: 4106 # heap profile: 1246: 8800744 [ 1246: 8800744] @ <heap-url>/266053 4107 # There are two pairs <count: size>, the first inuse objects/space, and the 4108 # second allocated objects/space. This is followed optionally by a profile 4109 # type, and if that is present, optionally by a sampling frequency. 4110 # For remote heap profiles (v1): 4111 # The interpretation of the sampling frequency is that the profiler, for 4112 # each sample, calculates a uniformly distributed random integer less than 4113 # the given value, and records the next sample after that many bytes have 4114 # been allocated. Therefore, the expected sample interval is half of the 4115 # given frequency. By default, if not specified, the expected sample 4116 # interval is 128KB. Only remote-heap-page profiles are adjusted for 4117 # sample size. 4118 # For remote heap profiles (v2): 4119 # The sampling frequency is the rate of a Poisson process. This means that 4120 # the probability of sampling an allocation of size X with sampling rate Y 4121 # is 1 - exp(-X/Y) 4122 # For version 2, a typical header line might look like this: 4123 # heap profile: 1922: 127792360 [ 1922: 127792360] @ <heap-url>_v2/524288 4124 # the trailing number (524288) is the sampling rate. (Version 1 showed 4125 # double the 'rate' here) 4126 my $sampling_algorithm = 0; 4127 my $sample_adjustment = 0; 4128 chomp($header); 4129 my $type = "unknown"; 4130 if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") { 4131 if (defined($6) && ($6 ne '')) { 4132 $type = $6; 4133 my $sample_period = $8; 4134 # $type is "heapprofile" for profiles generated by the 4135 # heap-profiler, and either "heap" or "heap_v2" for profiles 4136 # generated by sampling directly within tcmalloc. It can also 4137 # be "growth" for heap-growth profiles. The first is typically 4138 # found for profiles generated locally, and the others for 4139 # remote profiles. 4140 if (($type eq "heapprofile") || ($type !~ /heap/) ) { 4141 # No need to adjust for the sampling rate with heap-profiler-derived data 4142 $sampling_algorithm = 0; 4143 } elsif ($type =~ /_v2/) { 4144 $sampling_algorithm = 2; # version 2 sampling 4145 if (defined($sample_period) && ($sample_period ne '')) { 4146 $sample_adjustment = int($sample_period); 4147 } 4148 } else { 4149 $sampling_algorithm = 1; # version 1 sampling 4150 if (defined($sample_period) && ($sample_period ne '')) { 4151 $sample_adjustment = int($sample_period)/2; 4152 } 4153 } 4154 } else { 4155 # We detect whether or not this is a remote-heap profile by checking 4156 # that the total-allocated stats ($n2,$s2) are exactly the 4157 # same as the in-use stats ($n1,$s1). It is remotely conceivable 4158 # that a non-remote-heap profile may pass this check, but it is hard 4159 # to imagine how that could happen. 4160 # In this case it's so old it's guaranteed to be remote-heap version 1. 4161 my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4); 4162 if (($n1 == $n2) && ($s1 == $s2)) { 4163 # This is likely to be a remote-heap based sample profile 4164 $sampling_algorithm = 1; 4165 } 4166 } 4167 } 4168 4169 if ($sampling_algorithm > 0) { 4170 # For remote-heap generated profiles, adjust the counts and sizes to 4171 # account for the sample rate (we sample once every 128KB by default). 4172 if ($sample_adjustment == 0) { 4173 # Turn on profile adjustment. 4174 $sample_adjustment = 128*1024; 4175 print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n"; 4176 } else { 4177 printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n", 4178 $sample_adjustment); 4179 } 4180 if ($sampling_algorithm > 1) { 4181 # We don't bother printing anything for the original version (version 1) 4182 printf STDERR "Heap version $sampling_algorithm\n"; 4183 } 4184 } 4185 4186 my $profile = {}; 4187 my $pcs = {}; 4188 my $map = ""; 4189 4190 while (<PROFILE>) { 4191 s/\r//g; # turn windows-looking lines into unix-looking lines 4192 if (/^MAPPED_LIBRARIES:/) { 4193 $map .= ReadMappedLibraries(*PROFILE); 4194 last; 4195 } 4196 4197 if (/^--- Memory map:/) { 4198 $map .= ReadMemoryMap(*PROFILE); 4199 last; 4200 } 4201 4202 # Read entry of the form: 4203 # <count1>: <bytes1> [<count2>: <bytes2>] @ a1 a2 a3 ... an 4204 s/^\s*//; 4205 s/\s*$//; 4206 if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) { 4207 my $stack = $5; 4208 my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4); 4209 my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm, 4210 $n1, $s1, $n2, $s2); 4211 AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]); 4212 } 4213 } 4214 4215 my $r = {}; 4216 $r->{version} = "heap"; 4217 $r->{period} = 1; 4218 $r->{profile} = $profile; 4219 $r->{libs} = ParseLibraries($prog, $map, $pcs); 4220 $r->{pcs} = $pcs; 4221 return $r; 4222} 4223 4224sub ReadThreadedHeapProfile { 4225 my ($prog, $fname, $header) = @_; 4226 4227 my $index = HeapProfileIndex(); 4228 my $sampling_algorithm = 0; 4229 my $sample_adjustment = 0; 4230 chomp($header); 4231 my $type = "unknown"; 4232 # Assuming a very specific type of header for now. 4233 if ($header =~ m"^heap_v2/(\d+)") { 4234 $type = "_v2"; 4235 $sampling_algorithm = 2; 4236 $sample_adjustment = int($1); 4237 } 4238 if ($type ne "_v2" || !defined($sample_adjustment)) { 4239 die "Threaded heap profiles require v2 sampling with a sample rate\n"; 4240 } 4241 4242 my $profile = {}; 4243 my $thread_profiles = {}; 4244 my $pcs = {}; 4245 my $map = ""; 4246 my $stack = ""; 4247 4248 while (<PROFILE>) { 4249 s/\r//g; 4250 if (/^MAPPED_LIBRARIES:/) { 4251 $map .= ReadMappedLibraries(*PROFILE); 4252 last; 4253 } 4254 4255 if (/^--- Memory map:/) { 4256 $map .= ReadMemoryMap(*PROFILE); 4257 last; 4258 } 4259 4260 # Read entry of the form: 4261 # @ a1 a2 ... an 4262 # t*: <count1>: <bytes1> [<count2>: <bytes2>] 4263 # t1: <count1>: <bytes1> [<count2>: <bytes2>] 4264 # ... 4265 # tn: <count1>: <bytes1> [<count2>: <bytes2>] 4266 s/^\s*//; 4267 s/\s*$//; 4268 if (m/^@\s+(.*)$/) { 4269 $stack = $1; 4270 } elsif (m/^\s*(t(\*|\d+)):\s+(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]$/) { 4271 if ($stack eq "") { 4272 # Still in the header, so this is just a per-thread summary. 4273 next; 4274 } 4275 my $thread = $2; 4276 my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6); 4277 my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm, 4278 $n1, $s1, $n2, $s2); 4279 if ($thread eq "*") { 4280 AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]); 4281 } else { 4282 if (!exists($thread_profiles->{$thread})) { 4283 $thread_profiles->{$thread} = {}; 4284 } 4285 AddEntries($thread_profiles->{$thread}, $pcs, 4286 FixCallerAddresses($stack), $counts[$index]); 4287 } 4288 } 4289 } 4290 4291 my $r = {}; 4292 $r->{version} = "heap"; 4293 $r->{period} = 1; 4294 $r->{profile} = $profile; 4295 $r->{threads} = $thread_profiles; 4296 $r->{libs} = ParseLibraries($prog, $map, $pcs); 4297 $r->{pcs} = $pcs; 4298 return $r; 4299} 4300 4301sub ReadSynchProfile { 4302 my $prog = shift; 4303 local *PROFILE = shift; 4304 my $header = shift; 4305 4306 my $map = ''; 4307 my $profile = {}; 4308 my $pcs = {}; 4309 my $sampling_period = 1; 4310 my $cyclespernanosec = 2.8; # Default assumption for old binaries 4311 my $seen_clockrate = 0; 4312 my $line; 4313 4314 my $index = 0; 4315 if ($main::opt_total_delay) { 4316 $index = 0; 4317 } elsif ($main::opt_contentions) { 4318 $index = 1; 4319 } elsif ($main::opt_mean_delay) { 4320 $index = 2; 4321 } 4322 4323 while ( $line = <PROFILE> ) { 4324 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines 4325 if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) { 4326 my ($cycles, $count, $stack) = ($1, $2, $3); 4327 4328 # Convert cycles to nanoseconds 4329 $cycles /= $cyclespernanosec; 4330 4331 # Adjust for sampling done by application 4332 $cycles *= $sampling_period; 4333 $count *= $sampling_period; 4334 4335 my @values = ($cycles, $count, $cycles / $count); 4336 AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]); 4337 4338 } elsif ( $line =~ /^(slow release).*thread \d+ \@\s*(.*?)\s*$/ || 4339 $line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) { 4340 my ($cycles, $stack) = ($1, $2); 4341 if ($cycles !~ /^\d+$/) { 4342 next; 4343 } 4344 4345 # Convert cycles to nanoseconds 4346 $cycles /= $cyclespernanosec; 4347 4348 # Adjust for sampling done by application 4349 $cycles *= $sampling_period; 4350 4351 AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles); 4352 4353 } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) { 4354 my ($variable, $value) = ($1,$2); 4355 for ($variable, $value) { 4356 s/^\s+//; 4357 s/\s+$//; 4358 } 4359 if ($variable eq "cycles/second") { 4360 $cyclespernanosec = $value / 1e9; 4361 $seen_clockrate = 1; 4362 } elsif ($variable eq "sampling period") { 4363 $sampling_period = $value; 4364 } elsif ($variable eq "ms since reset") { 4365 # Currently nothing is done with this value in jeprof 4366 # So we just silently ignore it for now 4367 } elsif ($variable eq "discarded samples") { 4368 # Currently nothing is done with this value in jeprof 4369 # So we just silently ignore it for now 4370 } else { 4371 printf STDERR ("Ignoring unnknown variable in /contention output: " . 4372 "'%s' = '%s'\n",$variable,$value); 4373 } 4374 } else { 4375 # Memory map entry 4376 $map .= $line; 4377 } 4378 } 4379 4380 if (!$seen_clockrate) { 4381 printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n", 4382 $cyclespernanosec); 4383 } 4384 4385 my $r = {}; 4386 $r->{version} = 0; 4387 $r->{period} = $sampling_period; 4388 $r->{profile} = $profile; 4389 $r->{libs} = ParseLibraries($prog, $map, $pcs); 4390 $r->{pcs} = $pcs; 4391 return $r; 4392} 4393 4394# Given a hex value in the form "0x1abcd" or "1abcd", return either 4395# "0001abcd" or "000000000001abcd", depending on the current (global) 4396# address length. 4397sub HexExtend { 4398 my $addr = shift; 4399 4400 $addr =~ s/^(0x)?0*//; 4401 my $zeros_needed = $address_length - length($addr); 4402 if ($zeros_needed < 0) { 4403 printf STDERR "Warning: address $addr is longer than address length $address_length\n"; 4404 return $addr; 4405 } 4406 return ("0" x $zeros_needed) . $addr; 4407} 4408 4409##### Symbol extraction ##### 4410 4411# Aggressively search the lib_prefix values for the given library 4412# If all else fails, just return the name of the library unmodified. 4413# If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so" 4414# it will search the following locations in this order, until it finds a file: 4415# /my/path/lib/dir/mylib.so 4416# /other/path/lib/dir/mylib.so 4417# /my/path/dir/mylib.so 4418# /other/path/dir/mylib.so 4419# /my/path/mylib.so 4420# /other/path/mylib.so 4421# /lib/dir/mylib.so (returned as last resort) 4422sub FindLibrary { 4423 my $file = shift; 4424 my $suffix = $file; 4425 4426 # Search for the library as described above 4427 do { 4428 foreach my $prefix (@prefix_list) { 4429 my $fullpath = $prefix . $suffix; 4430 if (-e $fullpath) { 4431 return $fullpath; 4432 } 4433 } 4434 } while ($suffix =~ s|^/[^/]+/|/|); 4435 return $file; 4436} 4437 4438# Return path to library with debugging symbols. 4439# For libc libraries, the copy in /usr/lib/debug contains debugging symbols 4440sub DebuggingLibrary { 4441 my $file = shift; 4442 if ($file =~ m|^/|) { 4443 if (-f "/usr/lib/debug$file") { 4444 return "/usr/lib/debug$file"; 4445 } elsif (-f "/usr/lib/debug$file.debug") { 4446 return "/usr/lib/debug$file.debug"; 4447 } 4448 } 4449 return undef; 4450} 4451 4452# Parse text section header of a library using objdump 4453sub ParseTextSectionHeaderFromObjdump { 4454 my $lib = shift; 4455 4456 my $size = undef; 4457 my $vma; 4458 my $file_offset; 4459 # Get objdump output from the library file to figure out how to 4460 # map between mapped addresses and addresses in the library. 4461 my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib); 4462 open(OBJDUMP, "$cmd |") || error("$cmd: $!\n"); 4463 while (<OBJDUMP>) { 4464 s/\r//g; # turn windows-looking lines into unix-looking lines 4465 # Idx Name Size VMA LMA File off Algn 4466 # 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4 4467 # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file 4468 # offset may still be 8. But AddressSub below will still handle that. 4469 my @x = split; 4470 if (($#x >= 6) && ($x[1] eq '.text')) { 4471 $size = $x[2]; 4472 $vma = $x[3]; 4473 $file_offset = $x[5]; 4474 last; 4475 } 4476 } 4477 close(OBJDUMP); 4478 4479 if (!defined($size)) { 4480 return undef; 4481 } 4482 4483 my $r = {}; 4484 $r->{size} = $size; 4485 $r->{vma} = $vma; 4486 $r->{file_offset} = $file_offset; 4487 4488 return $r; 4489} 4490 4491# Parse text section header of a library using otool (on OS X) 4492sub ParseTextSectionHeaderFromOtool { 4493 my $lib = shift; 4494 4495 my $size = undef; 4496 my $vma = undef; 4497 my $file_offset = undef; 4498 # Get otool output from the library file to figure out how to 4499 # map between mapped addresses and addresses in the library. 4500 my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib); 4501 open(OTOOL, "$command |") || error("$command: $!\n"); 4502 my $cmd = ""; 4503 my $sectname = ""; 4504 my $segname = ""; 4505 foreach my $line (<OTOOL>) { 4506 $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines 4507 # Load command <#> 4508 # cmd LC_SEGMENT 4509 # [...] 4510 # Section 4511 # sectname __text 4512 # segname __TEXT 4513 # addr 0x000009f8 4514 # size 0x00018b9e 4515 # offset 2552 4516 # align 2^2 (4) 4517 # We will need to strip off the leading 0x from the hex addresses, 4518 # and convert the offset into hex. 4519 if ($line =~ /Load command/) { 4520 $cmd = ""; 4521 $sectname = ""; 4522 $segname = ""; 4523 } elsif ($line =~ /Section/) { 4524 $sectname = ""; 4525 $segname = ""; 4526 } elsif ($line =~ /cmd (\w+)/) { 4527 $cmd = $1; 4528 } elsif ($line =~ /sectname (\w+)/) { 4529 $sectname = $1; 4530 } elsif ($line =~ /segname (\w+)/) { 4531 $segname = $1; 4532 } elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") && 4533 $sectname eq "__text" && 4534 $segname eq "__TEXT")) { 4535 next; 4536 } elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) { 4537 $vma = $1; 4538 } elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) { 4539 $size = $1; 4540 } elsif ($line =~ /\boffset ([0-9]+)/) { 4541 $file_offset = sprintf("%016x", $1); 4542 } 4543 if (defined($vma) && defined($size) && defined($file_offset)) { 4544 last; 4545 } 4546 } 4547 close(OTOOL); 4548 4549 if (!defined($vma) || !defined($size) || !defined($file_offset)) { 4550 return undef; 4551 } 4552 4553 my $r = {}; 4554 $r->{size} = $size; 4555 $r->{vma} = $vma; 4556 $r->{file_offset} = $file_offset; 4557 4558 return $r; 4559} 4560 4561sub ParseTextSectionHeader { 4562 # obj_tool_map("otool") is only defined if we're in a Mach-O environment 4563 if (defined($obj_tool_map{"otool"})) { 4564 my $r = ParseTextSectionHeaderFromOtool(@_); 4565 if (defined($r)){ 4566 return $r; 4567 } 4568 } 4569 # If otool doesn't work, or we don't have it, fall back to objdump 4570 return ParseTextSectionHeaderFromObjdump(@_); 4571} 4572 4573# Split /proc/pid/maps dump into a list of libraries 4574sub ParseLibraries { 4575 return if $main::use_symbol_page; # We don't need libraries info. 4576 my $prog = Cwd::abs_path(shift); 4577 my $map = shift; 4578 my $pcs = shift; 4579 4580 my $result = []; 4581 my $h = "[a-f0-9]+"; 4582 my $zero_offset = HexExtend("0"); 4583 4584 my $buildvar = ""; 4585 foreach my $l (split("\n", $map)) { 4586 if ($l =~ m/^\s*build=(.*)$/) { 4587 $buildvar = $1; 4588 } 4589 4590 my $start; 4591 my $finish; 4592 my $offset; 4593 my $lib; 4594 if ($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+\.(so|dll|dylib|bundle)((\.\d+)+\w*(\.\d+){0,3})?)$/i) { 4595 # Full line from /proc/self/maps. Example: 4596 # 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so 4597 $start = HexExtend($1); 4598 $finish = HexExtend($2); 4599 $offset = HexExtend($3); 4600 $lib = $4; 4601 $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths 4602 } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) { 4603 # Cooked line from DumpAddressMap. Example: 4604 # 40000000-40015000: /lib/ld-2.3.2.so 4605 $start = HexExtend($1); 4606 $finish = HexExtend($2); 4607 $offset = $zero_offset; 4608 $lib = $3; 4609 } elsif (($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+)$/i) && ($4 eq $prog)) { 4610 # PIEs and address space randomization do not play well with our 4611 # default assumption that main executable is at lowest 4612 # addresses. So we're detecting main executable in 4613 # /proc/self/maps as well. 4614 $start = HexExtend($1); 4615 $finish = HexExtend($2); 4616 $offset = HexExtend($3); 4617 $lib = $4; 4618 $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths 4619 } 4620 # FreeBSD 10.0 virtual memory map /proc/curproc/map as defined in 4621 # function procfs_doprocmap (sys/fs/procfs/procfs_map.c) 4622 # 4623 # Example: 4624 # 0x800600000 0x80061a000 26 0 0xfffff800035a0000 r-x 75 33 0x1004 COW NC vnode /libexec/ld-elf.s 4625 # o.1 NCH -1 4626 elsif ($l =~ /^(0x$h)\s(0x$h)\s\d+\s\d+\s0x$h\sr-x\s\d+\s\d+\s0x\d+\s(COW|NCO)\s(NC|NNC)\svnode\s(\S+\.so(\.\d+)*)/) { 4627 $start = HexExtend($1); 4628 $finish = HexExtend($2); 4629 $offset = $zero_offset; 4630 $lib = FindLibrary($5); 4631 4632 } else { 4633 next; 4634 } 4635 4636 # Expand "$build" variable if available 4637 $lib =~ s/\$build\b/$buildvar/g; 4638 4639 $lib = FindLibrary($lib); 4640 4641 # Check for pre-relocated libraries, which use pre-relocated symbol tables 4642 # and thus require adjusting the offset that we'll use to translate 4643 # VM addresses into symbol table addresses. 4644 # Only do this if we're not going to fetch the symbol table from a 4645 # debugging copy of the library. 4646 if (!DebuggingLibrary($lib)) { 4647 my $text = ParseTextSectionHeader($lib); 4648 if (defined($text)) { 4649 my $vma_offset = AddressSub($text->{vma}, $text->{file_offset}); 4650 $offset = AddressAdd($offset, $vma_offset); 4651 } 4652 } 4653 4654 if($main::opt_debug) { printf STDERR "$start:$finish ($offset) $lib\n"; } 4655 push(@{$result}, [$lib, $start, $finish, $offset]); 4656 } 4657 4658 # Append special entry for additional library (not relocated) 4659 if ($main::opt_lib ne "") { 4660 my $text = ParseTextSectionHeader($main::opt_lib); 4661 if (defined($text)) { 4662 my $start = $text->{vma}; 4663 my $finish = AddressAdd($start, $text->{size}); 4664 4665 push(@{$result}, [$main::opt_lib, $start, $finish, $start]); 4666 } 4667 } 4668 4669 # Append special entry for the main program. This covers 4670 # 0..max_pc_value_seen, so that we assume pc values not found in one 4671 # of the library ranges will be treated as coming from the main 4672 # program binary. 4673 my $min_pc = HexExtend("0"); 4674 my $max_pc = $min_pc; # find the maximal PC value in any sample 4675 foreach my $pc (keys(%{$pcs})) { 4676 if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); } 4677 } 4678 push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]); 4679 4680 return $result; 4681} 4682 4683# Add two hex addresses of length $address_length. 4684# Run jeprof --test for unit test if this is changed. 4685sub AddressAdd { 4686 my $addr1 = shift; 4687 my $addr2 = shift; 4688 my $sum; 4689 4690 if ($address_length == 8) { 4691 # Perl doesn't cope with wraparound arithmetic, so do it explicitly: 4692 $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16); 4693 return sprintf("%08x", $sum); 4694 4695 } else { 4696 # Do the addition in 7-nibble chunks to trivialize carry handling. 4697 4698 if ($main::opt_debug and $main::opt_test) { 4699 print STDERR "AddressAdd $addr1 + $addr2 = "; 4700 } 4701 4702 my $a1 = substr($addr1,-7); 4703 $addr1 = substr($addr1,0,-7); 4704 my $a2 = substr($addr2,-7); 4705 $addr2 = substr($addr2,0,-7); 4706 $sum = hex($a1) + hex($a2); 4707 my $c = 0; 4708 if ($sum > 0xfffffff) { 4709 $c = 1; 4710 $sum -= 0x10000000; 4711 } 4712 my $r = sprintf("%07x", $sum); 4713 4714 $a1 = substr($addr1,-7); 4715 $addr1 = substr($addr1,0,-7); 4716 $a2 = substr($addr2,-7); 4717 $addr2 = substr($addr2,0,-7); 4718 $sum = hex($a1) + hex($a2) + $c; 4719 $c = 0; 4720 if ($sum > 0xfffffff) { 4721 $c = 1; 4722 $sum -= 0x10000000; 4723 } 4724 $r = sprintf("%07x", $sum) . $r; 4725 4726 $sum = hex($addr1) + hex($addr2) + $c; 4727 if ($sum > 0xff) { $sum -= 0x100; } 4728 $r = sprintf("%02x", $sum) . $r; 4729 4730 if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; } 4731 4732 return $r; 4733 } 4734} 4735 4736 4737# Subtract two hex addresses of length $address_length. 4738# Run jeprof --test for unit test if this is changed. 4739sub AddressSub { 4740 my $addr1 = shift; 4741 my $addr2 = shift; 4742 my $diff; 4743 4744 if ($address_length == 8) { 4745 # Perl doesn't cope with wraparound arithmetic, so do it explicitly: 4746 $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16); 4747 return sprintf("%08x", $diff); 4748 4749 } else { 4750 # Do the addition in 7-nibble chunks to trivialize borrow handling. 4751 # if ($main::opt_debug) { print STDERR "AddressSub $addr1 - $addr2 = "; } 4752 4753 my $a1 = hex(substr($addr1,-7)); 4754 $addr1 = substr($addr1,0,-7); 4755 my $a2 = hex(substr($addr2,-7)); 4756 $addr2 = substr($addr2,0,-7); 4757 my $b = 0; 4758 if ($a2 > $a1) { 4759 $b = 1; 4760 $a1 += 0x10000000; 4761 } 4762 $diff = $a1 - $a2; 4763 my $r = sprintf("%07x", $diff); 4764 4765 $a1 = hex(substr($addr1,-7)); 4766 $addr1 = substr($addr1,0,-7); 4767 $a2 = hex(substr($addr2,-7)) + $b; 4768 $addr2 = substr($addr2,0,-7); 4769 $b = 0; 4770 if ($a2 > $a1) { 4771 $b = 1; 4772 $a1 += 0x10000000; 4773 } 4774 $diff = $a1 - $a2; 4775 $r = sprintf("%07x", $diff) . $r; 4776 4777 $a1 = hex($addr1); 4778 $a2 = hex($addr2) + $b; 4779 if ($a2 > $a1) { $a1 += 0x100; } 4780 $diff = $a1 - $a2; 4781 $r = sprintf("%02x", $diff) . $r; 4782 4783 # if ($main::opt_debug) { print STDERR "$r\n"; } 4784 4785 return $r; 4786 } 4787} 4788 4789# Increment a hex addresses of length $address_length. 4790# Run jeprof --test for unit test if this is changed. 4791sub AddressInc { 4792 my $addr = shift; 4793 my $sum; 4794 4795 if ($address_length == 8) { 4796 # Perl doesn't cope with wraparound arithmetic, so do it explicitly: 4797 $sum = (hex($addr)+1) % (0x10000000 * 16); 4798 return sprintf("%08x", $sum); 4799 4800 } else { 4801 # Do the addition in 7-nibble chunks to trivialize carry handling. 4802 # We are always doing this to step through the addresses in a function, 4803 # and will almost never overflow the first chunk, so we check for this 4804 # case and exit early. 4805 4806 # if ($main::opt_debug) { print STDERR "AddressInc $addr1 = "; } 4807 4808 my $a1 = substr($addr,-7); 4809 $addr = substr($addr,0,-7); 4810 $sum = hex($a1) + 1; 4811 my $r = sprintf("%07x", $sum); 4812 if ($sum <= 0xfffffff) { 4813 $r = $addr . $r; 4814 # if ($main::opt_debug) { print STDERR "$r\n"; } 4815 return HexExtend($r); 4816 } else { 4817 $r = "0000000"; 4818 } 4819 4820 $a1 = substr($addr,-7); 4821 $addr = substr($addr,0,-7); 4822 $sum = hex($a1) + 1; 4823 $r = sprintf("%07x", $sum) . $r; 4824 if ($sum <= 0xfffffff) { 4825 $r = $addr . $r; 4826 # if ($main::opt_debug) { print STDERR "$r\n"; } 4827 return HexExtend($r); 4828 } else { 4829 $r = "00000000000000"; 4830 } 4831 4832 $sum = hex($addr) + 1; 4833 if ($sum > 0xff) { $sum -= 0x100; } 4834 $r = sprintf("%02x", $sum) . $r; 4835 4836 # if ($main::opt_debug) { print STDERR "$r\n"; } 4837 return $r; 4838 } 4839} 4840 4841# Extract symbols for all PC values found in profile 4842sub ExtractSymbols { 4843 my $libs = shift; 4844 my $pcset = shift; 4845 4846 my $symbols = {}; 4847 4848 # Map each PC value to the containing library. To make this faster, 4849 # we sort libraries by their starting pc value (highest first), and 4850 # advance through the libraries as we advance the pc. Sometimes the 4851 # addresses of libraries may overlap with the addresses of the main 4852 # binary, so to make sure the libraries 'win', we iterate over the 4853 # libraries in reverse order (which assumes the binary doesn't start 4854 # in the middle of a library, which seems a fair assumption). 4855 my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # pcset is 0-extended strings 4856 foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) { 4857 my $libname = $lib->[0]; 4858 my $start = $lib->[1]; 4859 my $finish = $lib->[2]; 4860 my $offset = $lib->[3]; 4861 4862 # Use debug library if it exists 4863 my $debug_libname = DebuggingLibrary($libname); 4864 if ($debug_libname) { 4865 $libname = $debug_libname; 4866 } 4867 4868 # Get list of pcs that belong in this library. 4869 my $contained = []; 4870 my ($start_pc_index, $finish_pc_index); 4871 # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index]. 4872 for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0; 4873 $finish_pc_index--) { 4874 last if $pcs[$finish_pc_index - 1] le $finish; 4875 } 4876 # Find smallest start_pc_index such that $start <= $pc[$start_pc_index]. 4877 for ($start_pc_index = $finish_pc_index; $start_pc_index > 0; 4878 $start_pc_index--) { 4879 last if $pcs[$start_pc_index - 1] lt $start; 4880 } 4881 # This keeps PC values higher than $pc[$finish_pc_index] in @pcs, 4882 # in case there are overlaps in libraries and the main binary. 4883 @{$contained} = splice(@pcs, $start_pc_index, 4884 $finish_pc_index - $start_pc_index); 4885 # Map to symbols 4886 MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols); 4887 } 4888 4889 return $symbols; 4890} 4891 4892# Map list of PC values to symbols for a given image 4893sub MapToSymbols { 4894 my $image = shift; 4895 my $offset = shift; 4896 my $pclist = shift; 4897 my $symbols = shift; 4898 4899 my $debug = 0; 4900 4901 # Ignore empty binaries 4902 if ($#{$pclist} < 0) { return; } 4903 4904 # Figure out the addr2line command to use 4905 my $addr2line = $obj_tool_map{"addr2line"}; 4906 my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image); 4907 if (exists $obj_tool_map{"addr2line_pdb"}) { 4908 $addr2line = $obj_tool_map{"addr2line_pdb"}; 4909 $cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image); 4910 } 4911 4912 # If "addr2line" isn't installed on the system at all, just use 4913 # nm to get what info we can (function names, but not line numbers). 4914 if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) { 4915 MapSymbolsWithNM($image, $offset, $pclist, $symbols); 4916 return; 4917 } 4918 4919 # "addr2line -i" can produce a variable number of lines per input 4920 # address, with no separator that allows us to tell when data for 4921 # the next address starts. So we find the address for a special 4922 # symbol (_fini) and interleave this address between all real 4923 # addresses passed to addr2line. The name of this special symbol 4924 # can then be used as a separator. 4925 $sep_address = undef; # May be filled in by MapSymbolsWithNM() 4926 my $nm_symbols = {}; 4927 MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols); 4928 if (defined($sep_address)) { 4929 # Only add " -i" to addr2line if the binary supports it. 4930 # addr2line --help returns 0, but not if it sees an unknown flag first. 4931 if (system("$cmd -i --help >$dev_null 2>&1") == 0) { 4932 $cmd .= " -i"; 4933 } else { 4934 $sep_address = undef; # no need for sep_address if we don't support -i 4935 } 4936 } 4937 4938 # Make file with all PC values with intervening 'sep_address' so 4939 # that we can reliably detect the end of inlined function list 4940 open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n"); 4941 if ($debug) { print("---- $image ---\n"); } 4942 for (my $i = 0; $i <= $#{$pclist}; $i++) { 4943 # addr2line always reads hex addresses, and does not need '0x' prefix. 4944 if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); } 4945 printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset)); 4946 if (defined($sep_address)) { 4947 printf ADDRESSES ("%s\n", $sep_address); 4948 } 4949 } 4950 close(ADDRESSES); 4951 if ($debug) { 4952 print("----\n"); 4953 system("cat", $main::tmpfile_sym); 4954 print("----\n"); 4955 system("$cmd < " . ShellEscape($main::tmpfile_sym)); 4956 print("----\n"); 4957 } 4958 4959 open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |") 4960 || error("$cmd: $!\n"); 4961 my $count = 0; # Index in pclist 4962 while (<SYMBOLS>) { 4963 # Read fullfunction and filelineinfo from next pair of lines 4964 s/\r?\n$//g; 4965 my $fullfunction = $_; 4966 $_ = <SYMBOLS>; 4967 s/\r?\n$//g; 4968 my $filelinenum = $_; 4969 4970 if (defined($sep_address) && $fullfunction eq $sep_symbol) { 4971 # Terminating marker for data for this address 4972 $count++; 4973 next; 4974 } 4975 4976 $filelinenum =~ s|\\|/|g; # turn windows-style paths into unix-style paths 4977 4978 my $pcstr = $pclist->[$count]; 4979 my $function = ShortFunctionName($fullfunction); 4980 my $nms = $nm_symbols->{$pcstr}; 4981 if (defined($nms)) { 4982 if ($fullfunction eq '??') { 4983 # nm found a symbol for us. 4984 $function = $nms->[0]; 4985 $fullfunction = $nms->[2]; 4986 } else { 4987 # MapSymbolsWithNM tags each routine with its starting address, 4988 # useful in case the image has multiple occurrences of this 4989 # routine. (It uses a syntax that resembles template paramters, 4990 # that are automatically stripped out by ShortFunctionName().) 4991 # addr2line does not provide the same information. So we check 4992 # if nm disambiguated our symbol, and if so take the annotated 4993 # (nm) version of the routine-name. TODO(csilvers): this won't 4994 # catch overloaded, inlined symbols, which nm doesn't see. 4995 # Better would be to do a check similar to nm's, in this fn. 4996 if ($nms->[2] =~ m/^\Q$function\E/) { # sanity check it's the right fn 4997 $function = $nms->[0]; 4998 $fullfunction = $nms->[2]; 4999 } 5000 } 5001 } 5002 5003 # Prepend to accumulated symbols for pcstr 5004 # (so that caller comes before callee) 5005 my $sym = $symbols->{$pcstr}; 5006 if (!defined($sym)) { 5007 $sym = []; 5008 $symbols->{$pcstr} = $sym; 5009 } 5010 unshift(@{$sym}, $function, $filelinenum, $fullfunction); 5011 if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); } 5012 if (!defined($sep_address)) { 5013 # Inlining is off, so this entry ends immediately 5014 $count++; 5015 } 5016 } 5017 close(SYMBOLS); 5018} 5019 5020# Use nm to map the list of referenced PCs to symbols. Return true iff we 5021# are able to read procedure information via nm. 5022sub MapSymbolsWithNM { 5023 my $image = shift; 5024 my $offset = shift; 5025 my $pclist = shift; 5026 my $symbols = shift; 5027 5028 # Get nm output sorted by increasing address 5029 my $symbol_table = GetProcedureBoundaries($image, "."); 5030 if (!%{$symbol_table}) { 5031 return 0; 5032 } 5033 # Start addresses are already the right length (8 or 16 hex digits). 5034 my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] } 5035 keys(%{$symbol_table}); 5036 5037 if ($#names < 0) { 5038 # No symbols: just use addresses 5039 foreach my $pc (@{$pclist}) { 5040 my $pcstr = "0x" . $pc; 5041 $symbols->{$pc} = [$pcstr, "?", $pcstr]; 5042 } 5043 return 0; 5044 } 5045 5046 # Sort addresses so we can do a join against nm output 5047 my $index = 0; 5048 my $fullname = $names[0]; 5049 my $name = ShortFunctionName($fullname); 5050 foreach my $pc (sort { $a cmp $b } @{$pclist}) { 5051 # Adjust for mapped offset 5052 my $mpc = AddressSub($pc, $offset); 5053 while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){ 5054 $index++; 5055 $fullname = $names[$index]; 5056 $name = ShortFunctionName($fullname); 5057 } 5058 if ($mpc lt $symbol_table->{$fullname}->[1]) { 5059 $symbols->{$pc} = [$name, "?", $fullname]; 5060 } else { 5061 my $pcstr = "0x" . $pc; 5062 $symbols->{$pc} = [$pcstr, "?", $pcstr]; 5063 } 5064 } 5065 return 1; 5066} 5067 5068sub ShortFunctionName { 5069 my $function = shift; 5070 while ($function =~ s/\([^()]*\)(\s*const)?//g) { } # Argument types 5071 while ($function =~ s/<[^<>]*>//g) { } # Remove template arguments 5072 $function =~ s/^.*\s+(\w+::)/$1/; # Remove leading type 5073 return $function; 5074} 5075 5076# Trim overly long symbols found in disassembler output 5077sub CleanDisassembly { 5078 my $d = shift; 5079 while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax) 5080 while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } # Remove template arguments 5081 return $d; 5082} 5083 5084# Clean file name for display 5085sub CleanFileName { 5086 my ($f) = @_; 5087 $f =~ s|^/proc/self/cwd/||; 5088 $f =~ s|^\./||; 5089 return $f; 5090} 5091 5092# Make address relative to section and clean up for display 5093sub UnparseAddress { 5094 my ($offset, $address) = @_; 5095 $address = AddressSub($address, $offset); 5096 $address =~ s/^0x//; 5097 $address =~ s/^0*//; 5098 return $address; 5099} 5100 5101##### Miscellaneous ##### 5102 5103# Find the right versions of the above object tools to use. The 5104# argument is the program file being analyzed, and should be an ELF 5105# 32-bit or ELF 64-bit executable file. The location of the tools 5106# is determined by considering the following options in this order: 5107# 1) --tools option, if set 5108# 2) JEPROF_TOOLS environment variable, if set 5109# 3) the environment 5110sub ConfigureObjTools { 5111 my $prog_file = shift; 5112 5113 # Check for the existence of $prog_file because /usr/bin/file does not 5114 # predictably return error status in prod. 5115 (-e $prog_file) || error("$prog_file does not exist.\n"); 5116 5117 my $file_type = undef; 5118 if (-e "/usr/bin/file") { 5119 # Follow symlinks (at least for systems where "file" supports that). 5120 my $escaped_prog_file = ShellEscape($prog_file); 5121 $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null || 5122 /usr/bin/file $escaped_prog_file`; 5123 } elsif ($^O == "MSWin32") { 5124 $file_type = "MS Windows"; 5125 } else { 5126 print STDERR "WARNING: Can't determine the file type of $prog_file"; 5127 } 5128 5129 if ($file_type =~ /64-bit/) { 5130 # Change $address_length to 16 if the program file is ELF 64-bit. 5131 # We can't detect this from many (most?) heap or lock contention 5132 # profiles, since the actual addresses referenced are generally in low 5133 # memory even for 64-bit programs. 5134 $address_length = 16; 5135 } 5136 5137 if ($file_type =~ /MS Windows/) { 5138 # For windows, we provide a version of nm and addr2line as part of 5139 # the opensource release, which is capable of parsing 5140 # Windows-style PDB executables. It should live in the path, or 5141 # in the same directory as jeprof. 5142 $obj_tool_map{"nm_pdb"} = "nm-pdb"; 5143 $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb"; 5144 } 5145 5146 if ($file_type =~ /Mach-O/) { 5147 # OS X uses otool to examine Mach-O files, rather than objdump. 5148 $obj_tool_map{"otool"} = "otool"; 5149 $obj_tool_map{"addr2line"} = "false"; # no addr2line 5150 $obj_tool_map{"objdump"} = "false"; # no objdump 5151 } 5152 5153 # Go fill in %obj_tool_map with the pathnames to use: 5154 foreach my $tool (keys %obj_tool_map) { 5155 $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool}); 5156 } 5157} 5158 5159# Returns the path of a caller-specified object tool. If --tools or 5160# JEPROF_TOOLS are specified, then returns the full path to the tool 5161# with that prefix. Otherwise, returns the path unmodified (which 5162# means we will look for it on PATH). 5163sub ConfigureTool { 5164 my $tool = shift; 5165 my $path; 5166 5167 # --tools (or $JEPROF_TOOLS) is a comma separated list, where each 5168 # item is either a) a pathname prefix, or b) a map of the form 5169 # <tool>:<path>. First we look for an entry of type (b) for our 5170 # tool. If one is found, we use it. Otherwise, we consider all the 5171 # pathname prefixes in turn, until one yields an existing file. If 5172 # none does, we use a default path. 5173 my $tools = $main::opt_tools || $ENV{"JEPROF_TOOLS"} || ""; 5174 if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) { 5175 $path = $2; 5176 # TODO(csilvers): sanity-check that $path exists? Hard if it's relative. 5177 } elsif ($tools ne '') { 5178 foreach my $prefix (split(',', $tools)) { 5179 next if ($prefix =~ /:/); # ignore "tool:fullpath" entries in the list 5180 if (-x $prefix . $tool) { 5181 $path = $prefix . $tool; 5182 last; 5183 } 5184 } 5185 if (!$path) { 5186 error("No '$tool' found with prefix specified by " . 5187 "--tools (or \$JEPROF_TOOLS) '$tools'\n"); 5188 } 5189 } else { 5190 # ... otherwise use the version that exists in the same directory as 5191 # jeprof. If there's nothing there, use $PATH. 5192 $0 =~ m,[^/]*$,; # this is everything after the last slash 5193 my $dirname = $`; # this is everything up to and including the last slash 5194 if (-x "$dirname$tool") { 5195 $path = "$dirname$tool"; 5196 } else { 5197 $path = $tool; 5198 } 5199 } 5200 if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; } 5201 return $path; 5202} 5203 5204sub ShellEscape { 5205 my @escaped_words = (); 5206 foreach my $word (@_) { 5207 my $escaped_word = $word; 5208 if ($word =~ m![^a-zA-Z0-9/.,_=-]!) { # check for anything not in whitelist 5209 $escaped_word =~ s/'/'\\''/; 5210 $escaped_word = "'$escaped_word'"; 5211 } 5212 push(@escaped_words, $escaped_word); 5213 } 5214 return join(" ", @escaped_words); 5215} 5216 5217sub cleanup { 5218 unlink($main::tmpfile_sym); 5219 unlink(keys %main::tempnames); 5220 5221 # We leave any collected profiles in $HOME/jeprof in case the user wants 5222 # to look at them later. We print a message informing them of this. 5223 if ((scalar(@main::profile_files) > 0) && 5224 defined($main::collected_profile)) { 5225 if (scalar(@main::profile_files) == 1) { 5226 print STDERR "Dynamically gathered profile is in $main::collected_profile\n"; 5227 } 5228 print STDERR "If you want to investigate this profile further, you can do:\n"; 5229 print STDERR "\n"; 5230 print STDERR " jeprof \\\n"; 5231 print STDERR " $main::prog \\\n"; 5232 print STDERR " $main::collected_profile\n"; 5233 print STDERR "\n"; 5234 } 5235} 5236 5237sub sighandler { 5238 cleanup(); 5239 exit(1); 5240} 5241 5242sub error { 5243 my $msg = shift; 5244 print STDERR $msg; 5245 cleanup(); 5246 exit(1); 5247} 5248 5249 5250# Run $nm_command and get all the resulting procedure boundaries whose 5251# names match "$regexp" and returns them in a hashtable mapping from 5252# procedure name to a two-element vector of [start address, end address] 5253sub GetProcedureBoundariesViaNm { 5254 my $escaped_nm_command = shift; # shell-escaped 5255 my $regexp = shift; 5256 5257 my $symbol_table = {}; 5258 open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n"); 5259 my $last_start = "0"; 5260 my $routine = ""; 5261 while (<NM>) { 5262 s/\r//g; # turn windows-looking lines into unix-looking lines 5263 if (m/^\s*([0-9a-f]+) (.) (..*)/) { 5264 my $start_val = $1; 5265 my $type = $2; 5266 my $this_routine = $3; 5267 5268 # It's possible for two symbols to share the same address, if 5269 # one is a zero-length variable (like __start_google_malloc) or 5270 # one symbol is a weak alias to another (like __libc_malloc). 5271 # In such cases, we want to ignore all values except for the 5272 # actual symbol, which in nm-speak has type "T". The logic 5273 # below does this, though it's a bit tricky: what happens when 5274 # we have a series of lines with the same address, is the first 5275 # one gets queued up to be processed. However, it won't 5276 # *actually* be processed until later, when we read a line with 5277 # a different address. That means that as long as we're reading 5278 # lines with the same address, we have a chance to replace that 5279 # item in the queue, which we do whenever we see a 'T' entry -- 5280 # that is, a line with type 'T'. If we never see a 'T' entry, 5281 # we'll just go ahead and process the first entry (which never 5282 # got touched in the queue), and ignore the others. 5283 if ($start_val eq $last_start && $type =~ /t/i) { 5284 # We are the 'T' symbol at this address, replace previous symbol. 5285 $routine = $this_routine; 5286 next; 5287 } elsif ($start_val eq $last_start) { 5288 # We're not the 'T' symbol at this address, so ignore us. 5289 next; 5290 } 5291 5292 if ($this_routine eq $sep_symbol) { 5293 $sep_address = HexExtend($start_val); 5294 } 5295 5296 # Tag this routine with the starting address in case the image 5297 # has multiple occurrences of this routine. We use a syntax 5298 # that resembles template parameters that are automatically 5299 # stripped out by ShortFunctionName() 5300 $this_routine .= "<$start_val>"; 5301 5302 if (defined($routine) && $routine =~ m/$regexp/) { 5303 $symbol_table->{$routine} = [HexExtend($last_start), 5304 HexExtend($start_val)]; 5305 } 5306 $last_start = $start_val; 5307 $routine = $this_routine; 5308 } elsif (m/^Loaded image name: (.+)/) { 5309 # The win32 nm workalike emits information about the binary it is using. 5310 if ($main::opt_debug) { print STDERR "Using Image $1\n"; } 5311 } elsif (m/^PDB file name: (.+)/) { 5312 # The win32 nm workalike emits information about the pdb it is using. 5313 if ($main::opt_debug) { print STDERR "Using PDB $1\n"; } 5314 } 5315 } 5316 close(NM); 5317 # Handle the last line in the nm output. Unfortunately, we don't know 5318 # how big this last symbol is, because we don't know how big the file 5319 # is. For now, we just give it a size of 0. 5320 # TODO(csilvers): do better here. 5321 if (defined($routine) && $routine =~ m/$regexp/) { 5322 $symbol_table->{$routine} = [HexExtend($last_start), 5323 HexExtend($last_start)]; 5324 } 5325 return $symbol_table; 5326} 5327 5328# Gets the procedure boundaries for all routines in "$image" whose names 5329# match "$regexp" and returns them in a hashtable mapping from procedure 5330# name to a two-element vector of [start address, end address]. 5331# Will return an empty map if nm is not installed or not working properly. 5332sub GetProcedureBoundaries { 5333 my $image = shift; 5334 my $regexp = shift; 5335 5336 # If $image doesn't start with /, then put ./ in front of it. This works 5337 # around an obnoxious bug in our probing of nm -f behavior. 5338 # "nm -f $image" is supposed to fail on GNU nm, but if: 5339 # 5340 # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND 5341 # b. you have a.out in your current directory (a not uncommon occurence) 5342 # 5343 # then "nm -f $image" succeeds because -f only looks at the first letter of 5344 # the argument, which looks valid because it's [BbSsPp], and then since 5345 # there's no image provided, it looks for a.out and finds it. 5346 # 5347 # This regex makes sure that $image starts with . or /, forcing the -f 5348 # parsing to fail since . and / are not valid formats. 5349 $image =~ s#^[^/]#./$&#; 5350 5351 # For libc libraries, the copy in /usr/lib/debug contains debugging symbols 5352 my $debugging = DebuggingLibrary($image); 5353 if ($debugging) { 5354 $image = $debugging; 5355 } 5356 5357 my $nm = $obj_tool_map{"nm"}; 5358 my $cppfilt = $obj_tool_map{"c++filt"}; 5359 5360 # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm 5361 # binary doesn't support --demangle. In addition, for OS X we need 5362 # to use the -f flag to get 'flat' nm output (otherwise we don't sort 5363 # properly and get incorrect results). Unfortunately, GNU nm uses -f 5364 # in an incompatible way. So first we test whether our nm supports 5365 # --demangle and -f. 5366 my $demangle_flag = ""; 5367 my $cppfilt_flag = ""; 5368 my $to_devnull = ">$dev_null 2>&1"; 5369 if (system(ShellEscape($nm, "--demangle", "image") . $to_devnull) == 0) { 5370 # In this mode, we do "nm --demangle <foo>" 5371 $demangle_flag = "--demangle"; 5372 $cppfilt_flag = ""; 5373 } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) { 5374 # In this mode, we do "nm <foo> | c++filt" 5375 $cppfilt_flag = " | " . ShellEscape($cppfilt); 5376 }; 5377 my $flatten_flag = ""; 5378 if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) { 5379 $flatten_flag = "-f"; 5380 } 5381 5382 # Finally, in the case $imagie isn't a debug library, we try again with 5383 # -D to at least get *exported* symbols. If we can't use --demangle, 5384 # we use c++filt instead, if it exists on this system. 5385 my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag, 5386 $image) . " 2>$dev_null $cppfilt_flag", 5387 ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag, 5388 $image) . " 2>$dev_null $cppfilt_flag", 5389 # 6nm is for Go binaries 5390 ShellEscape("6nm", "$image") . " 2>$dev_null | sort", 5391 ); 5392 5393 # If the executable is an MS Windows PDB-format executable, we'll 5394 # have set up obj_tool_map("nm_pdb"). In this case, we actually 5395 # want to use both unix nm and windows-specific nm_pdb, since 5396 # PDB-format executables can apparently include dwarf .o files. 5397 if (exists $obj_tool_map{"nm_pdb"}) { 5398 push(@nm_commands, 5399 ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image) 5400 . " 2>$dev_null"); 5401 } 5402 5403 foreach my $nm_command (@nm_commands) { 5404 my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp); 5405 return $symbol_table if (%{$symbol_table}); 5406 } 5407 my $symbol_table = {}; 5408 return $symbol_table; 5409} 5410 5411 5412# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings. 5413# To make them more readable, we add underscores at interesting places. 5414# This routine removes the underscores, producing the canonical representation 5415# used by jeprof to represent addresses, particularly in the tested routines. 5416sub CanonicalHex { 5417 my $arg = shift; 5418 return join '', (split '_',$arg); 5419} 5420 5421 5422# Unit test for AddressAdd: 5423sub AddressAddUnitTest { 5424 my $test_data_8 = shift; 5425 my $test_data_16 = shift; 5426 my $error_count = 0; 5427 my $fail_count = 0; 5428 my $pass_count = 0; 5429 # print STDERR "AddressAddUnitTest: ", 1+$#{$test_data_8}, " tests\n"; 5430 5431 # First a few 8-nibble addresses. Note that this implementation uses 5432 # plain old arithmetic, so a quick sanity check along with verifying what 5433 # happens to overflow (we want it to wrap): 5434 $address_length = 8; 5435 foreach my $row (@{$test_data_8}) { 5436 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5437 my $sum = AddressAdd ($row->[0], $row->[1]); 5438 if ($sum ne $row->[2]) { 5439 printf STDERR "ERROR: %s != %s + %s = %s\n", $sum, 5440 $row->[0], $row->[1], $row->[2]; 5441 ++$fail_count; 5442 } else { 5443 ++$pass_count; 5444 } 5445 } 5446 printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n", 5447 $pass_count, $fail_count; 5448 $error_count = $fail_count; 5449 $fail_count = 0; 5450 $pass_count = 0; 5451 5452 # Now 16-nibble addresses. 5453 $address_length = 16; 5454 foreach my $row (@{$test_data_16}) { 5455 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5456 my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1])); 5457 my $expected = join '', (split '_',$row->[2]); 5458 if ($sum ne CanonicalHex($row->[2])) { 5459 printf STDERR "ERROR: %s != %s + %s = %s\n", $sum, 5460 $row->[0], $row->[1], $row->[2]; 5461 ++$fail_count; 5462 } else { 5463 ++$pass_count; 5464 } 5465 } 5466 printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n", 5467 $pass_count, $fail_count; 5468 $error_count += $fail_count; 5469 5470 return $error_count; 5471} 5472 5473 5474# Unit test for AddressSub: 5475sub AddressSubUnitTest { 5476 my $test_data_8 = shift; 5477 my $test_data_16 = shift; 5478 my $error_count = 0; 5479 my $fail_count = 0; 5480 my $pass_count = 0; 5481 # print STDERR "AddressSubUnitTest: ", 1+$#{$test_data_8}, " tests\n"; 5482 5483 # First a few 8-nibble addresses. Note that this implementation uses 5484 # plain old arithmetic, so a quick sanity check along with verifying what 5485 # happens to overflow (we want it to wrap): 5486 $address_length = 8; 5487 foreach my $row (@{$test_data_8}) { 5488 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5489 my $sum = AddressSub ($row->[0], $row->[1]); 5490 if ($sum ne $row->[3]) { 5491 printf STDERR "ERROR: %s != %s - %s = %s\n", $sum, 5492 $row->[0], $row->[1], $row->[3]; 5493 ++$fail_count; 5494 } else { 5495 ++$pass_count; 5496 } 5497 } 5498 printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n", 5499 $pass_count, $fail_count; 5500 $error_count = $fail_count; 5501 $fail_count = 0; 5502 $pass_count = 0; 5503 5504 # Now 16-nibble addresses. 5505 $address_length = 16; 5506 foreach my $row (@{$test_data_16}) { 5507 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5508 my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1])); 5509 if ($sum ne CanonicalHex($row->[3])) { 5510 printf STDERR "ERROR: %s != %s - %s = %s\n", $sum, 5511 $row->[0], $row->[1], $row->[3]; 5512 ++$fail_count; 5513 } else { 5514 ++$pass_count; 5515 } 5516 } 5517 printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n", 5518 $pass_count, $fail_count; 5519 $error_count += $fail_count; 5520 5521 return $error_count; 5522} 5523 5524 5525# Unit test for AddressInc: 5526sub AddressIncUnitTest { 5527 my $test_data_8 = shift; 5528 my $test_data_16 = shift; 5529 my $error_count = 0; 5530 my $fail_count = 0; 5531 my $pass_count = 0; 5532 # print STDERR "AddressIncUnitTest: ", 1+$#{$test_data_8}, " tests\n"; 5533 5534 # First a few 8-nibble addresses. Note that this implementation uses 5535 # plain old arithmetic, so a quick sanity check along with verifying what 5536 # happens to overflow (we want it to wrap): 5537 $address_length = 8; 5538 foreach my $row (@{$test_data_8}) { 5539 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5540 my $sum = AddressInc ($row->[0]); 5541 if ($sum ne $row->[4]) { 5542 printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum, 5543 $row->[0], $row->[4]; 5544 ++$fail_count; 5545 } else { 5546 ++$pass_count; 5547 } 5548 } 5549 printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n", 5550 $pass_count, $fail_count; 5551 $error_count = $fail_count; 5552 $fail_count = 0; 5553 $pass_count = 0; 5554 5555 # Now 16-nibble addresses. 5556 $address_length = 16; 5557 foreach my $row (@{$test_data_16}) { 5558 if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } 5559 my $sum = AddressInc (CanonicalHex($row->[0])); 5560 if ($sum ne CanonicalHex($row->[4])) { 5561 printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum, 5562 $row->[0], $row->[4]; 5563 ++$fail_count; 5564 } else { 5565 ++$pass_count; 5566 } 5567 } 5568 printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n", 5569 $pass_count, $fail_count; 5570 $error_count += $fail_count; 5571 5572 return $error_count; 5573} 5574 5575 5576# Driver for unit tests. 5577# Currently just the address add/subtract/increment routines for 64-bit. 5578sub RunUnitTests { 5579 my $error_count = 0; 5580 5581 # This is a list of tuples [a, b, a+b, a-b, a+1] 5582 my $unit_test_data_8 = [ 5583 [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)], 5584 [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)], 5585 [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)], 5586 [qw(00000001 ffffffff 00000000 00000002 00000002)], 5587 [qw(00000001 fffffff0 fffffff1 00000011 00000002)], 5588 ]; 5589 my $unit_test_data_16 = [ 5590 # The implementation handles data in 7-nibble chunks, so those are the 5591 # interesting boundaries. 5592 [qw(aaaaaaaa 50505050 5593 00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)], 5594 [qw(50505050 aaaaaaaa 5595 00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)], 5596 [qw(ffffffff aaaaaaaa 5597 00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)], 5598 [qw(00000001 ffffffff 5599 00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)], 5600 [qw(00000001 fffffff0 5601 00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)], 5602 5603 [qw(00_a00000a_aaaaaaa 50505050 5604 00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)], 5605 [qw(0f_fff0005_0505050 aaaaaaaa 5606 0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)], 5607 [qw(00_000000f_fffffff 01_800000a_aaaaaaa 5608 01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)], 5609 [qw(00_0000000_0000001 ff_fffffff_fffffff 5610 00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)], 5611 [qw(00_0000000_0000001 ff_fffffff_ffffff0 5612 ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)], 5613 ]; 5614 5615 $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16); 5616 $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16); 5617 $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16); 5618 if ($error_count > 0) { 5619 print STDERR $error_count, " errors: FAILED\n"; 5620 } else { 5621 print STDERR "PASS\n"; 5622 } 5623 exit ($error_count); 5624} 5625