github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/misc/pprof (about)

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