github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/misc/pprof (about)

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