memory/jemalloc/src/bin/pprof

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial