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