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