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