memory/jemalloc/src/bin/pprof

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/memory/jemalloc/src/bin/pprof	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,5348 @@
     1.4 +#! /usr/bin/env perl
     1.5 +
     1.6 +# Copyright (c) 1998-2007, Google Inc.
     1.7 +# All rights reserved.
     1.8 +# 
     1.9 +# Redistribution and use in source and binary forms, with or without
    1.10 +# modification, are permitted provided that the following conditions are
    1.11 +# met:
    1.12 +# 
    1.13 +#     * Redistributions of source code must retain the above copyright
    1.14 +# notice, this list of conditions and the following disclaimer.
    1.15 +#     * Redistributions in binary form must reproduce the above
    1.16 +# copyright notice, this list of conditions and the following disclaimer
    1.17 +# in the documentation and/or other materials provided with the
    1.18 +# distribution.
    1.19 +#     * Neither the name of Google Inc. nor the names of its
    1.20 +# contributors may be used to endorse or promote products derived from
    1.21 +# this software without specific prior written permission.
    1.22 +# 
    1.23 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.24 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.25 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.26 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1.27 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.28 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.29 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1.30 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1.31 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.32 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1.33 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.34 +
    1.35 +# ---
    1.36 +# Program for printing the profile generated by common/profiler.cc,
    1.37 +# or by the heap profiler (common/debugallocation.cc)
    1.38 +#
    1.39 +# The profile contains a sequence of entries of the form:
    1.40 +#       <count> <stack trace>
    1.41 +# This program parses the profile, and generates user-readable
    1.42 +# output.
    1.43 +#
    1.44 +# Examples:
    1.45 +#
    1.46 +# % tools/pprof "program" "profile"
    1.47 +#   Enters "interactive" mode
    1.48 +#
    1.49 +# % tools/pprof --text "program" "profile"
    1.50 +#   Generates one line per procedure
    1.51 +#
    1.52 +# % tools/pprof --gv "program" "profile"
    1.53 +#   Generates annotated call-graph and displays via "gv"
    1.54 +#
    1.55 +# % tools/pprof --gv --focus=Mutex "program" "profile"
    1.56 +#   Restrict to code paths that involve an entry that matches "Mutex"
    1.57 +#
    1.58 +# % tools/pprof --gv --focus=Mutex --ignore=string "program" "profile"
    1.59 +#   Restrict to code paths that involve an entry that matches "Mutex"
    1.60 +#   and does not match "string"
    1.61 +#
    1.62 +# % tools/pprof --list=IBF_CheckDocid "program" "profile"
    1.63 +#   Generates disassembly listing of all routines with at least one
    1.64 +#   sample that match the --list=<regexp> pattern.  The listing is
    1.65 +#   annotated with the flat and cumulative sample counts at each line.
    1.66 +#
    1.67 +# % tools/pprof --disasm=IBF_CheckDocid "program" "profile"
    1.68 +#   Generates disassembly listing of all routines with at least one
    1.69 +#   sample that match the --disasm=<regexp> pattern.  The listing is
    1.70 +#   annotated with the flat and cumulative sample counts at each PC value.
    1.71 +#
    1.72 +# TODO: Use color to indicate files?
    1.73 +
    1.74 +use strict;
    1.75 +use warnings;
    1.76 +use Getopt::Long;
    1.77 +
    1.78 +my $PPROF_VERSION = "2.0";
    1.79 +
    1.80 +# These are the object tools we use which can come from a
    1.81 +# user-specified location using --tools, from the PPROF_TOOLS
    1.82 +# environment variable, or from the environment.
    1.83 +my %obj_tool_map = (
    1.84 +  "objdump" => "objdump",
    1.85 +  "nm" => "nm",
    1.86 +  "addr2line" => "addr2line",
    1.87 +  "c++filt" => "c++filt",
    1.88 +  ## ConfigureObjTools may add architecture-specific entries:
    1.89 +  #"nm_pdb" => "nm-pdb",       # for reading windows (PDB-format) executables
    1.90 +  #"addr2line_pdb" => "addr2line-pdb",                                # ditto
    1.91 +  #"otool" => "otool",         # equivalent of objdump on OS X
    1.92 +);
    1.93 +# NOTE: these are lists, so you can put in commandline flags if you want.
    1.94 +my @DOT = ("dot");          # leave non-absolute, since it may be in /usr/local
    1.95 +my @GV = ("gv");
    1.96 +my @EVINCE = ("evince");    # could also be xpdf or perhaps acroread
    1.97 +my @KCACHEGRIND = ("kcachegrind");
    1.98 +my @PS2PDF = ("ps2pdf");
    1.99 +# These are used for dynamic profiles
   1.100 +my @URL_FETCHER = ("curl", "-s");
   1.101 +
   1.102 +# These are the web pages that servers need to support for dynamic profiles
   1.103 +my $HEAP_PAGE = "/pprof/heap";
   1.104 +my $PROFILE_PAGE = "/pprof/profile";   # must support cgi-param "?seconds=#"
   1.105 +my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; # must support cgi-param
   1.106 +                                                # ?seconds=#&event=x&period=n
   1.107 +my $GROWTH_PAGE = "/pprof/growth";
   1.108 +my $CONTENTION_PAGE = "/pprof/contention";
   1.109 +my $WALL_PAGE = "/pprof/wall(?:\\?.*)?";  # accepts options like namefilter
   1.110 +my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?";
   1.111 +my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; # must support cgi-param
   1.112 +                                                       # "?seconds=#",
   1.113 +                                                       # "?tags_regexp=#" and
   1.114 +                                                       # "?type=#".
   1.115 +my $SYMBOL_PAGE = "/pprof/symbol";     # must support symbol lookup via POST
   1.116 +my $PROGRAM_NAME_PAGE = "/pprof/cmdline";
   1.117 +
   1.118 +# These are the web pages that can be named on the command line.
   1.119 +# All the alternatives must begin with /.
   1.120 +my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" .
   1.121 +               "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" .
   1.122 +               "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)";
   1.123 +
   1.124 +# default binary name
   1.125 +my $UNKNOWN_BINARY = "(unknown)";
   1.126 +
   1.127 +# There is a pervasive dependency on the length (in hex characters,
   1.128 +# i.e., nibbles) of an address, distinguishing between 32-bit and
   1.129 +# 64-bit profiles.  To err on the safe size, default to 64-bit here:
   1.130 +my $address_length = 16;
   1.131 +
   1.132 +my $dev_null = "/dev/null";
   1.133 +if (! -e $dev_null && $^O =~ /MSWin/) {    # $^O is the OS perl was built for
   1.134 +  $dev_null = "nul";
   1.135 +}
   1.136 +
   1.137 +# A list of paths to search for shared object files
   1.138 +my @prefix_list = ();
   1.139 +
   1.140 +# Special routine name that should not have any symbols.
   1.141 +# Used as separator to parse "addr2line -i" output.
   1.142 +my $sep_symbol = '_fini';
   1.143 +my $sep_address = undef;
   1.144 +
   1.145 +##### Argument parsing #####
   1.146 +
   1.147 +sub usage_string {
   1.148 +  return <<EOF;
   1.149 +Usage:
   1.150 +pprof [options] <program> <profiles>
   1.151 +   <profiles> is a space separated list of profile names.
   1.152 +pprof [options] <symbolized-profiles>
   1.153 +   <symbolized-profiles> is a list of profile files where each file contains
   1.154 +   the necessary symbol mappings  as well as profile data (likely generated
   1.155 +   with --raw).
   1.156 +pprof [options] <profile>
   1.157 +   <profile> is a remote form.  Symbols are obtained from host:port$SYMBOL_PAGE
   1.158 +
   1.159 +   Each name can be:
   1.160 +   /path/to/profile        - a path to a profile file
   1.161 +   host:port[/<service>]   - a location of a service to get profile from
   1.162 +
   1.163 +   The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
   1.164 +                         $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
   1.165 +                         $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.
   1.166 +   For instance:
   1.167 +     pprof http://myserver.com:80$HEAP_PAGE
   1.168 +   If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
   1.169 +pprof --symbols <program>
   1.170 +   Maps addresses to symbol names.  In this mode, stdin should be a
   1.171 +   list of library mappings, in the same format as is found in the heap-
   1.172 +   and cpu-profile files (this loosely matches that of /proc/self/maps
   1.173 +   on linux), followed by a list of hex addresses to map, one per line.
   1.174 +
   1.175 +   For more help with querying remote servers, including how to add the
   1.176 +   necessary server-side support code, see this filename (or one like it):
   1.177 +
   1.178 +   /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html
   1.179 +
   1.180 +Options:
   1.181 +   --cum               Sort by cumulative data
   1.182 +   --base=<base>       Subtract <base> from <profile> before display
   1.183 +   --interactive       Run in interactive mode (interactive "help" gives help) [default]
   1.184 +   --seconds=<n>       Length of time for dynamic profiles [default=30 secs]
   1.185 +   --add_lib=<file>    Read additional symbols and line info from the given library
   1.186 +   --lib_prefix=<dir>  Comma separated list of library path prefixes
   1.187 +
   1.188 +Reporting Granularity:
   1.189 +   --addresses         Report at address level
   1.190 +   --lines             Report at source line level
   1.191 +   --functions         Report at function level [default]
   1.192 +   --files             Report at source file level
   1.193 +
   1.194 +Output type:
   1.195 +   --text              Generate text report
   1.196 +   --callgrind         Generate callgrind format to stdout
   1.197 +   --gv                Generate Postscript and display
   1.198 +   --evince            Generate PDF and display
   1.199 +   --web               Generate SVG and display
   1.200 +   --list=<regexp>     Generate source listing of matching routines
   1.201 +   --disasm=<regexp>   Generate disassembly of matching routines
   1.202 +   --symbols           Print demangled symbol names found at given addresses
   1.203 +   --dot               Generate DOT file to stdout
   1.204 +   --ps                Generate Postcript to stdout
   1.205 +   --pdf               Generate PDF to stdout
   1.206 +   --svg               Generate SVG to stdout
   1.207 +   --gif               Generate GIF to stdout
   1.208 +   --raw               Generate symbolized pprof data (useful with remote fetch)
   1.209 +
   1.210 +Heap-Profile Options:
   1.211 +   --inuse_space       Display in-use (mega)bytes [default]
   1.212 +   --inuse_objects     Display in-use objects
   1.213 +   --alloc_space       Display allocated (mega)bytes
   1.214 +   --alloc_objects     Display allocated objects
   1.215 +   --show_bytes        Display space in bytes
   1.216 +   --drop_negative     Ignore negative differences
   1.217 +
   1.218 +Contention-profile options:
   1.219 +   --total_delay       Display total delay at each region [default]
   1.220 +   --contentions       Display number of delays at each region
   1.221 +   --mean_delay        Display mean delay at each region
   1.222 +
   1.223 +Call-graph Options:
   1.224 +   --nodecount=<n>     Show at most so many nodes [default=80]
   1.225 +   --nodefraction=<f>  Hide nodes below <f>*total [default=.005]
   1.226 +   --edgefraction=<f>  Hide edges below <f>*total [default=.001]
   1.227 +   --maxdegree=<n>     Max incoming/outgoing edges per node [default=8]
   1.228 +   --focus=<regexp>    Focus on nodes matching <regexp>
   1.229 +   --ignore=<regexp>   Ignore nodes matching <regexp>
   1.230 +   --scale=<n>         Set GV scaling [default=0]
   1.231 +   --heapcheck         Make nodes with non-0 object counts
   1.232 +                       (i.e. direct leak generators) more visible
   1.233 +
   1.234 +Miscellaneous:
   1.235 +   --tools=<prefix or binary:fullpath>[,...]   \$PATH for object tool pathnames
   1.236 +   --test              Run unit tests
   1.237 +   --help              This message
   1.238 +   --version           Version information
   1.239 +
   1.240 +Environment Variables:
   1.241 +   PPROF_TMPDIR        Profiles directory. Defaults to \$HOME/pprof
   1.242 +   PPROF_TOOLS         Prefix for object tools pathnames
   1.243 +
   1.244 +Examples:
   1.245 +
   1.246 +pprof /bin/ls ls.prof
   1.247 +                       Enters "interactive" mode
   1.248 +pprof --text /bin/ls ls.prof
   1.249 +                       Outputs one line per procedure
   1.250 +pprof --web /bin/ls ls.prof
   1.251 +                       Displays annotated call-graph in web browser
   1.252 +pprof --gv /bin/ls ls.prof
   1.253 +                       Displays annotated call-graph via 'gv'
   1.254 +pprof --gv --focus=Mutex /bin/ls ls.prof
   1.255 +                       Restricts to code paths including a .*Mutex.* entry
   1.256 +pprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
   1.257 +                       Code paths including Mutex but not string
   1.258 +pprof --list=getdir /bin/ls ls.prof
   1.259 +                       (Per-line) annotated source listing for getdir()
   1.260 +pprof --disasm=getdir /bin/ls ls.prof
   1.261 +                       (Per-PC) annotated disassembly for getdir()
   1.262 +
   1.263 +pprof http://localhost:1234/
   1.264 +                       Enters "interactive" mode
   1.265 +pprof --text localhost:1234
   1.266 +                       Outputs one line per procedure for localhost:1234
   1.267 +pprof --raw localhost:1234 > ./local.raw
   1.268 +pprof --text ./local.raw
   1.269 +                       Fetches a remote profile for later analysis and then
   1.270 +                       analyzes it in text mode.
   1.271 +EOF
   1.272 +}
   1.273 +
   1.274 +sub version_string {
   1.275 +  return <<EOF
   1.276 +pprof (part of gperftools $PPROF_VERSION)
   1.277 +
   1.278 +Copyright 1998-2007 Google Inc.
   1.279 +
   1.280 +This is BSD licensed software; see the source for copying conditions
   1.281 +and license information.
   1.282 +There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
   1.283 +PARTICULAR PURPOSE.
   1.284 +EOF
   1.285 +}
   1.286 +
   1.287 +sub usage {
   1.288 +  my $msg = shift;
   1.289 +  print STDERR "$msg\n\n";
   1.290 +  print STDERR usage_string();
   1.291 +  print STDERR "\nFATAL ERROR: $msg\n";    # just as a reminder
   1.292 +  exit(1);
   1.293 +}
   1.294 +
   1.295 +sub Init() {
   1.296 +  # Setup tmp-file name and handler to clean it up.
   1.297 +  # We do this in the very beginning so that we can use
   1.298 +  # error() and cleanup() function anytime here after.
   1.299 +  $main::tmpfile_sym = "/tmp/pprof$$.sym";
   1.300 +  $main::tmpfile_ps = "/tmp/pprof$$";
   1.301 +  $main::next_tmpfile = 0;
   1.302 +  $SIG{'INT'} = \&sighandler;
   1.303 +
   1.304 +  # Cache from filename/linenumber to source code
   1.305 +  $main::source_cache = ();
   1.306 +
   1.307 +  $main::opt_help = 0;
   1.308 +  $main::opt_version = 0;
   1.309 +
   1.310 +  $main::opt_cum = 0;
   1.311 +  $main::opt_base = '';
   1.312 +  $main::opt_addresses = 0;
   1.313 +  $main::opt_lines = 0;
   1.314 +  $main::opt_functions = 0;
   1.315 +  $main::opt_files = 0;
   1.316 +  $main::opt_lib_prefix = "";
   1.317 +
   1.318 +  $main::opt_text = 0;
   1.319 +  $main::opt_callgrind = 0;
   1.320 +  $main::opt_list = "";
   1.321 +  $main::opt_disasm = "";
   1.322 +  $main::opt_symbols = 0;
   1.323 +  $main::opt_gv = 0;
   1.324 +  $main::opt_evince = 0;
   1.325 +  $main::opt_web = 0;
   1.326 +  $main::opt_dot = 0;
   1.327 +  $main::opt_ps = 0;
   1.328 +  $main::opt_pdf = 0;
   1.329 +  $main::opt_gif = 0;
   1.330 +  $main::opt_svg = 0;
   1.331 +  $main::opt_raw = 0;
   1.332 +
   1.333 +  $main::opt_nodecount = 80;
   1.334 +  $main::opt_nodefraction = 0.005;
   1.335 +  $main::opt_edgefraction = 0.001;
   1.336 +  $main::opt_maxdegree = 8;
   1.337 +  $main::opt_focus = '';
   1.338 +  $main::opt_ignore = '';
   1.339 +  $main::opt_scale = 0;
   1.340 +  $main::opt_heapcheck = 0;
   1.341 +  $main::opt_seconds = 30;
   1.342 +  $main::opt_lib = "";
   1.343 +
   1.344 +  $main::opt_inuse_space   = 0;
   1.345 +  $main::opt_inuse_objects = 0;
   1.346 +  $main::opt_alloc_space   = 0;
   1.347 +  $main::opt_alloc_objects = 0;
   1.348 +  $main::opt_show_bytes    = 0;
   1.349 +  $main::opt_drop_negative = 0;
   1.350 +  $main::opt_interactive   = 0;
   1.351 +
   1.352 +  $main::opt_total_delay = 0;
   1.353 +  $main::opt_contentions = 0;
   1.354 +  $main::opt_mean_delay = 0;
   1.355 +
   1.356 +  $main::opt_tools   = "";
   1.357 +  $main::opt_debug   = 0;
   1.358 +  $main::opt_test    = 0;
   1.359 +
   1.360 +  # These are undocumented flags used only by unittests.
   1.361 +  $main::opt_test_stride = 0;
   1.362 +
   1.363 +  # Are we using $SYMBOL_PAGE?
   1.364 +  $main::use_symbol_page = 0;
   1.365 +
   1.366 +  # Files returned by TempName.
   1.367 +  %main::tempnames = ();
   1.368 +
   1.369 +  # Type of profile we are dealing with
   1.370 +  # Supported types:
   1.371 +  #     cpu
   1.372 +  #     heap
   1.373 +  #     growth
   1.374 +  #     contention
   1.375 +  $main::profile_type = '';     # Empty type means "unknown"
   1.376 +
   1.377 +  GetOptions("help!"          => \$main::opt_help,
   1.378 +             "version!"       => \$main::opt_version,
   1.379 +             "cum!"           => \$main::opt_cum,
   1.380 +             "base=s"         => \$main::opt_base,
   1.381 +             "seconds=i"      => \$main::opt_seconds,
   1.382 +             "add_lib=s"      => \$main::opt_lib,
   1.383 +             "lib_prefix=s"   => \$main::opt_lib_prefix,
   1.384 +             "functions!"     => \$main::opt_functions,
   1.385 +             "lines!"         => \$main::opt_lines,
   1.386 +             "addresses!"     => \$main::opt_addresses,
   1.387 +             "files!"         => \$main::opt_files,
   1.388 +             "text!"          => \$main::opt_text,
   1.389 +             "callgrind!"     => \$main::opt_callgrind,
   1.390 +             "list=s"         => \$main::opt_list,
   1.391 +             "disasm=s"       => \$main::opt_disasm,
   1.392 +             "symbols!"       => \$main::opt_symbols,
   1.393 +             "gv!"            => \$main::opt_gv,
   1.394 +             "evince!"        => \$main::opt_evince,
   1.395 +             "web!"           => \$main::opt_web,
   1.396 +             "dot!"           => \$main::opt_dot,
   1.397 +             "ps!"            => \$main::opt_ps,
   1.398 +             "pdf!"           => \$main::opt_pdf,
   1.399 +             "svg!"           => \$main::opt_svg,
   1.400 +             "gif!"           => \$main::opt_gif,
   1.401 +             "raw!"           => \$main::opt_raw,
   1.402 +             "interactive!"   => \$main::opt_interactive,
   1.403 +             "nodecount=i"    => \$main::opt_nodecount,
   1.404 +             "nodefraction=f" => \$main::opt_nodefraction,
   1.405 +             "edgefraction=f" => \$main::opt_edgefraction,
   1.406 +             "maxdegree=i"    => \$main::opt_maxdegree,
   1.407 +             "focus=s"        => \$main::opt_focus,
   1.408 +             "ignore=s"       => \$main::opt_ignore,
   1.409 +             "scale=i"        => \$main::opt_scale,
   1.410 +             "heapcheck"      => \$main::opt_heapcheck,
   1.411 +             "inuse_space!"   => \$main::opt_inuse_space,
   1.412 +             "inuse_objects!" => \$main::opt_inuse_objects,
   1.413 +             "alloc_space!"   => \$main::opt_alloc_space,
   1.414 +             "alloc_objects!" => \$main::opt_alloc_objects,
   1.415 +             "show_bytes!"    => \$main::opt_show_bytes,
   1.416 +             "drop_negative!" => \$main::opt_drop_negative,
   1.417 +             "total_delay!"   => \$main::opt_total_delay,
   1.418 +             "contentions!"   => \$main::opt_contentions,
   1.419 +             "mean_delay!"    => \$main::opt_mean_delay,
   1.420 +             "tools=s"        => \$main::opt_tools,
   1.421 +             "test!"          => \$main::opt_test,
   1.422 +             "debug!"         => \$main::opt_debug,
   1.423 +             # Undocumented flags used only by unittests:
   1.424 +             "test_stride=i"  => \$main::opt_test_stride,
   1.425 +      ) || usage("Invalid option(s)");
   1.426 +
   1.427 +  # Deal with the standard --help and --version
   1.428 +  if ($main::opt_help) {
   1.429 +    print usage_string();
   1.430 +    exit(0);
   1.431 +  }
   1.432 +
   1.433 +  if ($main::opt_version) {
   1.434 +    print version_string();
   1.435 +    exit(0);
   1.436 +  }
   1.437 +
   1.438 +  # Disassembly/listing/symbols mode requires address-level info
   1.439 +  if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {
   1.440 +    $main::opt_functions = 0;
   1.441 +    $main::opt_lines = 0;
   1.442 +    $main::opt_addresses = 1;
   1.443 +    $main::opt_files = 0;
   1.444 +  }
   1.445 +
   1.446 +  # Check heap-profiling flags
   1.447 +  if ($main::opt_inuse_space +
   1.448 +      $main::opt_inuse_objects +
   1.449 +      $main::opt_alloc_space +
   1.450 +      $main::opt_alloc_objects > 1) {
   1.451 +    usage("Specify at most on of --inuse/--alloc options");
   1.452 +  }
   1.453 +
   1.454 +  # Check output granularities
   1.455 +  my $grains =
   1.456 +      $main::opt_functions +
   1.457 +      $main::opt_lines +
   1.458 +      $main::opt_addresses +
   1.459 +      $main::opt_files +
   1.460 +      0;
   1.461 +  if ($grains > 1) {
   1.462 +    usage("Only specify one output granularity option");
   1.463 +  }
   1.464 +  if ($grains == 0) {
   1.465 +    $main::opt_functions = 1;
   1.466 +  }
   1.467 +
   1.468 +  # Check output modes
   1.469 +  my $modes =
   1.470 +      $main::opt_text +
   1.471 +      $main::opt_callgrind +
   1.472 +      ($main::opt_list eq '' ? 0 : 1) +
   1.473 +      ($main::opt_disasm eq '' ? 0 : 1) +
   1.474 +      ($main::opt_symbols == 0 ? 0 : 1) +
   1.475 +      $main::opt_gv +
   1.476 +      $main::opt_evince +
   1.477 +      $main::opt_web +
   1.478 +      $main::opt_dot +
   1.479 +      $main::opt_ps +
   1.480 +      $main::opt_pdf +
   1.481 +      $main::opt_svg +
   1.482 +      $main::opt_gif +
   1.483 +      $main::opt_raw +
   1.484 +      $main::opt_interactive +
   1.485 +      0;
   1.486 +  if ($modes > 1) {
   1.487 +    usage("Only specify one output mode");
   1.488 +  }
   1.489 +  if ($modes == 0) {
   1.490 +    if (-t STDOUT) {  # If STDOUT is a tty, activate interactive mode
   1.491 +      $main::opt_interactive = 1;
   1.492 +    } else {
   1.493 +      $main::opt_text = 1;
   1.494 +    }
   1.495 +  }
   1.496 +
   1.497 +  if ($main::opt_test) {
   1.498 +    RunUnitTests();
   1.499 +    # Should not return
   1.500 +    exit(1);
   1.501 +  }
   1.502 +
   1.503 +  # Binary name and profile arguments list
   1.504 +  $main::prog = "";
   1.505 +  @main::pfile_args = ();
   1.506 +
   1.507 +  # Remote profiling without a binary (using $SYMBOL_PAGE instead)
   1.508 +  if (@ARGV > 0) {
   1.509 +    if (IsProfileURL($ARGV[0])) {
   1.510 +      $main::use_symbol_page = 1;
   1.511 +    } elsif (IsSymbolizedProfileFile($ARGV[0])) {
   1.512 +      $main::use_symbolized_profile = 1;
   1.513 +      $main::prog = $UNKNOWN_BINARY;  # will be set later from the profile file
   1.514 +    }
   1.515 +  }
   1.516 +
   1.517 +  if ($main::use_symbol_page || $main::use_symbolized_profile) {
   1.518 +    # We don't need a binary!
   1.519 +    my %disabled = ('--lines' => $main::opt_lines,
   1.520 +                    '--disasm' => $main::opt_disasm);
   1.521 +    for my $option (keys %disabled) {
   1.522 +      usage("$option cannot be used without a binary") if $disabled{$option};
   1.523 +    }
   1.524 +    # Set $main::prog later...
   1.525 +    scalar(@ARGV) || usage("Did not specify profile file");
   1.526 +  } elsif ($main::opt_symbols) {
   1.527 +    # --symbols needs a binary-name (to run nm on, etc) but not profiles
   1.528 +    $main::prog = shift(@ARGV) || usage("Did not specify program");
   1.529 +  } else {
   1.530 +    $main::prog = shift(@ARGV) || usage("Did not specify program");
   1.531 +    scalar(@ARGV) || usage("Did not specify profile file");
   1.532 +  }
   1.533 +
   1.534 +  # Parse profile file/location arguments
   1.535 +  foreach my $farg (@ARGV) {
   1.536 +    if ($farg =~ m/(.*)\@([0-9]+)(|\/.*)$/ ) {
   1.537 +      my $machine = $1;
   1.538 +      my $num_machines = $2;
   1.539 +      my $path = $3;
   1.540 +      for (my $i = 0; $i < $num_machines; $i++) {
   1.541 +        unshift(@main::pfile_args, "$i.$machine$path");
   1.542 +      }
   1.543 +    } else {
   1.544 +      unshift(@main::pfile_args, $farg);
   1.545 +    }
   1.546 +  }
   1.547 +
   1.548 +  if ($main::use_symbol_page) {
   1.549 +    unless (IsProfileURL($main::pfile_args[0])) {
   1.550 +      error("The first profile should be a remote form to use $SYMBOL_PAGE\n");
   1.551 +    }
   1.552 +    CheckSymbolPage();
   1.553 +    $main::prog = FetchProgramName();
   1.554 +  } elsif (!$main::use_symbolized_profile) {  # may not need objtools!
   1.555 +    ConfigureObjTools($main::prog)
   1.556 +  }
   1.557 +
   1.558 +  # Break the opt_lib_prefix into the prefix_list array
   1.559 +  @prefix_list = split (',', $main::opt_lib_prefix);
   1.560 +
   1.561 +  # Remove trailing / from the prefixes, in the list to prevent
   1.562 +  # searching things like /my/path//lib/mylib.so
   1.563 +  foreach (@prefix_list) {
   1.564 +    s|/+$||;
   1.565 +  }
   1.566 +}
   1.567 +
   1.568 +sub Main() {
   1.569 +  Init();
   1.570 +  $main::collected_profile = undef;
   1.571 +  @main::profile_files = ();
   1.572 +  $main::op_time = time();
   1.573 +
   1.574 +  # Printing symbols is special and requires a lot less info that most.
   1.575 +  if ($main::opt_symbols) {
   1.576 +    PrintSymbols(*STDIN);   # Get /proc/maps and symbols output from stdin
   1.577 +    return;
   1.578 +  }
   1.579 +
   1.580 +  # Fetch all profile data
   1.581 +  FetchDynamicProfiles();
   1.582 +
   1.583 +  # this will hold symbols that we read from the profile files
   1.584 +  my $symbol_map = {};
   1.585 +
   1.586 +  # Read one profile, pick the last item on the list
   1.587 +  my $data = ReadProfile($main::prog, pop(@main::profile_files));
   1.588 +  my $profile = $data->{profile};
   1.589 +  my $pcs = $data->{pcs};
   1.590 +  my $libs = $data->{libs};   # Info about main program and shared libraries
   1.591 +  $symbol_map = MergeSymbols($symbol_map, $data->{symbols});
   1.592 +
   1.593 +  # Add additional profiles, if available.
   1.594 +  if (scalar(@main::profile_files) > 0) {
   1.595 +    foreach my $pname (@main::profile_files) {
   1.596 +      my $data2 = ReadProfile($main::prog, $pname);
   1.597 +      $profile = AddProfile($profile, $data2->{profile});
   1.598 +      $pcs = AddPcs($pcs, $data2->{pcs});
   1.599 +      $symbol_map = MergeSymbols($symbol_map, $data2->{symbols});
   1.600 +    }
   1.601 +  }
   1.602 +
   1.603 +  # Subtract base from profile, if specified
   1.604 +  if ($main::opt_base ne '') {
   1.605 +    my $base = ReadProfile($main::prog, $main::opt_base);
   1.606 +    $profile = SubtractProfile($profile, $base->{profile});
   1.607 +    $pcs = AddPcs($pcs, $base->{pcs});
   1.608 +    $symbol_map = MergeSymbols($symbol_map, $base->{symbols});
   1.609 +  }
   1.610 +
   1.611 +  # Get total data in profile
   1.612 +  my $total = TotalProfile($profile);
   1.613 +
   1.614 +  # Collect symbols
   1.615 +  my $symbols;
   1.616 +  if ($main::use_symbolized_profile) {
   1.617 +    $symbols = FetchSymbols($pcs, $symbol_map);
   1.618 +  } elsif ($main::use_symbol_page) {
   1.619 +    $symbols = FetchSymbols($pcs);
   1.620 +  } else {
   1.621 +    # TODO(csilvers): $libs uses the /proc/self/maps data from profile1,
   1.622 +    # which may differ from the data from subsequent profiles, especially
   1.623 +    # if they were run on different machines.  Use appropriate libs for
   1.624 +    # each pc somehow.
   1.625 +    $symbols = ExtractSymbols($libs, $pcs);
   1.626 +  }
   1.627 +
   1.628 +  # Remove uniniteresting stack items
   1.629 +  $profile = RemoveUninterestingFrames($symbols, $profile);
   1.630 +
   1.631 +  # Focus?
   1.632 +  if ($main::opt_focus ne '') {
   1.633 +    $profile = FocusProfile($symbols, $profile, $main::opt_focus);
   1.634 +  }
   1.635 +
   1.636 +  # Ignore?
   1.637 +  if ($main::opt_ignore ne '') {
   1.638 +    $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);
   1.639 +  }
   1.640 +
   1.641 +  my $calls = ExtractCalls($symbols, $profile);
   1.642 +
   1.643 +  # Reduce profiles to required output granularity, and also clean
   1.644 +  # each stack trace so a given entry exists at most once.
   1.645 +  my $reduced = ReduceProfile($symbols, $profile);
   1.646 +
   1.647 +  # Get derived profiles
   1.648 +  my $flat = FlatProfile($reduced);
   1.649 +  my $cumulative = CumulativeProfile($reduced);
   1.650 +
   1.651 +  # Print
   1.652 +  if (!$main::opt_interactive) {
   1.653 +    if ($main::opt_disasm) {
   1.654 +      PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);
   1.655 +    } elsif ($main::opt_list) {
   1.656 +      PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
   1.657 +    } elsif ($main::opt_text) {
   1.658 +      # Make sure the output is empty when have nothing to report
   1.659 +      # (only matters when --heapcheck is given but we must be
   1.660 +      # compatible with old branches that did not pass --heapcheck always):
   1.661 +      if ($total != 0) {
   1.662 +        printf("Total: %s %s\n", Unparse($total), Units());
   1.663 +      }
   1.664 +      PrintText($symbols, $flat, $cumulative, -1);
   1.665 +    } elsif ($main::opt_raw) {
   1.666 +      PrintSymbolizedProfile($symbols, $profile, $main::prog);
   1.667 +    } elsif ($main::opt_callgrind) {
   1.668 +      PrintCallgrind($calls);
   1.669 +    } else {
   1.670 +      if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
   1.671 +        if ($main::opt_gv) {
   1.672 +          RunGV(TempName($main::next_tmpfile, "ps"), "");
   1.673 +        } elsif ($main::opt_evince) {
   1.674 +          RunEvince(TempName($main::next_tmpfile, "pdf"), "");
   1.675 +        } elsif ($main::opt_web) {
   1.676 +          my $tmp = TempName($main::next_tmpfile, "svg");
   1.677 +          RunWeb($tmp);
   1.678 +          # The command we run might hand the file name off
   1.679 +          # to an already running browser instance and then exit.
   1.680 +          # Normally, we'd remove $tmp on exit (right now),
   1.681 +          # but fork a child to remove $tmp a little later, so that the
   1.682 +          # browser has time to load it first.
   1.683 +          delete $main::tempnames{$tmp};
   1.684 +          if (fork() == 0) {
   1.685 +            sleep 5;
   1.686 +            unlink($tmp);
   1.687 +            exit(0);
   1.688 +          }
   1.689 +        }
   1.690 +      } else {
   1.691 +        cleanup();
   1.692 +        exit(1);
   1.693 +      }
   1.694 +    }
   1.695 +  } else {
   1.696 +    InteractiveMode($profile, $symbols, $libs, $total);
   1.697 +  }
   1.698 +
   1.699 +  cleanup();
   1.700 +  exit(0);
   1.701 +}
   1.702 +
   1.703 +##### Entry Point #####
   1.704 +
   1.705 +Main();
   1.706 +
   1.707 +# Temporary code to detect if we're running on a Goobuntu system.
   1.708 +# These systems don't have the right stuff installed for the special
   1.709 +# Readline libraries to work, so as a temporary workaround, we default
   1.710 +# to using the normal stdio code, rather than the fancier readline-based
   1.711 +# code
   1.712 +sub ReadlineMightFail {
   1.713 +  if (-e '/lib/libtermcap.so.2') {
   1.714 +    return 0;  # libtermcap exists, so readline should be okay
   1.715 +  } else {
   1.716 +    return 1;
   1.717 +  }
   1.718 +}
   1.719 +
   1.720 +sub RunGV {
   1.721 +  my $fname = shift;
   1.722 +  my $bg = shift;       # "" or " &" if we should run in background
   1.723 +  if (!system(ShellEscape(@GV, "--version") . " >$dev_null 2>&1")) {
   1.724 +    # Options using double dash are supported by this gv version.
   1.725 +    # Also, turn on noantialias to better handle bug in gv for
   1.726 +    # postscript files with large dimensions.
   1.727 +    # TODO: Maybe we should not pass the --noantialias flag
   1.728 +    # if the gv version is known to work properly without the flag.
   1.729 +    system(ShellEscape(@GV, "--scale=$main::opt_scale", "--noantialias", $fname)
   1.730 +           . $bg);
   1.731 +  } else {
   1.732 +    # Old gv version - only supports options that use single dash.
   1.733 +    print STDERR ShellEscape(@GV, "-scale", $main::opt_scale) . "\n";
   1.734 +    system(ShellEscape(@GV, "-scale", "$main::opt_scale", $fname) . $bg);
   1.735 +  }
   1.736 +}
   1.737 +
   1.738 +sub RunEvince {
   1.739 +  my $fname = shift;
   1.740 +  my $bg = shift;       # "" or " &" if we should run in background
   1.741 +  system(ShellEscape(@EVINCE, $fname) . $bg);
   1.742 +}
   1.743 +
   1.744 +sub RunWeb {
   1.745 +  my $fname = shift;
   1.746 +  print STDERR "Loading web page file:///$fname\n";
   1.747 +
   1.748 +  if (`uname` =~ /Darwin/) {
   1.749 +    # OS X: open will use standard preference for SVG files.
   1.750 +    system("/usr/bin/open", $fname);
   1.751 +    return;
   1.752 +  }
   1.753 +
   1.754 +  # Some kind of Unix; try generic symlinks, then specific browsers.
   1.755 +  # (Stop once we find one.)
   1.756 +  # Works best if the browser is already running.
   1.757 +  my @alt = (
   1.758 +    "/etc/alternatives/gnome-www-browser",
   1.759 +    "/etc/alternatives/x-www-browser",
   1.760 +    "google-chrome",
   1.761 +    "firefox",
   1.762 +  );
   1.763 +  foreach my $b (@alt) {
   1.764 +    if (system($b, $fname) == 0) {
   1.765 +      return;
   1.766 +    }
   1.767 +  }
   1.768 +
   1.769 +  print STDERR "Could not load web browser.\n";
   1.770 +}
   1.771 +
   1.772 +sub RunKcachegrind {
   1.773 +  my $fname = shift;
   1.774 +  my $bg = shift;       # "" or " &" if we should run in background
   1.775 +  print STDERR "Starting '@KCACHEGRIND " . $fname . $bg . "'\n";
   1.776 +  system(ShellEscape(@KCACHEGRIND, $fname) . $bg);
   1.777 +}
   1.778 +
   1.779 +
   1.780 +##### Interactive helper routines #####
   1.781 +
   1.782 +sub InteractiveMode {
   1.783 +  $| = 1;  # Make output unbuffered for interactive mode
   1.784 +  my ($orig_profile, $symbols, $libs, $total) = @_;
   1.785 +
   1.786 +  print STDERR "Welcome to pprof!  For help, type 'help'.\n";
   1.787 +
   1.788 +  # Use ReadLine if it's installed and input comes from a console.
   1.789 +  if ( -t STDIN &&
   1.790 +       !ReadlineMightFail() &&
   1.791 +       defined(eval {require Term::ReadLine}) ) {
   1.792 +    my $term = new Term::ReadLine 'pprof';
   1.793 +    while ( defined ($_ = $term->readline('(pprof) '))) {
   1.794 +      $term->addhistory($_) if /\S/;
   1.795 +      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
   1.796 +        last;    # exit when we get an interactive command to quit
   1.797 +      }
   1.798 +    }
   1.799 +  } else {       # don't have readline
   1.800 +    while (1) {
   1.801 +      print STDERR "(pprof) ";
   1.802 +      $_ = <STDIN>;
   1.803 +      last if ! defined $_ ;
   1.804 +      s/\r//g;         # turn windows-looking lines into unix-looking lines
   1.805 +
   1.806 +      # Save some flags that might be reset by InteractiveCommand()
   1.807 +      my $save_opt_lines = $main::opt_lines;
   1.808 +
   1.809 +      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {
   1.810 +        last;    # exit when we get an interactive command to quit
   1.811 +      }
   1.812 +
   1.813 +      # Restore flags
   1.814 +      $main::opt_lines = $save_opt_lines;
   1.815 +    }
   1.816 +  }
   1.817 +}
   1.818 +
   1.819 +# Takes two args: orig profile, and command to run.
   1.820 +# Returns 1 if we should keep going, or 0 if we were asked to quit
   1.821 +sub InteractiveCommand {
   1.822 +  my($orig_profile, $symbols, $libs, $total, $command) = @_;
   1.823 +  $_ = $command;                # just to make future m//'s easier
   1.824 +  if (!defined($_)) {
   1.825 +    print STDERR "\n";
   1.826 +    return 0;
   1.827 +  }
   1.828 +  if (m/^\s*quit/) {
   1.829 +    return 0;
   1.830 +  }
   1.831 +  if (m/^\s*help/) {
   1.832 +    InteractiveHelpMessage();
   1.833 +    return 1;
   1.834 +  }
   1.835 +  # Clear all the mode options -- mode is controlled by "$command"
   1.836 +  $main::opt_text = 0;
   1.837 +  $main::opt_callgrind = 0;
   1.838 +  $main::opt_disasm = 0;
   1.839 +  $main::opt_list = 0;
   1.840 +  $main::opt_gv = 0;
   1.841 +  $main::opt_evince = 0;
   1.842 +  $main::opt_cum = 0;
   1.843 +
   1.844 +  if (m/^\s*(text|top)(\d*)\s*(.*)/) {
   1.845 +    $main::opt_text = 1;
   1.846 +
   1.847 +    my $line_limit = ($2 ne "") ? int($2) : 10;
   1.848 +
   1.849 +    my $routine;
   1.850 +    my $ignore;
   1.851 +    ($routine, $ignore) = ParseInteractiveArgs($3);
   1.852 +
   1.853 +    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
   1.854 +    my $reduced = ReduceProfile($symbols, $profile);
   1.855 +
   1.856 +    # Get derived profiles
   1.857 +    my $flat = FlatProfile($reduced);
   1.858 +    my $cumulative = CumulativeProfile($reduced);
   1.859 +
   1.860 +    PrintText($symbols, $flat, $cumulative, $line_limit);
   1.861 +    return 1;
   1.862 +  }
   1.863 +  if (m/^\s*callgrind\s*([^ \n]*)/) {
   1.864 +    $main::opt_callgrind = 1;
   1.865 +
   1.866 +    # Get derived profiles
   1.867 +    my $calls = ExtractCalls($symbols, $orig_profile);
   1.868 +    my $filename = $1;
   1.869 +    if ( $1 eq '' ) {
   1.870 +      $filename = TempName($main::next_tmpfile, "callgrind");
   1.871 +    }
   1.872 +    PrintCallgrind($calls, $filename);
   1.873 +    if ( $1 eq '' ) {
   1.874 +      RunKcachegrind($filename, " & ");
   1.875 +      $main::next_tmpfile++;
   1.876 +    }
   1.877 +
   1.878 +    return 1;
   1.879 +  }
   1.880 +  if (m/^\s*(web)?list\s*(.+)/) {
   1.881 +    my $html = (defined($1) && ($1 eq "web"));
   1.882 +    $main::opt_list = 1;
   1.883 +
   1.884 +    my $routine;
   1.885 +    my $ignore;
   1.886 +    ($routine, $ignore) = ParseInteractiveArgs($2);
   1.887 +
   1.888 +    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
   1.889 +    my $reduced = ReduceProfile($symbols, $profile);
   1.890 +
   1.891 +    # Get derived profiles
   1.892 +    my $flat = FlatProfile($reduced);
   1.893 +    my $cumulative = CumulativeProfile($reduced);
   1.894 +
   1.895 +    PrintListing($total, $libs, $flat, $cumulative, $routine, $html);
   1.896 +    return 1;
   1.897 +  }
   1.898 +  if (m/^\s*disasm\s*(.+)/) {
   1.899 +    $main::opt_disasm = 1;
   1.900 +
   1.901 +    my $routine;
   1.902 +    my $ignore;
   1.903 +    ($routine, $ignore) = ParseInteractiveArgs($1);
   1.904 +
   1.905 +    # Process current profile to account for various settings
   1.906 +    my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
   1.907 +    my $reduced = ReduceProfile($symbols, $profile);
   1.908 +
   1.909 +    # Get derived profiles
   1.910 +    my $flat = FlatProfile($reduced);
   1.911 +    my $cumulative = CumulativeProfile($reduced);
   1.912 +
   1.913 +    PrintDisassembly($libs, $flat, $cumulative, $routine);
   1.914 +    return 1;
   1.915 +  }
   1.916 +  if (m/^\s*(gv|web|evince)\s*(.*)/) {
   1.917 +    $main::opt_gv = 0;
   1.918 +    $main::opt_evince = 0;
   1.919 +    $main::opt_web = 0;
   1.920 +    if ($1 eq "gv") {
   1.921 +      $main::opt_gv = 1;
   1.922 +    } elsif ($1 eq "evince") {
   1.923 +      $main::opt_evince = 1;
   1.924 +    } elsif ($1 eq "web") {
   1.925 +      $main::opt_web = 1;
   1.926 +    }
   1.927 +
   1.928 +    my $focus;
   1.929 +    my $ignore;
   1.930 +    ($focus, $ignore) = ParseInteractiveArgs($2);
   1.931 +
   1.932 +    # Process current profile to account for various settings
   1.933 +    my $profile = ProcessProfile($total, $orig_profile, $symbols,
   1.934 +                                 $focus, $ignore);
   1.935 +    my $reduced = ReduceProfile($symbols, $profile);
   1.936 +
   1.937 +    # Get derived profiles
   1.938 +    my $flat = FlatProfile($reduced);
   1.939 +    my $cumulative = CumulativeProfile($reduced);
   1.940 +
   1.941 +    if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {
   1.942 +      if ($main::opt_gv) {
   1.943 +        RunGV(TempName($main::next_tmpfile, "ps"), " &");
   1.944 +      } elsif ($main::opt_evince) {
   1.945 +        RunEvince(TempName($main::next_tmpfile, "pdf"), " &");
   1.946 +      } elsif ($main::opt_web) {
   1.947 +        RunWeb(TempName($main::next_tmpfile, "svg"));
   1.948 +      }
   1.949 +      $main::next_tmpfile++;
   1.950 +    }
   1.951 +    return 1;
   1.952 +  }
   1.953 +  if (m/^\s*$/) {
   1.954 +    return 1;
   1.955 +  }
   1.956 +  print STDERR "Unknown command: try 'help'.\n";
   1.957 +  return 1;
   1.958 +}
   1.959 +
   1.960 +
   1.961 +sub ProcessProfile {
   1.962 +  my $total_count = shift;
   1.963 +  my $orig_profile = shift;
   1.964 +  my $symbols = shift;
   1.965 +  my $focus = shift;
   1.966 +  my $ignore = shift;
   1.967 +
   1.968 +  # Process current profile to account for various settings
   1.969 +  my $profile = $orig_profile;
   1.970 +  printf("Total: %s %s\n", Unparse($total_count), Units());
   1.971 +  if ($focus ne '') {
   1.972 +    $profile = FocusProfile($symbols, $profile, $focus);
   1.973 +    my $focus_count = TotalProfile($profile);
   1.974 +    printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n",
   1.975 +           $focus,
   1.976 +           Unparse($focus_count), Units(),
   1.977 +           Unparse($total_count), ($focus_count*100.0) / $total_count);
   1.978 +  }
   1.979 +  if ($ignore ne '') {
   1.980 +    $profile = IgnoreProfile($symbols, $profile, $ignore);
   1.981 +    my $ignore_count = TotalProfile($profile);
   1.982 +    printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n",
   1.983 +           $ignore,
   1.984 +           Unparse($ignore_count), Units(),
   1.985 +           Unparse($total_count),
   1.986 +           ($ignore_count*100.0) / $total_count);
   1.987 +  }
   1.988 +
   1.989 +  return $profile;
   1.990 +}
   1.991 +
   1.992 +sub InteractiveHelpMessage {
   1.993 +  print STDERR <<ENDOFHELP;
   1.994 +Interactive pprof mode
   1.995 +
   1.996 +Commands:
   1.997 +  gv
   1.998 +  gv [focus] [-ignore1] [-ignore2]
   1.999 +      Show graphical hierarchical display of current profile.  Without
  1.1000 +      any arguments, shows all samples in the profile.  With the optional
  1.1001 +      "focus" argument, restricts the samples shown to just those where
  1.1002 +      the "focus" regular expression matches a routine name on the stack
  1.1003 +      trace.
  1.1004 +
  1.1005 +  web
  1.1006 +  web [focus] [-ignore1] [-ignore2]
  1.1007 +      Like GV, but displays profile in your web browser instead of using
  1.1008 +      Ghostview. Works best if your web browser is already running.
  1.1009 +      To change the browser that gets used:
  1.1010 +      On Linux, set the /etc/alternatives/gnome-www-browser symlink.
  1.1011 +      On OS X, change the Finder association for SVG files.
  1.1012 +
  1.1013 +  list [routine_regexp] [-ignore1] [-ignore2]
  1.1014 +      Show source listing of routines whose names match "routine_regexp"
  1.1015 +
  1.1016 +  weblist [routine_regexp] [-ignore1] [-ignore2]
  1.1017 +     Displays a source listing of routines whose names match "routine_regexp"
  1.1018 +     in a web browser.  You can click on source lines to view the
  1.1019 +     corresponding disassembly.
  1.1020 +
  1.1021 +  top [--cum] [-ignore1] [-ignore2]
  1.1022 +  top20 [--cum] [-ignore1] [-ignore2]
  1.1023 +  top37 [--cum] [-ignore1] [-ignore2]
  1.1024 +      Show top lines ordered by flat profile count, or cumulative count
  1.1025 +      if --cum is specified.  If a number is present after 'top', the
  1.1026 +      top K routines will be shown (defaults to showing the top 10)
  1.1027 +
  1.1028 +  disasm [routine_regexp] [-ignore1] [-ignore2]
  1.1029 +      Show disassembly of routines whose names match "routine_regexp",
  1.1030 +      annotated with sample counts.
  1.1031 +
  1.1032 +  callgrind
  1.1033 +  callgrind [filename]
  1.1034 +      Generates callgrind file. If no filename is given, kcachegrind is called.
  1.1035 +
  1.1036 +  help - This listing
  1.1037 +  quit or ^D - End pprof
  1.1038 +
  1.1039 +For commands that accept optional -ignore tags, samples where any routine in
  1.1040 +the stack trace matches the regular expression in any of the -ignore
  1.1041 +parameters will be ignored.
  1.1042 +
  1.1043 +Further pprof details are available at this location (or one similar):
  1.1044 +
  1.1045 + /usr/doc/gperftools-$PPROF_VERSION/cpu_profiler.html
  1.1046 + /usr/doc/gperftools-$PPROF_VERSION/heap_profiler.html
  1.1047 +
  1.1048 +ENDOFHELP
  1.1049 +}
  1.1050 +sub ParseInteractiveArgs {
  1.1051 +  my $args = shift;
  1.1052 +  my $focus = "";
  1.1053 +  my $ignore = "";
  1.1054 +  my @x = split(/ +/, $args);
  1.1055 +  foreach $a (@x) {
  1.1056 +    if ($a =~ m/^(--|-)lines$/) {
  1.1057 +      $main::opt_lines = 1;
  1.1058 +    } elsif ($a =~ m/^(--|-)cum$/) {
  1.1059 +      $main::opt_cum = 1;
  1.1060 +    } elsif ($a =~ m/^-(.*)/) {
  1.1061 +      $ignore .= (($ignore ne "") ? "|" : "" ) . $1;
  1.1062 +    } else {
  1.1063 +      $focus .= (($focus ne "") ? "|" : "" ) . $a;
  1.1064 +    }
  1.1065 +  }
  1.1066 +  if ($ignore ne "") {
  1.1067 +    print STDERR "Ignoring samples in call stacks that match '$ignore'\n";
  1.1068 +  }
  1.1069 +  return ($focus, $ignore);
  1.1070 +}
  1.1071 +
  1.1072 +##### Output code #####
  1.1073 +
  1.1074 +sub TempName {
  1.1075 +  my $fnum = shift;
  1.1076 +  my $ext = shift;
  1.1077 +  my $file = "$main::tmpfile_ps.$fnum.$ext";
  1.1078 +  $main::tempnames{$file} = 1;
  1.1079 +  return $file;
  1.1080 +}
  1.1081 +
  1.1082 +# Print profile data in packed binary format (64-bit) to standard out
  1.1083 +sub PrintProfileData {
  1.1084 +  my $profile = shift;
  1.1085 +
  1.1086 +  # print header (64-bit style)
  1.1087 +  # (zero) (header-size) (version) (sample-period) (zero)
  1.1088 +  print pack('L*', 0, 0, 3, 0, 0, 0, 1, 0, 0, 0);
  1.1089 +
  1.1090 +  foreach my $k (keys(%{$profile})) {
  1.1091 +    my $count = $profile->{$k};
  1.1092 +    my @addrs = split(/\n/, $k);
  1.1093 +    if ($#addrs >= 0) {
  1.1094 +      my $depth = $#addrs + 1;
  1.1095 +      # int(foo / 2**32) is the only reliable way to get rid of bottom
  1.1096 +      # 32 bits on both 32- and 64-bit systems.
  1.1097 +      print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));
  1.1098 +      print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));
  1.1099 +
  1.1100 +      foreach my $full_addr (@addrs) {
  1.1101 +        my $addr = $full_addr;
  1.1102 +        $addr =~ s/0x0*//;  # strip off leading 0x, zeroes
  1.1103 +        if (length($addr) > 16) {
  1.1104 +          print STDERR "Invalid address in profile: $full_addr\n";
  1.1105 +          next;
  1.1106 +        }
  1.1107 +        my $low_addr = substr($addr, -8);       # get last 8 hex chars
  1.1108 +        my $high_addr = substr($addr, -16, 8);  # get up to 8 more hex chars
  1.1109 +        print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));
  1.1110 +      }
  1.1111 +    }
  1.1112 +  }
  1.1113 +}
  1.1114 +
  1.1115 +# Print symbols and profile data
  1.1116 +sub PrintSymbolizedProfile {
  1.1117 +  my $symbols = shift;
  1.1118 +  my $profile = shift;
  1.1119 +  my $prog = shift;
  1.1120 +
  1.1121 +  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.1122 +  my $symbol_marker = $&;
  1.1123 +
  1.1124 +  print '--- ', $symbol_marker, "\n";
  1.1125 +  if (defined($prog)) {
  1.1126 +    print 'binary=', $prog, "\n";
  1.1127 +  }
  1.1128 +  while (my ($pc, $name) = each(%{$symbols})) {
  1.1129 +    my $sep = ' ';
  1.1130 +    print '0x', $pc;
  1.1131 +    # We have a list of function names, which include the inlined
  1.1132 +    # calls.  They are separated (and terminated) by --, which is
  1.1133 +    # illegal in function names.
  1.1134 +    for (my $j = 2; $j <= $#{$name}; $j += 3) {
  1.1135 +      print $sep, $name->[$j];
  1.1136 +      $sep = '--';
  1.1137 +    }
  1.1138 +    print "\n";
  1.1139 +  }
  1.1140 +  print '---', "\n";
  1.1141 +
  1.1142 +  $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.1143 +  my $profile_marker = $&;
  1.1144 +  print '--- ', $profile_marker, "\n";
  1.1145 +  if (defined($main::collected_profile)) {
  1.1146 +    # if used with remote fetch, simply dump the collected profile to output.
  1.1147 +    open(SRC, "<$main::collected_profile");
  1.1148 +    while (<SRC>) {
  1.1149 +      print $_;
  1.1150 +    }
  1.1151 +    close(SRC);
  1.1152 +  } else {
  1.1153 +    # dump a cpu-format profile to standard out
  1.1154 +    PrintProfileData($profile);
  1.1155 +  }
  1.1156 +}
  1.1157 +
  1.1158 +# Print text output
  1.1159 +sub PrintText {
  1.1160 +  my $symbols = shift;
  1.1161 +  my $flat = shift;
  1.1162 +  my $cumulative = shift;
  1.1163 +  my $line_limit = shift;
  1.1164 +
  1.1165 +  my $total = TotalProfile($flat);
  1.1166 +
  1.1167 +  # Which profile to sort by?
  1.1168 +  my $s = $main::opt_cum ? $cumulative : $flat;
  1.1169 +
  1.1170 +  my $running_sum = 0;
  1.1171 +  my $lines = 0;
  1.1172 +  foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }
  1.1173 +                 keys(%{$cumulative})) {
  1.1174 +    my $f = GetEntry($flat, $k);
  1.1175 +    my $c = GetEntry($cumulative, $k);
  1.1176 +    $running_sum += $f;
  1.1177 +
  1.1178 +    my $sym = $k;
  1.1179 +    if (exists($symbols->{$k})) {
  1.1180 +      $sym = $symbols->{$k}->[0] . " " . $symbols->{$k}->[1];
  1.1181 +      if ($main::opt_addresses) {
  1.1182 +        $sym = $k . " " . $sym;
  1.1183 +      }
  1.1184 +    }
  1.1185 +
  1.1186 +    if ($f != 0 || $c != 0) {
  1.1187 +      printf("%8s %6s %6s %8s %6s %s\n",
  1.1188 +             Unparse($f),
  1.1189 +             Percent($f, $total),
  1.1190 +             Percent($running_sum, $total),
  1.1191 +             Unparse($c),
  1.1192 +             Percent($c, $total),
  1.1193 +             $sym);
  1.1194 +    }
  1.1195 +    $lines++;
  1.1196 +    last if ($line_limit >= 0 && $lines >= $line_limit);
  1.1197 +  }
  1.1198 +}
  1.1199 +
  1.1200 +# Callgrind format has a compression for repeated function and file
  1.1201 +# names.  You show the name the first time, and just use its number
  1.1202 +# subsequently.  This can cut down the file to about a third or a
  1.1203 +# quarter of its uncompressed size.  $key and $val are the key/value
  1.1204 +# pair that would normally be printed by callgrind; $map is a map from
  1.1205 +# value to number.
  1.1206 +sub CompressedCGName {
  1.1207 +  my($key, $val, $map) = @_;
  1.1208 +  my $idx = $map->{$val};
  1.1209 +  # For very short keys, providing an index hurts rather than helps.
  1.1210 +  if (length($val) <= 3) {
  1.1211 +    return "$key=$val\n";
  1.1212 +  } elsif (defined($idx)) {
  1.1213 +    return "$key=($idx)\n";
  1.1214 +  } else {
  1.1215 +    # scalar(keys $map) gives the number of items in the map.
  1.1216 +    $idx = scalar(keys(%{$map})) + 1;
  1.1217 +    $map->{$val} = $idx;
  1.1218 +    return "$key=($idx) $val\n";
  1.1219 +  }
  1.1220 +}
  1.1221 +
  1.1222 +# Print the call graph in a way that's suiteable for callgrind.
  1.1223 +sub PrintCallgrind {
  1.1224 +  my $calls = shift;
  1.1225 +  my $filename;
  1.1226 +  my %filename_to_index_map;
  1.1227 +  my %fnname_to_index_map;
  1.1228 +
  1.1229 +  if ($main::opt_interactive) {
  1.1230 +    $filename = shift;
  1.1231 +    print STDERR "Writing callgrind file to '$filename'.\n"
  1.1232 +  } else {
  1.1233 +    $filename = "&STDOUT";
  1.1234 +  }
  1.1235 +  open(CG, ">$filename");
  1.1236 +  printf CG ("events: Hits\n\n");
  1.1237 +  foreach my $call ( map { $_->[0] }
  1.1238 +                     sort { $a->[1] cmp $b ->[1] ||
  1.1239 +                            $a->[2] <=> $b->[2] }
  1.1240 +                     map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
  1.1241 +                           [$_, $1, $2] }
  1.1242 +                     keys %$calls ) {
  1.1243 +    my $count = int($calls->{$call});
  1.1244 +    $call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/;
  1.1245 +    my ( $caller_file, $caller_line, $caller_function,
  1.1246 +         $callee_file, $callee_line, $callee_function ) =
  1.1247 +       ( $1, $2, $3, $5, $6, $7 );
  1.1248 +
  1.1249 +    # TODO(csilvers): for better compression, collect all the
  1.1250 +    # caller/callee_files and functions first, before printing
  1.1251 +    # anything, and only compress those referenced more than once.
  1.1252 +    printf CG CompressedCGName("fl", $caller_file, \%filename_to_index_map);
  1.1253 +    printf CG CompressedCGName("fn", $caller_function, \%fnname_to_index_map);
  1.1254 +    if (defined $6) {
  1.1255 +      printf CG CompressedCGName("cfl", $callee_file, \%filename_to_index_map);
  1.1256 +      printf CG CompressedCGName("cfn", $callee_function, \%fnname_to_index_map);
  1.1257 +      printf CG ("calls=$count $callee_line\n");
  1.1258 +    }
  1.1259 +    printf CG ("$caller_line $count\n\n");
  1.1260 +  }
  1.1261 +}
  1.1262 +
  1.1263 +# Print disassembly for all all routines that match $main::opt_disasm
  1.1264 +sub PrintDisassembly {
  1.1265 +  my $libs = shift;
  1.1266 +  my $flat = shift;
  1.1267 +  my $cumulative = shift;
  1.1268 +  my $disasm_opts = shift;
  1.1269 +
  1.1270 +  my $total = TotalProfile($flat);
  1.1271 +
  1.1272 +  foreach my $lib (@{$libs}) {
  1.1273 +    my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);
  1.1274 +    my $offset = AddressSub($lib->[1], $lib->[3]);
  1.1275 +    foreach my $routine (sort ByName keys(%{$symbol_table})) {
  1.1276 +      my $start_addr = $symbol_table->{$routine}->[0];
  1.1277 +      my $end_addr = $symbol_table->{$routine}->[1];
  1.1278 +      # See if there are any samples in this routine
  1.1279 +      my $length = hex(AddressSub($end_addr, $start_addr));
  1.1280 +      my $addr = AddressAdd($start_addr, $offset);
  1.1281 +      for (my $i = 0; $i < $length; $i++) {
  1.1282 +        if (defined($cumulative->{$addr})) {
  1.1283 +          PrintDisassembledFunction($lib->[0], $offset,
  1.1284 +                                    $routine, $flat, $cumulative,
  1.1285 +                                    $start_addr, $end_addr, $total);
  1.1286 +          last;
  1.1287 +        }
  1.1288 +        $addr = AddressInc($addr);
  1.1289 +      }
  1.1290 +    }
  1.1291 +  }
  1.1292 +}
  1.1293 +
  1.1294 +# Return reference to array of tuples of the form:
  1.1295 +#       [start_address, filename, linenumber, instruction, limit_address]
  1.1296 +# E.g.,
  1.1297 +#       ["0x806c43d", "/foo/bar.cc", 131, "ret", "0x806c440"]
  1.1298 +sub Disassemble {
  1.1299 +  my $prog = shift;
  1.1300 +  my $offset = shift;
  1.1301 +  my $start_addr = shift;
  1.1302 +  my $end_addr = shift;
  1.1303 +
  1.1304 +  my $objdump = $obj_tool_map{"objdump"};
  1.1305 +  my $cmd = ShellEscape($objdump, "-C", "-d", "-l", "--no-show-raw-insn",
  1.1306 +                        "--start-address=0x$start_addr",
  1.1307 +                        "--stop-address=0x$end_addr", $prog);
  1.1308 +  open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
  1.1309 +  my @result = ();
  1.1310 +  my $filename = "";
  1.1311 +  my $linenumber = -1;
  1.1312 +  my $last = ["", "", "", ""];
  1.1313 +  while (<OBJDUMP>) {
  1.1314 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.1315 +    chop;
  1.1316 +    if (m|\s*([^:\s]+):(\d+)\s*$|) {
  1.1317 +      # Location line of the form:
  1.1318 +      #   <filename>:<linenumber>
  1.1319 +      $filename = $1;
  1.1320 +      $linenumber = $2;
  1.1321 +    } elsif (m/^ +([0-9a-f]+):\s*(.*)/) {
  1.1322 +      # Disassembly line -- zero-extend address to full length
  1.1323 +      my $addr = HexExtend($1);
  1.1324 +      my $k = AddressAdd($addr, $offset);
  1.1325 +      $last->[4] = $k;   # Store ending address for previous instruction
  1.1326 +      $last = [$k, $filename, $linenumber, $2, $end_addr];
  1.1327 +      push(@result, $last);
  1.1328 +    }
  1.1329 +  }
  1.1330 +  close(OBJDUMP);
  1.1331 +  return @result;
  1.1332 +}
  1.1333 +
  1.1334 +# The input file should contain lines of the form /proc/maps-like
  1.1335 +# output (same format as expected from the profiles) or that looks
  1.1336 +# like hex addresses (like "0xDEADBEEF").  We will parse all
  1.1337 +# /proc/maps output, and for all the hex addresses, we will output
  1.1338 +# "short" symbol names, one per line, in the same order as the input.
  1.1339 +sub PrintSymbols {
  1.1340 +  my $maps_and_symbols_file = shift;
  1.1341 +
  1.1342 +  # ParseLibraries expects pcs to be in a set.  Fine by us...
  1.1343 +  my @pclist = ();   # pcs in sorted order
  1.1344 +  my $pcs = {};
  1.1345 +  my $map = "";
  1.1346 +  foreach my $line (<$maps_and_symbols_file>) {
  1.1347 +    $line =~ s/\r//g;    # turn windows-looking lines into unix-looking lines
  1.1348 +    if ($line =~ /\b(0x[0-9a-f]+)\b/i) {
  1.1349 +      push(@pclist, HexExtend($1));
  1.1350 +      $pcs->{$pclist[-1]} = 1;
  1.1351 +    } else {
  1.1352 +      $map .= $line;
  1.1353 +    }
  1.1354 +  }
  1.1355 +
  1.1356 +  my $libs = ParseLibraries($main::prog, $map, $pcs);
  1.1357 +  my $symbols = ExtractSymbols($libs, $pcs);
  1.1358 +
  1.1359 +  foreach my $pc (@pclist) {
  1.1360 +    # ->[0] is the shortname, ->[2] is the full name
  1.1361 +    print(($symbols->{$pc}->[0] || "??") . "\n");
  1.1362 +  }
  1.1363 +}
  1.1364 +
  1.1365 +
  1.1366 +# For sorting functions by name
  1.1367 +sub ByName {
  1.1368 +  return ShortFunctionName($a) cmp ShortFunctionName($b);
  1.1369 +}
  1.1370 +
  1.1371 +# Print source-listing for all all routines that match $list_opts
  1.1372 +sub PrintListing {
  1.1373 +  my $total = shift;
  1.1374 +  my $libs = shift;
  1.1375 +  my $flat = shift;
  1.1376 +  my $cumulative = shift;
  1.1377 +  my $list_opts = shift;
  1.1378 +  my $html = shift;
  1.1379 +
  1.1380 +  my $output = \*STDOUT;
  1.1381 +  my $fname = "";
  1.1382 +
  1.1383 +  if ($html) {
  1.1384 +    # Arrange to write the output to a temporary file
  1.1385 +    $fname = TempName($main::next_tmpfile, "html");
  1.1386 +    $main::next_tmpfile++;
  1.1387 +    if (!open(TEMP, ">$fname")) {
  1.1388 +      print STDERR "$fname: $!\n";
  1.1389 +      return;
  1.1390 +    }
  1.1391 +    $output = \*TEMP;
  1.1392 +    print $output HtmlListingHeader();
  1.1393 +    printf $output ("<div class=\"legend\">%s<br>Total: %s %s</div>\n",
  1.1394 +                    $main::prog, Unparse($total), Units());
  1.1395 +  }
  1.1396 +
  1.1397 +  my $listed = 0;
  1.1398 +  foreach my $lib (@{$libs}) {
  1.1399 +    my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);
  1.1400 +    my $offset = AddressSub($lib->[1], $lib->[3]);
  1.1401 +    foreach my $routine (sort ByName keys(%{$symbol_table})) {
  1.1402 +      # Print if there are any samples in this routine
  1.1403 +      my $start_addr = $symbol_table->{$routine}->[0];
  1.1404 +      my $end_addr = $symbol_table->{$routine}->[1];
  1.1405 +      my $length = hex(AddressSub($end_addr, $start_addr));
  1.1406 +      my $addr = AddressAdd($start_addr, $offset);
  1.1407 +      for (my $i = 0; $i < $length; $i++) {
  1.1408 +        if (defined($cumulative->{$addr})) {
  1.1409 +          $listed += PrintSource(
  1.1410 +            $lib->[0], $offset,
  1.1411 +            $routine, $flat, $cumulative,
  1.1412 +            $start_addr, $end_addr,
  1.1413 +            $html,
  1.1414 +            $output);
  1.1415 +          last;
  1.1416 +        }
  1.1417 +        $addr = AddressInc($addr);
  1.1418 +      }
  1.1419 +    }
  1.1420 +  }
  1.1421 +
  1.1422 +  if ($html) {
  1.1423 +    if ($listed > 0) {
  1.1424 +      print $output HtmlListingFooter();
  1.1425 +      close($output);
  1.1426 +      RunWeb($fname);
  1.1427 +    } else {
  1.1428 +      close($output);
  1.1429 +      unlink($fname);
  1.1430 +    }
  1.1431 +  }
  1.1432 +}
  1.1433 +
  1.1434 +sub HtmlListingHeader {
  1.1435 +  return <<'EOF';
  1.1436 +<DOCTYPE html>
  1.1437 +<html>
  1.1438 +<head>
  1.1439 +<title>Pprof listing</title>
  1.1440 +<style type="text/css">
  1.1441 +body {
  1.1442 +  font-family: sans-serif;
  1.1443 +}
  1.1444 +h1 {
  1.1445 +  font-size: 1.5em;
  1.1446 +  margin-bottom: 4px;
  1.1447 +}
  1.1448 +.legend {
  1.1449 +  font-size: 1.25em;
  1.1450 +}
  1.1451 +.line {
  1.1452 +  color: #aaaaaa;
  1.1453 +}
  1.1454 +.nop {
  1.1455 +  color: #aaaaaa;
  1.1456 +}
  1.1457 +.unimportant {
  1.1458 +  color: #cccccc;
  1.1459 +}
  1.1460 +.disasmloc {
  1.1461 +  color: #000000;
  1.1462 +}
  1.1463 +.deadsrc {
  1.1464 +  cursor: pointer;
  1.1465 +}
  1.1466 +.deadsrc:hover {
  1.1467 +  background-color: #eeeeee;
  1.1468 +}
  1.1469 +.livesrc {
  1.1470 +  color: #0000ff;
  1.1471 +  cursor: pointer;
  1.1472 +}
  1.1473 +.livesrc:hover {
  1.1474 +  background-color: #eeeeee;
  1.1475 +}
  1.1476 +.asm {
  1.1477 +  color: #008800;
  1.1478 +  display: none;
  1.1479 +}
  1.1480 +</style>
  1.1481 +<script type="text/javascript">
  1.1482 +function pprof_toggle_asm(e) {
  1.1483 +  var target;
  1.1484 +  if (!e) e = window.event;
  1.1485 +  if (e.target) target = e.target;
  1.1486 +  else if (e.srcElement) target = e.srcElement;
  1.1487 +
  1.1488 +  if (target) {
  1.1489 +    var asm = target.nextSibling;
  1.1490 +    if (asm && asm.className == "asm") {
  1.1491 +      asm.style.display = (asm.style.display == "block" ? "" : "block");
  1.1492 +      e.preventDefault();
  1.1493 +      return false;
  1.1494 +    }
  1.1495 +  }
  1.1496 +}
  1.1497 +</script>
  1.1498 +</head>
  1.1499 +<body>
  1.1500 +EOF
  1.1501 +}
  1.1502 +
  1.1503 +sub HtmlListingFooter {
  1.1504 +  return <<'EOF';
  1.1505 +</body>
  1.1506 +</html>
  1.1507 +EOF
  1.1508 +}
  1.1509 +
  1.1510 +sub HtmlEscape {
  1.1511 +  my $text = shift;
  1.1512 +  $text =~ s/&/&amp;/g;
  1.1513 +  $text =~ s/</&lt;/g;
  1.1514 +  $text =~ s/>/&gt;/g;
  1.1515 +  return $text;
  1.1516 +}
  1.1517 +
  1.1518 +# Returns the indentation of the line, if it has any non-whitespace
  1.1519 +# characters.  Otherwise, returns -1.
  1.1520 +sub Indentation {
  1.1521 +  my $line = shift;
  1.1522 +  if (m/^(\s*)\S/) {
  1.1523 +    return length($1);
  1.1524 +  } else {
  1.1525 +    return -1;
  1.1526 +  }
  1.1527 +}
  1.1528 +
  1.1529 +# If the symbol table contains inlining info, Disassemble() may tag an
  1.1530 +# instruction with a location inside an inlined function.  But for
  1.1531 +# source listings, we prefer to use the location in the function we
  1.1532 +# are listing.  So use MapToSymbols() to fetch full location
  1.1533 +# information for each instruction and then pick out the first
  1.1534 +# location from a location list (location list contains callers before
  1.1535 +# callees in case of inlining).
  1.1536 +#
  1.1537 +# After this routine has run, each entry in $instructions contains:
  1.1538 +#   [0] start address
  1.1539 +#   [1] filename for function we are listing
  1.1540 +#   [2] line number for function we are listing
  1.1541 +#   [3] disassembly
  1.1542 +#   [4] limit address
  1.1543 +#   [5] most specific filename (may be different from [1] due to inlining)
  1.1544 +#   [6] most specific line number (may be different from [2] due to inlining)
  1.1545 +sub GetTopLevelLineNumbers {
  1.1546 +  my ($lib, $offset, $instructions) = @_;
  1.1547 +  my $pcs = [];
  1.1548 +  for (my $i = 0; $i <= $#{$instructions}; $i++) {
  1.1549 +    push(@{$pcs}, $instructions->[$i]->[0]);
  1.1550 +  }
  1.1551 +  my $symbols = {};
  1.1552 +  MapToSymbols($lib, $offset, $pcs, $symbols);
  1.1553 +  for (my $i = 0; $i <= $#{$instructions}; $i++) {
  1.1554 +    my $e = $instructions->[$i];
  1.1555 +    push(@{$e}, $e->[1]);
  1.1556 +    push(@{$e}, $e->[2]);
  1.1557 +    my $addr = $e->[0];
  1.1558 +    my $sym = $symbols->{$addr};
  1.1559 +    if (defined($sym)) {
  1.1560 +      if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\d+)$/) {
  1.1561 +        $e->[1] = $1;  # File name
  1.1562 +        $e->[2] = $2;  # Line number
  1.1563 +      }
  1.1564 +    }
  1.1565 +  }
  1.1566 +}
  1.1567 +
  1.1568 +# Print source-listing for one routine
  1.1569 +sub PrintSource {
  1.1570 +  my $prog = shift;
  1.1571 +  my $offset = shift;
  1.1572 +  my $routine = shift;
  1.1573 +  my $flat = shift;
  1.1574 +  my $cumulative = shift;
  1.1575 +  my $start_addr = shift;
  1.1576 +  my $end_addr = shift;
  1.1577 +  my $html = shift;
  1.1578 +  my $output = shift;
  1.1579 +
  1.1580 +  # Disassemble all instructions (just to get line numbers)
  1.1581 +  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
  1.1582 +  GetTopLevelLineNumbers($prog, $offset, \@instructions);
  1.1583 +
  1.1584 +  # Hack 1: assume that the first source file encountered in the
  1.1585 +  # disassembly contains the routine
  1.1586 +  my $filename = undef;
  1.1587 +  for (my $i = 0; $i <= $#instructions; $i++) {
  1.1588 +    if ($instructions[$i]->[2] >= 0) {
  1.1589 +      $filename = $instructions[$i]->[1];
  1.1590 +      last;
  1.1591 +    }
  1.1592 +  }
  1.1593 +  if (!defined($filename)) {
  1.1594 +    print STDERR "no filename found in $routine\n";
  1.1595 +    return 0;
  1.1596 +  }
  1.1597 +
  1.1598 +  # Hack 2: assume that the largest line number from $filename is the
  1.1599 +  # end of the procedure.  This is typically safe since if P1 contains
  1.1600 +  # an inlined call to P2, then P2 usually occurs earlier in the
  1.1601 +  # source file.  If this does not work, we might have to compute a
  1.1602 +  # density profile or just print all regions we find.
  1.1603 +  my $lastline = 0;
  1.1604 +  for (my $i = 0; $i <= $#instructions; $i++) {
  1.1605 +    my $f = $instructions[$i]->[1];
  1.1606 +    my $l = $instructions[$i]->[2];
  1.1607 +    if (($f eq $filename) && ($l > $lastline)) {
  1.1608 +      $lastline = $l;
  1.1609 +    }
  1.1610 +  }
  1.1611 +
  1.1612 +  # Hack 3: assume the first source location from "filename" is the start of
  1.1613 +  # the source code.
  1.1614 +  my $firstline = 1;
  1.1615 +  for (my $i = 0; $i <= $#instructions; $i++) {
  1.1616 +    if ($instructions[$i]->[1] eq $filename) {
  1.1617 +      $firstline = $instructions[$i]->[2];
  1.1618 +      last;
  1.1619 +    }
  1.1620 +  }
  1.1621 +
  1.1622 +  # Hack 4: Extend last line forward until its indentation is less than
  1.1623 +  # the indentation we saw on $firstline
  1.1624 +  my $oldlastline = $lastline;
  1.1625 +  {
  1.1626 +    if (!open(FILE, "<$filename")) {
  1.1627 +      print STDERR "$filename: $!\n";
  1.1628 +      return 0;
  1.1629 +    }
  1.1630 +    my $l = 0;
  1.1631 +    my $first_indentation = -1;
  1.1632 +    while (<FILE>) {
  1.1633 +      s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.1634 +      $l++;
  1.1635 +      my $indent = Indentation($_);
  1.1636 +      if ($l >= $firstline) {
  1.1637 +        if ($first_indentation < 0 && $indent >= 0) {
  1.1638 +          $first_indentation = $indent;
  1.1639 +          last if ($first_indentation == 0);
  1.1640 +        }
  1.1641 +      }
  1.1642 +      if ($l >= $lastline && $indent >= 0) {
  1.1643 +        if ($indent >= $first_indentation) {
  1.1644 +          $lastline = $l+1;
  1.1645 +        } else {
  1.1646 +          last;
  1.1647 +        }
  1.1648 +      }
  1.1649 +    }
  1.1650 +    close(FILE);
  1.1651 +  }
  1.1652 +
  1.1653 +  # Assign all samples to the range $firstline,$lastline,
  1.1654 +  # Hack 4: If an instruction does not occur in the range, its samples
  1.1655 +  # are moved to the next instruction that occurs in the range.
  1.1656 +  my $samples1 = {};        # Map from line number to flat count
  1.1657 +  my $samples2 = {};        # Map from line number to cumulative count
  1.1658 +  my $running1 = 0;         # Unassigned flat counts
  1.1659 +  my $running2 = 0;         # Unassigned cumulative counts
  1.1660 +  my $total1 = 0;           # Total flat counts
  1.1661 +  my $total2 = 0;           # Total cumulative counts
  1.1662 +  my %disasm = ();          # Map from line number to disassembly
  1.1663 +  my $running_disasm = "";  # Unassigned disassembly
  1.1664 +  my $skip_marker = "---\n";
  1.1665 +  if ($html) {
  1.1666 +    $skip_marker = "";
  1.1667 +    for (my $l = $firstline; $l <= $lastline; $l++) {
  1.1668 +      $disasm{$l} = "";
  1.1669 +    }
  1.1670 +  }
  1.1671 +  my $last_dis_filename = '';
  1.1672 +  my $last_dis_linenum = -1;
  1.1673 +  my $last_touched_line = -1;  # To detect gaps in disassembly for a line
  1.1674 +  foreach my $e (@instructions) {
  1.1675 +    # Add up counts for all address that fall inside this instruction
  1.1676 +    my $c1 = 0;
  1.1677 +    my $c2 = 0;
  1.1678 +    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
  1.1679 +      $c1 += GetEntry($flat, $a);
  1.1680 +      $c2 += GetEntry($cumulative, $a);
  1.1681 +    }
  1.1682 +
  1.1683 +    if ($html) {
  1.1684 +      my $dis = sprintf("      %6s %6s \t\t%8s: %s ",
  1.1685 +                        HtmlPrintNumber($c1),
  1.1686 +                        HtmlPrintNumber($c2),
  1.1687 +                        UnparseAddress($offset, $e->[0]),
  1.1688 +                        CleanDisassembly($e->[3]));
  1.1689 +      
  1.1690 +      # Append the most specific source line associated with this instruction
  1.1691 +      if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };
  1.1692 +      $dis = HtmlEscape($dis);
  1.1693 +      my $f = $e->[5];
  1.1694 +      my $l = $e->[6];
  1.1695 +      if ($f ne $last_dis_filename) {
  1.1696 +        $dis .= sprintf("<span class=disasmloc>%s:%d</span>", 
  1.1697 +                        HtmlEscape(CleanFileName($f)), $l);
  1.1698 +      } elsif ($l ne $last_dis_linenum) {
  1.1699 +        # De-emphasize the unchanged file name portion
  1.1700 +        $dis .= sprintf("<span class=unimportant>%s</span>" .
  1.1701 +                        "<span class=disasmloc>:%d</span>", 
  1.1702 +                        HtmlEscape(CleanFileName($f)), $l);
  1.1703 +      } else {
  1.1704 +        # De-emphasize the entire location
  1.1705 +        $dis .= sprintf("<span class=unimportant>%s:%d</span>", 
  1.1706 +                        HtmlEscape(CleanFileName($f)), $l);
  1.1707 +      }
  1.1708 +      $last_dis_filename = $f;
  1.1709 +      $last_dis_linenum = $l;
  1.1710 +      $running_disasm .= $dis;
  1.1711 +      $running_disasm .= "\n";
  1.1712 +    }
  1.1713 +
  1.1714 +    $running1 += $c1;
  1.1715 +    $running2 += $c2;
  1.1716 +    $total1 += $c1;
  1.1717 +    $total2 += $c2;
  1.1718 +    my $file = $e->[1];
  1.1719 +    my $line = $e->[2];
  1.1720 +    if (($file eq $filename) &&
  1.1721 +        ($line >= $firstline) &&
  1.1722 +        ($line <= $lastline)) {
  1.1723 +      # Assign all accumulated samples to this line
  1.1724 +      AddEntry($samples1, $line, $running1);
  1.1725 +      AddEntry($samples2, $line, $running2);
  1.1726 +      $running1 = 0;
  1.1727 +      $running2 = 0;
  1.1728 +      if ($html) {
  1.1729 +        if ($line != $last_touched_line && $disasm{$line} ne '') {
  1.1730 +          $disasm{$line} .= "\n";
  1.1731 +        }
  1.1732 +        $disasm{$line} .= $running_disasm;
  1.1733 +        $running_disasm = '';
  1.1734 +        $last_touched_line = $line;
  1.1735 +      }
  1.1736 +    }
  1.1737 +  }
  1.1738 +
  1.1739 +  # Assign any leftover samples to $lastline
  1.1740 +  AddEntry($samples1, $lastline, $running1);
  1.1741 +  AddEntry($samples2, $lastline, $running2);
  1.1742 +  if ($html) {
  1.1743 +    if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {
  1.1744 +      $disasm{$lastline} .= "\n";
  1.1745 +    }
  1.1746 +    $disasm{$lastline} .= $running_disasm;
  1.1747 +  }
  1.1748 +
  1.1749 +  if ($html) {
  1.1750 +    printf $output (
  1.1751 +      "<h1>%s</h1>%s\n<pre onClick=\"pprof_toggle_asm()\">\n" .
  1.1752 +      "Total:%6s %6s (flat / cumulative %s)\n",
  1.1753 +      HtmlEscape(ShortFunctionName($routine)),
  1.1754 +      HtmlEscape(CleanFileName($filename)),
  1.1755 +      Unparse($total1),
  1.1756 +      Unparse($total2),
  1.1757 +      Units());
  1.1758 +  } else {
  1.1759 +    printf $output (
  1.1760 +      "ROUTINE ====================== %s in %s\n" .
  1.1761 +      "%6s %6s Total %s (flat / cumulative)\n",
  1.1762 +      ShortFunctionName($routine),
  1.1763 +      CleanFileName($filename),
  1.1764 +      Unparse($total1),
  1.1765 +      Unparse($total2),
  1.1766 +      Units());
  1.1767 +  }
  1.1768 +  if (!open(FILE, "<$filename")) {
  1.1769 +    print STDERR "$filename: $!\n";
  1.1770 +    return 0;
  1.1771 +  }
  1.1772 +  my $l = 0;
  1.1773 +  while (<FILE>) {
  1.1774 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.1775 +    $l++;
  1.1776 +    if ($l >= $firstline - 5 &&
  1.1777 +        (($l <= $oldlastline + 5) || ($l <= $lastline))) {
  1.1778 +      chop;
  1.1779 +      my $text = $_;
  1.1780 +      if ($l == $firstline) { print $output $skip_marker; }
  1.1781 +      my $n1 = GetEntry($samples1, $l);
  1.1782 +      my $n2 = GetEntry($samples2, $l);
  1.1783 +      if ($html) {
  1.1784 +        # Emit a span that has one of the following classes:
  1.1785 +        #    livesrc -- has samples
  1.1786 +        #    deadsrc -- has disassembly, but with no samples
  1.1787 +        #    nop     -- has no matching disasembly
  1.1788 +        # Also emit an optional span containing disassembly.
  1.1789 +        my $dis = $disasm{$l};
  1.1790 +        my $asm = "";
  1.1791 +        if (defined($dis) && $dis ne '') {
  1.1792 +          $asm = "<span class=\"asm\">" . $dis . "</span>";
  1.1793 +        }
  1.1794 +        my $source_class = (($n1 + $n2 > 0) 
  1.1795 +                            ? "livesrc" 
  1.1796 +                            : (($asm ne "") ? "deadsrc" : "nop"));
  1.1797 +        printf $output (
  1.1798 +          "<span class=\"line\">%5d</span> " .
  1.1799 +          "<span class=\"%s\">%6s %6s %s</span>%s\n",
  1.1800 +          $l, $source_class,
  1.1801 +          HtmlPrintNumber($n1),
  1.1802 +          HtmlPrintNumber($n2),
  1.1803 +          HtmlEscape($text),
  1.1804 +          $asm);
  1.1805 +      } else {
  1.1806 +        printf $output(
  1.1807 +          "%6s %6s %4d: %s\n",
  1.1808 +          UnparseAlt($n1),
  1.1809 +          UnparseAlt($n2),
  1.1810 +          $l,
  1.1811 +          $text);
  1.1812 +      }
  1.1813 +      if ($l == $lastline)  { print $output $skip_marker; }
  1.1814 +    };
  1.1815 +  }
  1.1816 +  close(FILE);
  1.1817 +  if ($html) {
  1.1818 +    print $output "</pre>\n";
  1.1819 +  }
  1.1820 +  return 1;
  1.1821 +}
  1.1822 +
  1.1823 +# Return the source line for the specified file/linenumber.
  1.1824 +# Returns undef if not found.
  1.1825 +sub SourceLine {
  1.1826 +  my $file = shift;
  1.1827 +  my $line = shift;
  1.1828 +
  1.1829 +  # Look in cache
  1.1830 +  if (!defined($main::source_cache{$file})) {
  1.1831 +    if (100 < scalar keys(%main::source_cache)) {
  1.1832 +      # Clear the cache when it gets too big
  1.1833 +      $main::source_cache = ();
  1.1834 +    }
  1.1835 +
  1.1836 +    # Read all lines from the file
  1.1837 +    if (!open(FILE, "<$file")) {
  1.1838 +      print STDERR "$file: $!\n";
  1.1839 +      $main::source_cache{$file} = [];  # Cache the negative result
  1.1840 +      return undef;
  1.1841 +    }
  1.1842 +    my $lines = [];
  1.1843 +    push(@{$lines}, "");        # So we can use 1-based line numbers as indices
  1.1844 +    while (<FILE>) {
  1.1845 +      push(@{$lines}, $_);
  1.1846 +    }
  1.1847 +    close(FILE);
  1.1848 +
  1.1849 +    # Save the lines in the cache
  1.1850 +    $main::source_cache{$file} = $lines;
  1.1851 +  }
  1.1852 +
  1.1853 +  my $lines = $main::source_cache{$file};
  1.1854 +  if (($line < 0) || ($line > $#{$lines})) {
  1.1855 +    return undef;
  1.1856 +  } else {
  1.1857 +    return $lines->[$line];
  1.1858 +  }
  1.1859 +}
  1.1860 +
  1.1861 +# Print disassembly for one routine with interspersed source if available
  1.1862 +sub PrintDisassembledFunction {
  1.1863 +  my $prog = shift;
  1.1864 +  my $offset = shift;
  1.1865 +  my $routine = shift;
  1.1866 +  my $flat = shift;
  1.1867 +  my $cumulative = shift;
  1.1868 +  my $start_addr = shift;
  1.1869 +  my $end_addr = shift;
  1.1870 +  my $total = shift;
  1.1871 +
  1.1872 +  # Disassemble all instructions
  1.1873 +  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
  1.1874 +
  1.1875 +  # Make array of counts per instruction
  1.1876 +  my @flat_count = ();
  1.1877 +  my @cum_count = ();
  1.1878 +  my $flat_total = 0;
  1.1879 +  my $cum_total = 0;
  1.1880 +  foreach my $e (@instructions) {
  1.1881 +    # Add up counts for all address that fall inside this instruction
  1.1882 +    my $c1 = 0;
  1.1883 +    my $c2 = 0;
  1.1884 +    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
  1.1885 +      $c1 += GetEntry($flat, $a);
  1.1886 +      $c2 += GetEntry($cumulative, $a);
  1.1887 +    }
  1.1888 +    push(@flat_count, $c1);
  1.1889 +    push(@cum_count, $c2);
  1.1890 +    $flat_total += $c1;
  1.1891 +    $cum_total += $c2;
  1.1892 +  }
  1.1893 +
  1.1894 +  # Print header with total counts
  1.1895 +  printf("ROUTINE ====================== %s\n" .
  1.1896 +         "%6s %6s %s (flat, cumulative) %.1f%% of total\n",
  1.1897 +         ShortFunctionName($routine),
  1.1898 +         Unparse($flat_total),
  1.1899 +         Unparse($cum_total),
  1.1900 +         Units(),
  1.1901 +         ($cum_total * 100.0) / $total);
  1.1902 +
  1.1903 +  # Process instructions in order
  1.1904 +  my $current_file = "";
  1.1905 +  for (my $i = 0; $i <= $#instructions; ) {
  1.1906 +    my $e = $instructions[$i];
  1.1907 +
  1.1908 +    # Print the new file name whenever we switch files
  1.1909 +    if ($e->[1] ne $current_file) {
  1.1910 +      $current_file = $e->[1];
  1.1911 +      my $fname = $current_file;
  1.1912 +      $fname =~ s|^\./||;   # Trim leading "./"
  1.1913 +
  1.1914 +      # Shorten long file names
  1.1915 +      if (length($fname) >= 58) {
  1.1916 +        $fname = "..." . substr($fname, -55);
  1.1917 +      }
  1.1918 +      printf("-------------------- %s\n", $fname);
  1.1919 +    }
  1.1920 +
  1.1921 +    # TODO: Compute range of lines to print together to deal with
  1.1922 +    # small reorderings.
  1.1923 +    my $first_line = $e->[2];
  1.1924 +    my $last_line = $first_line;
  1.1925 +    my %flat_sum = ();
  1.1926 +    my %cum_sum = ();
  1.1927 +    for (my $l = $first_line; $l <= $last_line; $l++) {
  1.1928 +      $flat_sum{$l} = 0;
  1.1929 +      $cum_sum{$l} = 0;
  1.1930 +    }
  1.1931 +
  1.1932 +    # Find run of instructions for this range of source lines
  1.1933 +    my $first_inst = $i;
  1.1934 +    while (($i <= $#instructions) &&
  1.1935 +           ($instructions[$i]->[2] >= $first_line) &&
  1.1936 +           ($instructions[$i]->[2] <= $last_line)) {
  1.1937 +      $e = $instructions[$i];
  1.1938 +      $flat_sum{$e->[2]} += $flat_count[$i];
  1.1939 +      $cum_sum{$e->[2]} += $cum_count[$i];
  1.1940 +      $i++;
  1.1941 +    }
  1.1942 +    my $last_inst = $i - 1;
  1.1943 +
  1.1944 +    # Print source lines
  1.1945 +    for (my $l = $first_line; $l <= $last_line; $l++) {
  1.1946 +      my $line = SourceLine($current_file, $l);
  1.1947 +      if (!defined($line)) {
  1.1948 +        $line = "?\n";
  1.1949 +        next;
  1.1950 +      } else {
  1.1951 +        $line =~ s/^\s+//;
  1.1952 +      }
  1.1953 +      printf("%6s %6s %5d: %s",
  1.1954 +             UnparseAlt($flat_sum{$l}),
  1.1955 +             UnparseAlt($cum_sum{$l}),
  1.1956 +             $l,
  1.1957 +             $line);
  1.1958 +    }
  1.1959 +
  1.1960 +    # Print disassembly
  1.1961 +    for (my $x = $first_inst; $x <= $last_inst; $x++) {
  1.1962 +      my $e = $instructions[$x];
  1.1963 +      printf("%6s %6s    %8s: %6s\n",
  1.1964 +             UnparseAlt($flat_count[$x]),
  1.1965 +             UnparseAlt($cum_count[$x]),
  1.1966 +             UnparseAddress($offset, $e->[0]),
  1.1967 +             CleanDisassembly($e->[3]));
  1.1968 +    }
  1.1969 +  }
  1.1970 +}
  1.1971 +
  1.1972 +# Print DOT graph
  1.1973 +sub PrintDot {
  1.1974 +  my $prog = shift;
  1.1975 +  my $symbols = shift;
  1.1976 +  my $raw = shift;
  1.1977 +  my $flat = shift;
  1.1978 +  my $cumulative = shift;
  1.1979 +  my $overall_total = shift;
  1.1980 +
  1.1981 +  # Get total
  1.1982 +  my $local_total = TotalProfile($flat);
  1.1983 +  my $nodelimit = int($main::opt_nodefraction * $local_total);
  1.1984 +  my $edgelimit = int($main::opt_edgefraction * $local_total);
  1.1985 +  my $nodecount = $main::opt_nodecount;
  1.1986 +
  1.1987 +  # Find nodes to include
  1.1988 +  my @list = (sort { abs(GetEntry($cumulative, $b)) <=>
  1.1989 +                     abs(GetEntry($cumulative, $a))
  1.1990 +                     || $a cmp $b }
  1.1991 +              keys(%{$cumulative}));
  1.1992 +  my $last = $nodecount - 1;
  1.1993 +  if ($last > $#list) {
  1.1994 +    $last = $#list;
  1.1995 +  }
  1.1996 +  while (($last >= 0) &&
  1.1997 +         (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {
  1.1998 +    $last--;
  1.1999 +  }
  1.2000 +  if ($last < 0) {
  1.2001 +    print STDERR "No nodes to print\n";
  1.2002 +    return 0;
  1.2003 +  }
  1.2004 +
  1.2005 +  if ($nodelimit > 0 || $edgelimit > 0) {
  1.2006 +    printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n",
  1.2007 +                   Unparse($nodelimit), Units(),
  1.2008 +                   Unparse($edgelimit), Units());
  1.2009 +  }
  1.2010 +
  1.2011 +  # Open DOT output file
  1.2012 +  my $output;
  1.2013 +  my $escaped_dot = ShellEscape(@DOT);
  1.2014 +  my $escaped_ps2pdf = ShellEscape(@PS2PDF);
  1.2015 +  if ($main::opt_gv) {
  1.2016 +    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps"));
  1.2017 +    $output = "| $escaped_dot -Tps2 >$escaped_outfile";
  1.2018 +  } elsif ($main::opt_evince) {
  1.2019 +    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf"));
  1.2020 +    $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile";
  1.2021 +  } elsif ($main::opt_ps) {
  1.2022 +    $output = "| $escaped_dot -Tps2";
  1.2023 +  } elsif ($main::opt_pdf) {
  1.2024 +    $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -";
  1.2025 +  } elsif ($main::opt_web || $main::opt_svg) {
  1.2026 +    # We need to post-process the SVG, so write to a temporary file always.
  1.2027 +    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg"));
  1.2028 +    $output = "| $escaped_dot -Tsvg >$escaped_outfile";
  1.2029 +  } elsif ($main::opt_gif) {
  1.2030 +    $output = "| $escaped_dot -Tgif";
  1.2031 +  } else {
  1.2032 +    $output = ">&STDOUT";
  1.2033 +  }
  1.2034 +  open(DOT, $output) || error("$output: $!\n");
  1.2035 +
  1.2036 +  # Title
  1.2037 +  printf DOT ("digraph \"%s; %s %s\" {\n",
  1.2038 +              $prog,
  1.2039 +              Unparse($overall_total),
  1.2040 +              Units());
  1.2041 +  if ($main::opt_pdf) {
  1.2042 +    # The output is more printable if we set the page size for dot.
  1.2043 +    printf DOT ("size=\"8,11\"\n");
  1.2044 +  }
  1.2045 +  printf DOT ("node [width=0.375,height=0.25];\n");
  1.2046 +
  1.2047 +  # Print legend
  1.2048 +  printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," .
  1.2049 +              "label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n",
  1.2050 +              $prog,
  1.2051 +              sprintf("Total %s: %s", Units(), Unparse($overall_total)),
  1.2052 +              sprintf("Focusing on: %s", Unparse($local_total)),
  1.2053 +              sprintf("Dropped nodes with <= %s abs(%s)",
  1.2054 +                      Unparse($nodelimit), Units()),
  1.2055 +              sprintf("Dropped edges with <= %s %s",
  1.2056 +                      Unparse($edgelimit), Units())
  1.2057 +              );
  1.2058 +
  1.2059 +  # Print nodes
  1.2060 +  my %node = ();
  1.2061 +  my $nextnode = 1;
  1.2062 +  foreach my $a (@list[0..$last]) {
  1.2063 +    # Pick font size
  1.2064 +    my $f = GetEntry($flat, $a);
  1.2065 +    my $c = GetEntry($cumulative, $a);
  1.2066 +
  1.2067 +    my $fs = 8;
  1.2068 +    if ($local_total > 0) {
  1.2069 +      $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));
  1.2070 +    }
  1.2071 +
  1.2072 +    $node{$a} = $nextnode++;
  1.2073 +    my $sym = $a;
  1.2074 +    $sym =~ s/\s+/\\n/g;
  1.2075 +    $sym =~ s/::/\\n/g;
  1.2076 +
  1.2077 +    # Extra cumulative info to print for non-leaves
  1.2078 +    my $extra = "";
  1.2079 +    if ($f != $c) {
  1.2080 +      $extra = sprintf("\\rof %s (%s)",
  1.2081 +                       Unparse($c),
  1.2082 +                       Percent($c, $local_total));
  1.2083 +    }
  1.2084 +    my $style = "";
  1.2085 +    if ($main::opt_heapcheck) {
  1.2086 +      if ($f > 0) {
  1.2087 +        # make leak-causing nodes more visible (add a background)
  1.2088 +        $style = ",style=filled,fillcolor=gray"
  1.2089 +      } elsif ($f < 0) {
  1.2090 +        # make anti-leak-causing nodes (which almost never occur)
  1.2091 +        # stand out as well (triple border)
  1.2092 +        $style = ",peripheries=3"
  1.2093 +      }
  1.2094 +    }
  1.2095 +
  1.2096 +    printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
  1.2097 +                "\",shape=box,fontsize=%.1f%s];\n",
  1.2098 +                $node{$a},
  1.2099 +                $sym,
  1.2100 +                Unparse($f),
  1.2101 +                Percent($f, $local_total),
  1.2102 +                $extra,
  1.2103 +                $fs,
  1.2104 +                $style,
  1.2105 +               );
  1.2106 +  }
  1.2107 +
  1.2108 +  # Get edges and counts per edge
  1.2109 +  my %edge = ();
  1.2110 +  my $n;
  1.2111 +  my $fullname_to_shortname_map = {};
  1.2112 +  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
  1.2113 +  foreach my $k (keys(%{$raw})) {
  1.2114 +    # TODO: omit low %age edges
  1.2115 +    $n = $raw->{$k};
  1.2116 +    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
  1.2117 +    for (my $i = 1; $i <= $#translated; $i++) {
  1.2118 +      my $src = $translated[$i];
  1.2119 +      my $dst = $translated[$i-1];
  1.2120 +      #next if ($src eq $dst);  # Avoid self-edges?
  1.2121 +      if (exists($node{$src}) && exists($node{$dst})) {
  1.2122 +        my $edge_label = "$src\001$dst";
  1.2123 +        if (!exists($edge{$edge_label})) {
  1.2124 +          $edge{$edge_label} = 0;
  1.2125 +        }
  1.2126 +        $edge{$edge_label} += $n;
  1.2127 +      }
  1.2128 +    }
  1.2129 +  }
  1.2130 +
  1.2131 +  # Print edges (process in order of decreasing counts)
  1.2132 +  my %indegree = ();   # Number of incoming edges added per node so far
  1.2133 +  my %outdegree = ();  # Number of outgoing edges added per node so far
  1.2134 +  foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
  1.2135 +    my @x = split(/\001/, $e);
  1.2136 +    $n = $edge{$e};
  1.2137 +
  1.2138 +    # Initialize degree of kept incoming and outgoing edges if necessary
  1.2139 +    my $src = $x[0];
  1.2140 +    my $dst = $x[1];
  1.2141 +    if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }
  1.2142 +    if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }
  1.2143 +
  1.2144 +    my $keep;
  1.2145 +    if ($indegree{$dst} == 0) {
  1.2146 +      # Keep edge if needed for reachability
  1.2147 +      $keep = 1;
  1.2148 +    } elsif (abs($n) <= $edgelimit) {
  1.2149 +      # Drop if we are below --edgefraction
  1.2150 +      $keep = 0;
  1.2151 +    } elsif ($outdegree{$src} >= $main::opt_maxdegree ||
  1.2152 +             $indegree{$dst} >= $main::opt_maxdegree) {
  1.2153 +      # Keep limited number of in/out edges per node
  1.2154 +      $keep = 0;
  1.2155 +    } else {
  1.2156 +      $keep = 1;
  1.2157 +    }
  1.2158 +
  1.2159 +    if ($keep) {
  1.2160 +      $outdegree{$src}++;
  1.2161 +      $indegree{$dst}++;
  1.2162 +
  1.2163 +      # Compute line width based on edge count
  1.2164 +      my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
  1.2165 +      if ($fraction > 1) { $fraction = 1; }
  1.2166 +      my $w = $fraction * 2;
  1.2167 +      if ($w < 1 && ($main::opt_web || $main::opt_svg)) {
  1.2168 +        # SVG output treats line widths < 1 poorly.
  1.2169 +        $w = 1;
  1.2170 +      }
  1.2171 +
  1.2172 +      # Dot sometimes segfaults if given edge weights that are too large, so
  1.2173 +      # we cap the weights at a large value
  1.2174 +      my $edgeweight = abs($n) ** 0.7;
  1.2175 +      if ($edgeweight > 100000) { $edgeweight = 100000; }
  1.2176 +      $edgeweight = int($edgeweight);
  1.2177 +
  1.2178 +      my $style = sprintf("setlinewidth(%f)", $w);
  1.2179 +      if ($x[1] =~ m/\(inline\)/) {
  1.2180 +        $style .= ",dashed";
  1.2181 +      }
  1.2182 +
  1.2183 +      # Use a slightly squashed function of the edge count as the weight
  1.2184 +      printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n",
  1.2185 +                  $node{$x[0]},
  1.2186 +                  $node{$x[1]},
  1.2187 +                  Unparse($n),
  1.2188 +                  $edgeweight,
  1.2189 +                  $style);
  1.2190 +    }
  1.2191 +  }
  1.2192 +
  1.2193 +  print DOT ("}\n");
  1.2194 +  close(DOT);
  1.2195 +
  1.2196 +  if ($main::opt_web || $main::opt_svg) {
  1.2197 +    # Rewrite SVG to be more usable inside web browser.
  1.2198 +    RewriteSvg(TempName($main::next_tmpfile, "svg"));
  1.2199 +  }
  1.2200 +
  1.2201 +  return 1;
  1.2202 +}
  1.2203 +
  1.2204 +sub RewriteSvg {
  1.2205 +  my $svgfile = shift;
  1.2206 +
  1.2207 +  open(SVG, $svgfile) || die "open temp svg: $!";
  1.2208 +  my @svg = <SVG>;
  1.2209 +  close(SVG);
  1.2210 +  unlink $svgfile;
  1.2211 +  my $svg = join('', @svg);
  1.2212 +
  1.2213 +  # Dot's SVG output is
  1.2214 +  #
  1.2215 +  #    <svg width="___" height="___"
  1.2216 +  #     viewBox="___" xmlns=...>
  1.2217 +  #    <g id="graph0" transform="...">
  1.2218 +  #    ...
  1.2219 +  #    </g>
  1.2220 +  #    </svg>
  1.2221 +  #
  1.2222 +  # Change it to
  1.2223 +  #
  1.2224 +  #    <svg width="100%" height="100%"
  1.2225 +  #     xmlns=...>
  1.2226 +  #    $svg_javascript
  1.2227 +  #    <g id="viewport" transform="translate(0,0)">
  1.2228 +  #    <g id="graph0" transform="...">
  1.2229 +  #    ...
  1.2230 +  #    </g>
  1.2231 +  #    </g>
  1.2232 +  #    </svg>
  1.2233 +
  1.2234 +  # Fix width, height; drop viewBox.
  1.2235 +  $svg =~ s/(?s)<svg width="[^"]+" height="[^"]+"(.*?)viewBox="[^"]+"/<svg width="100%" height="100%"$1/;
  1.2236 +
  1.2237 +  # Insert script, viewport <g> above first <g>
  1.2238 +  my $svg_javascript = SvgJavascript();
  1.2239 +  my $viewport = "<g id=\"viewport\" transform=\"translate(0,0)\">\n";
  1.2240 +  $svg =~ s/<g id="graph\d"/$svg_javascript$viewport$&/;
  1.2241 +
  1.2242 +  # Insert final </g> above </svg>.
  1.2243 +  $svg =~ s/(.*)(<\/svg>)/$1<\/g>$2/;
  1.2244 +  $svg =~ s/<g id="graph\d"(.*?)/<g id="viewport"$1/;
  1.2245 +
  1.2246 +  if ($main::opt_svg) {
  1.2247 +    # --svg: write to standard output.
  1.2248 +    print $svg;
  1.2249 +  } else {
  1.2250 +    # Write back to temporary file.
  1.2251 +    open(SVG, ">$svgfile") || die "open $svgfile: $!";
  1.2252 +    print SVG $svg;
  1.2253 +    close(SVG);
  1.2254 +  }
  1.2255 +}
  1.2256 +
  1.2257 +sub SvgJavascript {
  1.2258 +  return <<'EOF';
  1.2259 +<script type="text/ecmascript"><![CDATA[
  1.2260 +// SVGPan
  1.2261 +// http://www.cyberz.org/blog/2009/12/08/svgpan-a-javascript-svg-panzoomdrag-library/
  1.2262 +// Local modification: if(true || ...) below to force panning, never moving.
  1.2263 +
  1.2264 +/**
  1.2265 + *  SVGPan library 1.2
  1.2266 + * ====================
  1.2267 + *
  1.2268 + * Given an unique existing element with id "viewport", including the
  1.2269 + * the library into any SVG adds the following capabilities:
  1.2270 + *
  1.2271 + *  - Mouse panning
  1.2272 + *  - Mouse zooming (using the wheel)
  1.2273 + *  - Object dargging
  1.2274 + *
  1.2275 + * Known issues:
  1.2276 + *
  1.2277 + *  - Zooming (while panning) on Safari has still some issues
  1.2278 + *
  1.2279 + * Releases:
  1.2280 + *
  1.2281 + * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
  1.2282 + *	Fixed a bug with browser mouse handler interaction
  1.2283 + *
  1.2284 + * 1.1, Wed Feb  3 17:39:33 GMT 2010, Zeng Xiaohui
  1.2285 + *	Updated the zoom code to support the mouse wheel on Safari/Chrome
  1.2286 + *
  1.2287 + * 1.0, Andrea Leofreddi
  1.2288 + *	First release
  1.2289 + *
  1.2290 + * This code is licensed under the following BSD license:
  1.2291 + *
  1.2292 + * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.
  1.2293 + *
  1.2294 + * Redistribution and use in source and binary forms, with or without modification, are
  1.2295 + * permitted provided that the following conditions are met:
  1.2296 + *
  1.2297 + *    1. Redistributions of source code must retain the above copyright notice, this list of
  1.2298 + *       conditions and the following disclaimer.
  1.2299 + *
  1.2300 + *    2. Redistributions in binary form must reproduce the above copyright notice, this list
  1.2301 + *       of conditions and the following disclaimer in the documentation and/or other materials
  1.2302 + *       provided with the distribution.
  1.2303 + *
  1.2304 + * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
  1.2305 + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  1.2306 + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
  1.2307 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  1.2308 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  1.2309 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  1.2310 + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  1.2311 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  1.2312 + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  1.2313 + *
  1.2314 + * The views and conclusions contained in the software and documentation are those of the
  1.2315 + * authors and should not be interpreted as representing official policies, either expressed
  1.2316 + * or implied, of Andrea Leofreddi.
  1.2317 + */
  1.2318 +
  1.2319 +var root = document.documentElement;
  1.2320 +
  1.2321 +var state = 'none', stateTarget, stateOrigin, stateTf;
  1.2322 +
  1.2323 +setupHandlers(root);
  1.2324 +
  1.2325 +/**
  1.2326 + * Register handlers
  1.2327 + */
  1.2328 +function setupHandlers(root){
  1.2329 +	setAttributes(root, {
  1.2330 +		"onmouseup" : "add(evt)",
  1.2331 +		"onmousedown" : "handleMouseDown(evt)",
  1.2332 +		"onmousemove" : "handleMouseMove(evt)",
  1.2333 +		"onmouseup" : "handleMouseUp(evt)",
  1.2334 +		//"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
  1.2335 +	});
  1.2336 +
  1.2337 +	if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
  1.2338 +		window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
  1.2339 +	else
  1.2340 +		window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
  1.2341 +
  1.2342 +	var g = svgDoc.getElementById("svg");
  1.2343 +	g.width = "100%";
  1.2344 +	g.height = "100%";
  1.2345 +}
  1.2346 +
  1.2347 +/**
  1.2348 + * Instance an SVGPoint object with given event coordinates.
  1.2349 + */
  1.2350 +function getEventPoint(evt) {
  1.2351 +	var p = root.createSVGPoint();
  1.2352 +
  1.2353 +	p.x = evt.clientX;
  1.2354 +	p.y = evt.clientY;
  1.2355 +
  1.2356 +	return p;
  1.2357 +}
  1.2358 +
  1.2359 +/**
  1.2360 + * Sets the current transform matrix of an element.
  1.2361 + */
  1.2362 +function setCTM(element, matrix) {
  1.2363 +	var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
  1.2364 +
  1.2365 +	element.setAttribute("transform", s);
  1.2366 +}
  1.2367 +
  1.2368 +/**
  1.2369 + * Dumps a matrix to a string (useful for debug).
  1.2370 + */
  1.2371 +function dumpMatrix(matrix) {
  1.2372 +	var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n  " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n  0, 0, 1 ]";
  1.2373 +
  1.2374 +	return s;
  1.2375 +}
  1.2376 +
  1.2377 +/**
  1.2378 + * Sets attributes of an element.
  1.2379 + */
  1.2380 +function setAttributes(element, attributes){
  1.2381 +	for (i in attributes)
  1.2382 +		element.setAttributeNS(null, i, attributes[i]);
  1.2383 +}
  1.2384 +
  1.2385 +/**
  1.2386 + * Handle mouse move event.
  1.2387 + */
  1.2388 +function handleMouseWheel(evt) {
  1.2389 +	if(evt.preventDefault)
  1.2390 +		evt.preventDefault();
  1.2391 +
  1.2392 +	evt.returnValue = false;
  1.2393 +
  1.2394 +	var svgDoc = evt.target.ownerDocument;
  1.2395 +
  1.2396 +	var delta;
  1.2397 +
  1.2398 +	if(evt.wheelDelta)
  1.2399 +		delta = evt.wheelDelta / 3600; // Chrome/Safari
  1.2400 +	else
  1.2401 +		delta = evt.detail / -90; // Mozilla
  1.2402 +
  1.2403 +	var z = 1 + delta; // Zoom factor: 0.9/1.1
  1.2404 +
  1.2405 +	var g = svgDoc.getElementById("viewport");
  1.2406 +
  1.2407 +	var p = getEventPoint(evt);
  1.2408 +
  1.2409 +	p = p.matrixTransform(g.getCTM().inverse());
  1.2410 +
  1.2411 +	// Compute new scale matrix in current mouse position
  1.2412 +	var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
  1.2413 +
  1.2414 +        setCTM(g, g.getCTM().multiply(k));
  1.2415 +
  1.2416 +	stateTf = stateTf.multiply(k.inverse());
  1.2417 +}
  1.2418 +
  1.2419 +/**
  1.2420 + * Handle mouse move event.
  1.2421 + */
  1.2422 +function handleMouseMove(evt) {
  1.2423 +	if(evt.preventDefault)
  1.2424 +		evt.preventDefault();
  1.2425 +
  1.2426 +	evt.returnValue = false;
  1.2427 +
  1.2428 +	var svgDoc = evt.target.ownerDocument;
  1.2429 +
  1.2430 +	var g = svgDoc.getElementById("viewport");
  1.2431 +
  1.2432 +	if(state == 'pan') {
  1.2433 +		// Pan mode
  1.2434 +		var p = getEventPoint(evt).matrixTransform(stateTf);
  1.2435 +
  1.2436 +		setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
  1.2437 +	} else if(state == 'move') {
  1.2438 +		// Move mode
  1.2439 +		var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
  1.2440 +
  1.2441 +		setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
  1.2442 +
  1.2443 +		stateOrigin = p;
  1.2444 +	}
  1.2445 +}
  1.2446 +
  1.2447 +/**
  1.2448 + * Handle click event.
  1.2449 + */
  1.2450 +function handleMouseDown(evt) {
  1.2451 +	if(evt.preventDefault)
  1.2452 +		evt.preventDefault();
  1.2453 +
  1.2454 +	evt.returnValue = false;
  1.2455 +
  1.2456 +	var svgDoc = evt.target.ownerDocument;
  1.2457 +
  1.2458 +	var g = svgDoc.getElementById("viewport");
  1.2459 +
  1.2460 +	if(true || evt.target.tagName == "svg") {
  1.2461 +		// Pan mode
  1.2462 +		state = 'pan';
  1.2463 +
  1.2464 +		stateTf = g.getCTM().inverse();
  1.2465 +
  1.2466 +		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
  1.2467 +	} else {
  1.2468 +		// Move mode
  1.2469 +		state = 'move';
  1.2470 +
  1.2471 +		stateTarget = evt.target;
  1.2472 +
  1.2473 +		stateTf = g.getCTM().inverse();
  1.2474 +
  1.2475 +		stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
  1.2476 +	}
  1.2477 +}
  1.2478 +
  1.2479 +/**
  1.2480 + * Handle mouse button release event.
  1.2481 + */
  1.2482 +function handleMouseUp(evt) {
  1.2483 +	if(evt.preventDefault)
  1.2484 +		evt.preventDefault();
  1.2485 +
  1.2486 +	evt.returnValue = false;
  1.2487 +
  1.2488 +	var svgDoc = evt.target.ownerDocument;
  1.2489 +
  1.2490 +	if(state == 'pan' || state == 'move') {
  1.2491 +		// Quit pan mode
  1.2492 +		state = '';
  1.2493 +	}
  1.2494 +}
  1.2495 +
  1.2496 +]]></script>
  1.2497 +EOF
  1.2498 +}
  1.2499 +
  1.2500 +# Provides a map from fullname to shortname for cases where the
  1.2501 +# shortname is ambiguous.  The symlist has both the fullname and
  1.2502 +# shortname for all symbols, which is usually fine, but sometimes --
  1.2503 +# such as overloaded functions -- two different fullnames can map to
  1.2504 +# the same shortname.  In that case, we use the address of the
  1.2505 +# function to disambiguate the two.  This function fills in a map that
  1.2506 +# maps fullnames to modified shortnames in such cases.  If a fullname
  1.2507 +# is not present in the map, the 'normal' shortname provided by the
  1.2508 +# symlist is the appropriate one to use.
  1.2509 +sub FillFullnameToShortnameMap {
  1.2510 +  my $symbols = shift;
  1.2511 +  my $fullname_to_shortname_map = shift;
  1.2512 +  my $shortnames_seen_once = {};
  1.2513 +  my $shortnames_seen_more_than_once = {};
  1.2514 +
  1.2515 +  foreach my $symlist (values(%{$symbols})) {
  1.2516 +    # TODO(csilvers): deal with inlined symbols too.
  1.2517 +    my $shortname = $symlist->[0];
  1.2518 +    my $fullname = $symlist->[2];
  1.2519 +    if ($fullname !~ /<[0-9a-fA-F]+>$/) {  # fullname doesn't end in an address
  1.2520 +      next;       # the only collisions we care about are when addresses differ
  1.2521 +    }
  1.2522 +    if (defined($shortnames_seen_once->{$shortname}) &&
  1.2523 +        $shortnames_seen_once->{$shortname} ne $fullname) {
  1.2524 +      $shortnames_seen_more_than_once->{$shortname} = 1;
  1.2525 +    } else {
  1.2526 +      $shortnames_seen_once->{$shortname} = $fullname;
  1.2527 +    }
  1.2528 +  }
  1.2529 +
  1.2530 +  foreach my $symlist (values(%{$symbols})) {
  1.2531 +    my $shortname = $symlist->[0];
  1.2532 +    my $fullname = $symlist->[2];
  1.2533 +    # TODO(csilvers): take in a list of addresses we care about, and only
  1.2534 +    # store in the map if $symlist->[1] is in that list.  Saves space.
  1.2535 +    next if defined($fullname_to_shortname_map->{$fullname});
  1.2536 +    if (defined($shortnames_seen_more_than_once->{$shortname})) {
  1.2537 +      if ($fullname =~ /<0*([^>]*)>$/) {   # fullname has address at end of it
  1.2538 +        $fullname_to_shortname_map->{$fullname} = "$shortname\@$1";
  1.2539 +      }
  1.2540 +    }
  1.2541 +  }
  1.2542 +}
  1.2543 +
  1.2544 +# Return a small number that identifies the argument.
  1.2545 +# Multiple calls with the same argument will return the same number.
  1.2546 +# Calls with different arguments will return different numbers.
  1.2547 +sub ShortIdFor {
  1.2548 +  my $key = shift;
  1.2549 +  my $id = $main::uniqueid{$key};
  1.2550 +  if (!defined($id)) {
  1.2551 +    $id = keys(%main::uniqueid) + 1;
  1.2552 +    $main::uniqueid{$key} = $id;
  1.2553 +  }
  1.2554 +  return $id;
  1.2555 +}
  1.2556 +
  1.2557 +# Translate a stack of addresses into a stack of symbols
  1.2558 +sub TranslateStack {
  1.2559 +  my $symbols = shift;
  1.2560 +  my $fullname_to_shortname_map = shift;
  1.2561 +  my $k = shift;
  1.2562 +
  1.2563 +  my @addrs = split(/\n/, $k);
  1.2564 +  my @result = ();
  1.2565 +  for (my $i = 0; $i <= $#addrs; $i++) {
  1.2566 +    my $a = $addrs[$i];
  1.2567 +
  1.2568 +    # Skip large addresses since they sometimes show up as fake entries on RH9
  1.2569 +    if (length($a) > 8 && $a gt "7fffffffffffffff") {
  1.2570 +      next;
  1.2571 +    }
  1.2572 +
  1.2573 +    if ($main::opt_disasm || $main::opt_list) {
  1.2574 +      # We want just the address for the key
  1.2575 +      push(@result, $a);
  1.2576 +      next;
  1.2577 +    }
  1.2578 +
  1.2579 +    my $symlist = $symbols->{$a};
  1.2580 +    if (!defined($symlist)) {
  1.2581 +      $symlist = [$a, "", $a];
  1.2582 +    }
  1.2583 +
  1.2584 +    # We can have a sequence of symbols for a particular entry
  1.2585 +    # (more than one symbol in the case of inlining).  Callers
  1.2586 +    # come before callees in symlist, so walk backwards since
  1.2587 +    # the translated stack should contain callees before callers.
  1.2588 +    for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {
  1.2589 +      my $func = $symlist->[$j-2];
  1.2590 +      my $fileline = $symlist->[$j-1];
  1.2591 +      my $fullfunc = $symlist->[$j];
  1.2592 +      if (defined($fullname_to_shortname_map->{$fullfunc})) {
  1.2593 +        $func = $fullname_to_shortname_map->{$fullfunc};
  1.2594 +      }
  1.2595 +      if ($j > 2) {
  1.2596 +        $func = "$func (inline)";
  1.2597 +      }
  1.2598 +
  1.2599 +      # Do not merge nodes corresponding to Callback::Run since that
  1.2600 +      # causes confusing cycles in dot display.  Instead, we synthesize
  1.2601 +      # a unique name for this frame per caller.
  1.2602 +      if ($func =~ m/Callback.*::Run$/) {
  1.2603 +        my $caller = ($i > 0) ? $addrs[$i-1] : 0;
  1.2604 +        $func = "Run#" . ShortIdFor($caller);
  1.2605 +      }
  1.2606 +
  1.2607 +      if ($main::opt_addresses) {
  1.2608 +        push(@result, "$a $func $fileline");
  1.2609 +      } elsif ($main::opt_lines) {
  1.2610 +        if ($func eq '??' && $fileline eq '??:0') {
  1.2611 +          push(@result, "$a");
  1.2612 +        } else {
  1.2613 +          push(@result, "$func $fileline");
  1.2614 +        }
  1.2615 +      } elsif ($main::opt_functions) {
  1.2616 +        if ($func eq '??') {
  1.2617 +          push(@result, "$a");
  1.2618 +        } else {
  1.2619 +          push(@result, $func);
  1.2620 +        }
  1.2621 +      } elsif ($main::opt_files) {
  1.2622 +        if ($fileline eq '??:0' || $fileline eq '') {
  1.2623 +          push(@result, "$a");
  1.2624 +        } else {
  1.2625 +          my $f = $fileline;
  1.2626 +          $f =~ s/:\d+$//;
  1.2627 +          push(@result, $f);
  1.2628 +        }
  1.2629 +      } else {
  1.2630 +        push(@result, $a);
  1.2631 +        last;  # Do not print inlined info
  1.2632 +      }
  1.2633 +    }
  1.2634 +  }
  1.2635 +
  1.2636 +  # print join(",", @addrs), " => ", join(",", @result), "\n";
  1.2637 +  return @result;
  1.2638 +}
  1.2639 +
  1.2640 +# Generate percent string for a number and a total
  1.2641 +sub Percent {
  1.2642 +  my $num = shift;
  1.2643 +  my $tot = shift;
  1.2644 +  if ($tot != 0) {
  1.2645 +    return sprintf("%.1f%%", $num * 100.0 / $tot);
  1.2646 +  } else {
  1.2647 +    return ($num == 0) ? "nan" : (($num > 0) ? "+inf" : "-inf");
  1.2648 +  }
  1.2649 +}
  1.2650 +
  1.2651 +# Generate pretty-printed form of number
  1.2652 +sub Unparse {
  1.2653 +  my $num = shift;
  1.2654 +  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
  1.2655 +    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
  1.2656 +      return sprintf("%d", $num);
  1.2657 +    } else {
  1.2658 +      if ($main::opt_show_bytes) {
  1.2659 +        return sprintf("%d", $num);
  1.2660 +      } else {
  1.2661 +        return sprintf("%.1f", $num / 1048576.0);
  1.2662 +      }
  1.2663 +    }
  1.2664 +  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
  1.2665 +    return sprintf("%.3f", $num / 1e9); # Convert nanoseconds to seconds
  1.2666 +  } else {
  1.2667 +    return sprintf("%d", $num);
  1.2668 +  }
  1.2669 +}
  1.2670 +
  1.2671 +# Alternate pretty-printed form: 0 maps to "."
  1.2672 +sub UnparseAlt {
  1.2673 +  my $num = shift;
  1.2674 +  if ($num == 0) {
  1.2675 +    return ".";
  1.2676 +  } else {
  1.2677 +    return Unparse($num);
  1.2678 +  }
  1.2679 +}
  1.2680 +
  1.2681 +# Alternate pretty-printed form: 0 maps to ""
  1.2682 +sub HtmlPrintNumber {
  1.2683 +  my $num = shift;
  1.2684 +  if ($num == 0) {
  1.2685 +    return "";
  1.2686 +  } else {
  1.2687 +    return Unparse($num);
  1.2688 +  }
  1.2689 +}
  1.2690 +
  1.2691 +# Return output units
  1.2692 +sub Units {
  1.2693 +  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
  1.2694 +    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {
  1.2695 +      return "objects";
  1.2696 +    } else {
  1.2697 +      if ($main::opt_show_bytes) {
  1.2698 +        return "B";
  1.2699 +      } else {
  1.2700 +        return "MB";
  1.2701 +      }
  1.2702 +    }
  1.2703 +  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {
  1.2704 +    return "seconds";
  1.2705 +  } else {
  1.2706 +    return "samples";
  1.2707 +  }
  1.2708 +}
  1.2709 +
  1.2710 +##### Profile manipulation code #####
  1.2711 +
  1.2712 +# Generate flattened profile:
  1.2713 +# If count is charged to stack [a,b,c,d], in generated profile,
  1.2714 +# it will be charged to [a]
  1.2715 +sub FlatProfile {
  1.2716 +  my $profile = shift;
  1.2717 +  my $result = {};
  1.2718 +  foreach my $k (keys(%{$profile})) {
  1.2719 +    my $count = $profile->{$k};
  1.2720 +    my @addrs = split(/\n/, $k);
  1.2721 +    if ($#addrs >= 0) {
  1.2722 +      AddEntry($result, $addrs[0], $count);
  1.2723 +    }
  1.2724 +  }
  1.2725 +  return $result;
  1.2726 +}
  1.2727 +
  1.2728 +# Generate cumulative profile:
  1.2729 +# If count is charged to stack [a,b,c,d], in generated profile,
  1.2730 +# it will be charged to [a], [b], [c], [d]
  1.2731 +sub CumulativeProfile {
  1.2732 +  my $profile = shift;
  1.2733 +  my $result = {};
  1.2734 +  foreach my $k (keys(%{$profile})) {
  1.2735 +    my $count = $profile->{$k};
  1.2736 +    my @addrs = split(/\n/, $k);
  1.2737 +    foreach my $a (@addrs) {
  1.2738 +      AddEntry($result, $a, $count);
  1.2739 +    }
  1.2740 +  }
  1.2741 +  return $result;
  1.2742 +}
  1.2743 +
  1.2744 +# If the second-youngest PC on the stack is always the same, returns
  1.2745 +# that pc.  Otherwise, returns undef.
  1.2746 +sub IsSecondPcAlwaysTheSame {
  1.2747 +  my $profile = shift;
  1.2748 +
  1.2749 +  my $second_pc = undef;
  1.2750 +  foreach my $k (keys(%{$profile})) {
  1.2751 +    my @addrs = split(/\n/, $k);
  1.2752 +    if ($#addrs < 1) {
  1.2753 +      return undef;
  1.2754 +    }
  1.2755 +    if (not defined $second_pc) {
  1.2756 +      $second_pc = $addrs[1];
  1.2757 +    } else {
  1.2758 +      if ($second_pc ne $addrs[1]) {
  1.2759 +        return undef;
  1.2760 +      }
  1.2761 +    }
  1.2762 +  }
  1.2763 +  return $second_pc;
  1.2764 +}
  1.2765 +
  1.2766 +sub ExtractSymbolLocation {
  1.2767 +  my $symbols = shift;
  1.2768 +  my $address = shift;
  1.2769 +  # 'addr2line' outputs "??:0" for unknown locations; we do the
  1.2770 +  # same to be consistent.
  1.2771 +  my $location = "??:0:unknown";
  1.2772 +  if (exists $symbols->{$address}) {
  1.2773 +    my $file = $symbols->{$address}->[1];
  1.2774 +    if ($file eq "?") {
  1.2775 +      $file = "??:0"
  1.2776 +    }
  1.2777 +    $location = $file . ":" . $symbols->{$address}->[0];
  1.2778 +  }
  1.2779 +  return $location;
  1.2780 +}
  1.2781 +
  1.2782 +# Extracts a graph of calls.
  1.2783 +sub ExtractCalls {
  1.2784 +  my $symbols = shift;
  1.2785 +  my $profile = shift;
  1.2786 +
  1.2787 +  my $calls = {};
  1.2788 +  while( my ($stack_trace, $count) = each %$profile ) {
  1.2789 +    my @address = split(/\n/, $stack_trace);
  1.2790 +    my $destination = ExtractSymbolLocation($symbols, $address[0]);
  1.2791 +    AddEntry($calls, $destination, $count);
  1.2792 +    for (my $i = 1; $i <= $#address; $i++) {
  1.2793 +      my $source = ExtractSymbolLocation($symbols, $address[$i]);
  1.2794 +      my $call = "$source -> $destination";
  1.2795 +      AddEntry($calls, $call, $count);
  1.2796 +      $destination = $source;
  1.2797 +    }
  1.2798 +  }
  1.2799 +
  1.2800 +  return $calls;
  1.2801 +}
  1.2802 +
  1.2803 +sub RemoveUninterestingFrames {
  1.2804 +  my $symbols = shift;
  1.2805 +  my $profile = shift;
  1.2806 +
  1.2807 +  # List of function names to skip
  1.2808 +  my %skip = ();
  1.2809 +  my $skip_regexp = 'NOMATCH';
  1.2810 +  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
  1.2811 +    foreach my $name ('calloc',
  1.2812 +                      'cfree',
  1.2813 +                      'malloc',
  1.2814 +                      'free',
  1.2815 +                      'memalign',
  1.2816 +                      'posix_memalign',
  1.2817 +                      'pvalloc',
  1.2818 +                      'valloc',
  1.2819 +                      'realloc',
  1.2820 +                      'tc_calloc',
  1.2821 +                      'tc_cfree',
  1.2822 +                      'tc_malloc',
  1.2823 +                      'tc_free',
  1.2824 +                      'tc_memalign',
  1.2825 +                      'tc_posix_memalign',
  1.2826 +                      'tc_pvalloc',
  1.2827 +                      'tc_valloc',
  1.2828 +                      'tc_realloc',
  1.2829 +                      'tc_new',
  1.2830 +                      'tc_delete',
  1.2831 +                      'tc_newarray',
  1.2832 +                      'tc_deletearray',
  1.2833 +                      'tc_new_nothrow',
  1.2834 +                      'tc_newarray_nothrow',
  1.2835 +                      'do_malloc',
  1.2836 +                      '::do_malloc',   # new name -- got moved to an unnamed ns
  1.2837 +                      '::do_malloc_or_cpp_alloc',
  1.2838 +                      'DoSampledAllocation',
  1.2839 +                      'simple_alloc::allocate',
  1.2840 +                      '__malloc_alloc_template::allocate',
  1.2841 +                      '__builtin_delete',
  1.2842 +                      '__builtin_new',
  1.2843 +                      '__builtin_vec_delete',
  1.2844 +                      '__builtin_vec_new',
  1.2845 +                      'operator new',
  1.2846 +                      'operator new[]',
  1.2847 +                      # The entry to our memory-allocation routines on OS X
  1.2848 +                      'malloc_zone_malloc',
  1.2849 +                      'malloc_zone_calloc',
  1.2850 +                      'malloc_zone_valloc',
  1.2851 +                      'malloc_zone_realloc',
  1.2852 +                      'malloc_zone_memalign',
  1.2853 +                      'malloc_zone_free',
  1.2854 +                      # These mark the beginning/end of our custom sections
  1.2855 +                      '__start_google_malloc',
  1.2856 +                      '__stop_google_malloc',
  1.2857 +                      '__start_malloc_hook',
  1.2858 +                      '__stop_malloc_hook') {
  1.2859 +      $skip{$name} = 1;
  1.2860 +      $skip{"_" . $name} = 1;   # Mach (OS X) adds a _ prefix to everything
  1.2861 +    }
  1.2862 +    # TODO: Remove TCMalloc once everything has been
  1.2863 +    # moved into the tcmalloc:: namespace and we have flushed
  1.2864 +    # old code out of the system.
  1.2865 +    $skip_regexp = "TCMalloc|^tcmalloc::";
  1.2866 +  } elsif ($main::profile_type eq 'contention') {
  1.2867 +    foreach my $vname ('base::RecordLockProfileData',
  1.2868 +                       'base::SubmitMutexProfileData',
  1.2869 +                       'base::SubmitSpinLockProfileData',
  1.2870 +                       'Mutex::Unlock',
  1.2871 +                       'Mutex::UnlockSlow',
  1.2872 +                       'Mutex::ReaderUnlock',
  1.2873 +                       'MutexLock::~MutexLock',
  1.2874 +                       'SpinLock::Unlock',
  1.2875 +                       'SpinLock::SlowUnlock',
  1.2876 +                       'SpinLockHolder::~SpinLockHolder') {
  1.2877 +      $skip{$vname} = 1;
  1.2878 +    }
  1.2879 +  } elsif ($main::profile_type eq 'cpu') {
  1.2880 +    # Drop signal handlers used for CPU profile collection
  1.2881 +    # TODO(dpeng): this should not be necessary; it's taken
  1.2882 +    # care of by the general 2nd-pc mechanism below.
  1.2883 +    foreach my $name ('ProfileData::Add',           # historical
  1.2884 +                      'ProfileData::prof_handler',  # historical
  1.2885 +                      'CpuProfiler::prof_handler',
  1.2886 +                      '__FRAME_END__',
  1.2887 +                      '__pthread_sighandler',
  1.2888 +                      '__restore') {
  1.2889 +      $skip{$name} = 1;
  1.2890 +    }
  1.2891 +  } else {
  1.2892 +    # Nothing skipped for unknown types
  1.2893 +  }
  1.2894 +
  1.2895 +  if ($main::profile_type eq 'cpu') {
  1.2896 +    # If all the second-youngest program counters are the same,
  1.2897 +    # this STRONGLY suggests that it is an artifact of measurement,
  1.2898 +    # i.e., stack frames pushed by the CPU profiler signal handler.
  1.2899 +    # Hence, we delete them.
  1.2900 +    # (The topmost PC is read from the signal structure, not from
  1.2901 +    # the stack, so it does not get involved.)
  1.2902 +    while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {
  1.2903 +      my $result = {};
  1.2904 +      my $func = '';
  1.2905 +      if (exists($symbols->{$second_pc})) {
  1.2906 +        $second_pc = $symbols->{$second_pc}->[0];
  1.2907 +      }
  1.2908 +      print STDERR "Removing $second_pc from all stack traces.\n";
  1.2909 +      foreach my $k (keys(%{$profile})) {
  1.2910 +        my $count = $profile->{$k};
  1.2911 +        my @addrs = split(/\n/, $k);
  1.2912 +        splice @addrs, 1, 1;
  1.2913 +        my $reduced_path = join("\n", @addrs);
  1.2914 +        AddEntry($result, $reduced_path, $count);
  1.2915 +      }
  1.2916 +      $profile = $result;
  1.2917 +    }
  1.2918 +  }
  1.2919 +
  1.2920 +  my $result = {};
  1.2921 +  foreach my $k (keys(%{$profile})) {
  1.2922 +    my $count = $profile->{$k};
  1.2923 +    my @addrs = split(/\n/, $k);
  1.2924 +    my @path = ();
  1.2925 +    foreach my $a (@addrs) {
  1.2926 +      if (exists($symbols->{$a})) {
  1.2927 +        my $func = $symbols->{$a}->[0];
  1.2928 +        if ($skip{$func} || ($func =~ m/$skip_regexp/)) {
  1.2929 +          next;
  1.2930 +        }
  1.2931 +      }
  1.2932 +      push(@path, $a);
  1.2933 +    }
  1.2934 +    my $reduced_path = join("\n", @path);
  1.2935 +    AddEntry($result, $reduced_path, $count);
  1.2936 +  }
  1.2937 +  return $result;
  1.2938 +}
  1.2939 +
  1.2940 +# Reduce profile to granularity given by user
  1.2941 +sub ReduceProfile {
  1.2942 +  my $symbols = shift;
  1.2943 +  my $profile = shift;
  1.2944 +  my $result = {};
  1.2945 +  my $fullname_to_shortname_map = {};
  1.2946 +  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
  1.2947 +  foreach my $k (keys(%{$profile})) {
  1.2948 +    my $count = $profile->{$k};
  1.2949 +    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
  1.2950 +    my @path = ();
  1.2951 +    my %seen = ();
  1.2952 +    $seen{''} = 1;      # So that empty keys are skipped
  1.2953 +    foreach my $e (@translated) {
  1.2954 +      # To avoid double-counting due to recursion, skip a stack-trace
  1.2955 +      # entry if it has already been seen
  1.2956 +      if (!$seen{$e}) {
  1.2957 +        $seen{$e} = 1;
  1.2958 +        push(@path, $e);
  1.2959 +      }
  1.2960 +    }
  1.2961 +    my $reduced_path = join("\n", @path);
  1.2962 +    AddEntry($result, $reduced_path, $count);
  1.2963 +  }
  1.2964 +  return $result;
  1.2965 +}
  1.2966 +
  1.2967 +# Does the specified symbol array match the regexp?
  1.2968 +sub SymbolMatches {
  1.2969 +  my $sym = shift;
  1.2970 +  my $re = shift;
  1.2971 +  if (defined($sym)) {
  1.2972 +    for (my $i = 0; $i < $#{$sym}; $i += 3) {
  1.2973 +      if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {
  1.2974 +        return 1;
  1.2975 +      }
  1.2976 +    }
  1.2977 +  }
  1.2978 +  return 0;
  1.2979 +}
  1.2980 +
  1.2981 +# Focus only on paths involving specified regexps
  1.2982 +sub FocusProfile {
  1.2983 +  my $symbols = shift;
  1.2984 +  my $profile = shift;
  1.2985 +  my $focus = shift;
  1.2986 +  my $result = {};
  1.2987 +  foreach my $k (keys(%{$profile})) {
  1.2988 +    my $count = $profile->{$k};
  1.2989 +    my @addrs = split(/\n/, $k);
  1.2990 +    foreach my $a (@addrs) {
  1.2991 +      # Reply if it matches either the address/shortname/fileline
  1.2992 +      if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {
  1.2993 +        AddEntry($result, $k, $count);
  1.2994 +        last;
  1.2995 +      }
  1.2996 +    }
  1.2997 +  }
  1.2998 +  return $result;
  1.2999 +}
  1.3000 +
  1.3001 +# Focus only on paths not involving specified regexps
  1.3002 +sub IgnoreProfile {
  1.3003 +  my $symbols = shift;
  1.3004 +  my $profile = shift;
  1.3005 +  my $ignore = shift;
  1.3006 +  my $result = {};
  1.3007 +  foreach my $k (keys(%{$profile})) {
  1.3008 +    my $count = $profile->{$k};
  1.3009 +    my @addrs = split(/\n/, $k);
  1.3010 +    my $matched = 0;
  1.3011 +    foreach my $a (@addrs) {
  1.3012 +      # Reply if it matches either the address/shortname/fileline
  1.3013 +      if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {
  1.3014 +        $matched = 1;
  1.3015 +        last;
  1.3016 +      }
  1.3017 +    }
  1.3018 +    if (!$matched) {
  1.3019 +      AddEntry($result, $k, $count);
  1.3020 +    }
  1.3021 +  }
  1.3022 +  return $result;
  1.3023 +}
  1.3024 +
  1.3025 +# Get total count in profile
  1.3026 +sub TotalProfile {
  1.3027 +  my $profile = shift;
  1.3028 +  my $result = 0;
  1.3029 +  foreach my $k (keys(%{$profile})) {
  1.3030 +    $result += $profile->{$k};
  1.3031 +  }
  1.3032 +  return $result;
  1.3033 +}
  1.3034 +
  1.3035 +# Add A to B
  1.3036 +sub AddProfile {
  1.3037 +  my $A = shift;
  1.3038 +  my $B = shift;
  1.3039 +
  1.3040 +  my $R = {};
  1.3041 +  # add all keys in A
  1.3042 +  foreach my $k (keys(%{$A})) {
  1.3043 +    my $v = $A->{$k};
  1.3044 +    AddEntry($R, $k, $v);
  1.3045 +  }
  1.3046 +  # add all keys in B
  1.3047 +  foreach my $k (keys(%{$B})) {
  1.3048 +    my $v = $B->{$k};
  1.3049 +    AddEntry($R, $k, $v);
  1.3050 +  }
  1.3051 +  return $R;
  1.3052 +}
  1.3053 +
  1.3054 +# Merges symbol maps
  1.3055 +sub MergeSymbols {
  1.3056 +  my $A = shift;
  1.3057 +  my $B = shift;
  1.3058 +
  1.3059 +  my $R = {};
  1.3060 +  foreach my $k (keys(%{$A})) {
  1.3061 +    $R->{$k} = $A->{$k};
  1.3062 +  }
  1.3063 +  if (defined($B)) {
  1.3064 +    foreach my $k (keys(%{$B})) {
  1.3065 +      $R->{$k} = $B->{$k};
  1.3066 +    }
  1.3067 +  }
  1.3068 +  return $R;
  1.3069 +}
  1.3070 +
  1.3071 +
  1.3072 +# Add A to B
  1.3073 +sub AddPcs {
  1.3074 +  my $A = shift;
  1.3075 +  my $B = shift;
  1.3076 +
  1.3077 +  my $R = {};
  1.3078 +  # add all keys in A
  1.3079 +  foreach my $k (keys(%{$A})) {
  1.3080 +    $R->{$k} = 1
  1.3081 +  }
  1.3082 +  # add all keys in B
  1.3083 +  foreach my $k (keys(%{$B})) {
  1.3084 +    $R->{$k} = 1
  1.3085 +  }
  1.3086 +  return $R;
  1.3087 +}
  1.3088 +
  1.3089 +# Subtract B from A
  1.3090 +sub SubtractProfile {
  1.3091 +  my $A = shift;
  1.3092 +  my $B = shift;
  1.3093 +
  1.3094 +  my $R = {};
  1.3095 +  foreach my $k (keys(%{$A})) {
  1.3096 +    my $v = $A->{$k} - GetEntry($B, $k);
  1.3097 +    if ($v < 0 && $main::opt_drop_negative) {
  1.3098 +      $v = 0;
  1.3099 +    }
  1.3100 +    AddEntry($R, $k, $v);
  1.3101 +  }
  1.3102 +  if (!$main::opt_drop_negative) {
  1.3103 +    # Take care of when subtracted profile has more entries
  1.3104 +    foreach my $k (keys(%{$B})) {
  1.3105 +      if (!exists($A->{$k})) {
  1.3106 +        AddEntry($R, $k, 0 - $B->{$k});
  1.3107 +      }
  1.3108 +    }
  1.3109 +  }
  1.3110 +  return $R;
  1.3111 +}
  1.3112 +
  1.3113 +# Get entry from profile; zero if not present
  1.3114 +sub GetEntry {
  1.3115 +  my $profile = shift;
  1.3116 +  my $k = shift;
  1.3117 +  if (exists($profile->{$k})) {
  1.3118 +    return $profile->{$k};
  1.3119 +  } else {
  1.3120 +    return 0;
  1.3121 +  }
  1.3122 +}
  1.3123 +
  1.3124 +# Add entry to specified profile
  1.3125 +sub AddEntry {
  1.3126 +  my $profile = shift;
  1.3127 +  my $k = shift;
  1.3128 +  my $n = shift;
  1.3129 +  if (!exists($profile->{$k})) {
  1.3130 +    $profile->{$k} = 0;
  1.3131 +  }
  1.3132 +  $profile->{$k} += $n;
  1.3133 +}
  1.3134 +
  1.3135 +# Add a stack of entries to specified profile, and add them to the $pcs
  1.3136 +# list.
  1.3137 +sub AddEntries {
  1.3138 +  my $profile = shift;
  1.3139 +  my $pcs = shift;
  1.3140 +  my $stack = shift;
  1.3141 +  my $count = shift;
  1.3142 +  my @k = ();
  1.3143 +
  1.3144 +  foreach my $e (split(/\s+/, $stack)) {
  1.3145 +    my $pc = HexExtend($e);
  1.3146 +    $pcs->{$pc} = 1;
  1.3147 +    push @k, $pc;
  1.3148 +  }
  1.3149 +  AddEntry($profile, (join "\n", @k), $count);
  1.3150 +}
  1.3151 +
  1.3152 +##### Code to profile a server dynamically #####
  1.3153 +
  1.3154 +sub CheckSymbolPage {
  1.3155 +  my $url = SymbolPageURL();
  1.3156 +  my $command = ShellEscape(@URL_FETCHER, $url);
  1.3157 +  open(SYMBOL, "$command |") or error($command);
  1.3158 +  my $line = <SYMBOL>;
  1.3159 +  $line =~ s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3160 +  close(SYMBOL);
  1.3161 +  unless (defined($line)) {
  1.3162 +    error("$url doesn't exist\n");
  1.3163 +  }
  1.3164 +
  1.3165 +  if ($line =~ /^num_symbols:\s+(\d+)$/) {
  1.3166 +    if ($1 == 0) {
  1.3167 +      error("Stripped binary. No symbols available.\n");
  1.3168 +    }
  1.3169 +  } else {
  1.3170 +    error("Failed to get the number of symbols from $url\n");
  1.3171 +  }
  1.3172 +}
  1.3173 +
  1.3174 +sub IsProfileURL {
  1.3175 +  my $profile_name = shift;
  1.3176 +  if (-f $profile_name) {
  1.3177 +    printf STDERR "Using local file $profile_name.\n";
  1.3178 +    return 0;
  1.3179 +  }
  1.3180 +  return 1;
  1.3181 +}
  1.3182 +
  1.3183 +sub ParseProfileURL {
  1.3184 +  my $profile_name = shift;
  1.3185 +
  1.3186 +  if (!defined($profile_name) || $profile_name eq "") {
  1.3187 +    return ();
  1.3188 +  }
  1.3189 +
  1.3190 +  # Split profile URL - matches all non-empty strings, so no test.
  1.3191 +  $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;
  1.3192 +
  1.3193 +  my $proto = $1 || "http://";
  1.3194 +  my $hostport = $2;
  1.3195 +  my $prefix = $3;
  1.3196 +  my $profile = $4 || "/";
  1.3197 +
  1.3198 +  my $host = $hostport;
  1.3199 +  $host =~ s/:.*//;
  1.3200 +
  1.3201 +  my $baseurl = "$proto$hostport$prefix";
  1.3202 +  return ($host, $baseurl, $profile);
  1.3203 +}
  1.3204 +
  1.3205 +# We fetch symbols from the first profile argument.
  1.3206 +sub SymbolPageURL {
  1.3207 +  my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
  1.3208 +  return "$baseURL$SYMBOL_PAGE";
  1.3209 +}
  1.3210 +
  1.3211 +sub FetchProgramName() {
  1.3212 +  my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);
  1.3213 +  my $url = "$baseURL$PROGRAM_NAME_PAGE";
  1.3214 +  my $command_line = ShellEscape(@URL_FETCHER, $url);
  1.3215 +  open(CMDLINE, "$command_line |") or error($command_line);
  1.3216 +  my $cmdline = <CMDLINE>;
  1.3217 +  $cmdline =~ s/\r//g;   # turn windows-looking lines into unix-looking lines
  1.3218 +  close(CMDLINE);
  1.3219 +  error("Failed to get program name from $url\n") unless defined($cmdline);
  1.3220 +  $cmdline =~ s/\x00.+//;  # Remove argv[1] and latters.
  1.3221 +  $cmdline =~ s!\n!!g;  # Remove LFs.
  1.3222 +  return $cmdline;
  1.3223 +}
  1.3224 +
  1.3225 +# Gee, curl's -L (--location) option isn't reliable at least
  1.3226 +# with its 7.12.3 version.  Curl will forget to post data if
  1.3227 +# there is a redirection.  This function is a workaround for
  1.3228 +# curl.  Redirection happens on borg hosts.
  1.3229 +sub ResolveRedirectionForCurl {
  1.3230 +  my $url = shift;
  1.3231 +  my $command_line = ShellEscape(@URL_FETCHER, "--head", $url);
  1.3232 +  open(CMDLINE, "$command_line |") or error($command_line);
  1.3233 +  while (<CMDLINE>) {
  1.3234 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3235 +    if (/^Location: (.*)/) {
  1.3236 +      $url = $1;
  1.3237 +    }
  1.3238 +  }
  1.3239 +  close(CMDLINE);
  1.3240 +  return $url;
  1.3241 +}
  1.3242 +
  1.3243 +# Add a timeout flat to URL_FETCHER.  Returns a new list.
  1.3244 +sub AddFetchTimeout {
  1.3245 +  my $timeout = shift;
  1.3246 +  my @fetcher = shift;
  1.3247 +  if (defined($timeout)) {
  1.3248 +    if (join(" ", @fetcher) =~ m/\bcurl -s/) {
  1.3249 +      push(@fetcher, "--max-time", sprintf("%d", $timeout));
  1.3250 +    } elsif (join(" ", @fetcher) =~ m/\brpcget\b/) {
  1.3251 +      push(@fetcher, sprintf("--deadline=%d", $timeout));
  1.3252 +    }
  1.3253 +  }
  1.3254 +  return @fetcher;
  1.3255 +}
  1.3256 +
  1.3257 +# Reads a symbol map from the file handle name given as $1, returning
  1.3258 +# the resulting symbol map.  Also processes variables relating to symbols.
  1.3259 +# Currently, the only variable processed is 'binary=<value>' which updates
  1.3260 +# $main::prog to have the correct program name.
  1.3261 +sub ReadSymbols {
  1.3262 +  my $in = shift;
  1.3263 +  my $map = {};
  1.3264 +  while (<$in>) {
  1.3265 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3266 +    # Removes all the leading zeroes from the symbols, see comment below.
  1.3267 +    if (m/^0x0*([0-9a-f]+)\s+(.+)/) {
  1.3268 +      $map->{$1} = $2;
  1.3269 +    } elsif (m/^---/) {
  1.3270 +      last;
  1.3271 +    } elsif (m/^([a-z][^=]*)=(.*)$/ ) {
  1.3272 +      my ($variable, $value) = ($1, $2);
  1.3273 +      for ($variable, $value) {
  1.3274 +        s/^\s+//;
  1.3275 +        s/\s+$//;
  1.3276 +      }
  1.3277 +      if ($variable eq "binary") {
  1.3278 +        if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {
  1.3279 +          printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n",
  1.3280 +                         $main::prog, $value);
  1.3281 +        }
  1.3282 +        $main::prog = $value;
  1.3283 +      } else {
  1.3284 +        printf STDERR ("Ignoring unknown variable in symbols list: " .
  1.3285 +            "'%s' = '%s'\n", $variable, $value);
  1.3286 +      }
  1.3287 +    }
  1.3288 +  }
  1.3289 +  return $map;
  1.3290 +}
  1.3291 +
  1.3292 +# Fetches and processes symbols to prepare them for use in the profile output
  1.3293 +# code.  If the optional 'symbol_map' arg is not given, fetches symbols from
  1.3294 +# $SYMBOL_PAGE for all PC values found in profile.  Otherwise, the raw symbols
  1.3295 +# are assumed to have already been fetched into 'symbol_map' and are simply
  1.3296 +# extracted and processed.
  1.3297 +sub FetchSymbols {
  1.3298 +  my $pcset = shift;
  1.3299 +  my $symbol_map = shift;
  1.3300 +
  1.3301 +  my %seen = ();
  1.3302 +  my @pcs = grep { !$seen{$_}++ } keys(%$pcset);  # uniq
  1.3303 +
  1.3304 +  if (!defined($symbol_map)) {
  1.3305 +    my $post_data = join("+", sort((map {"0x" . "$_"} @pcs)));
  1.3306 +
  1.3307 +    open(POSTFILE, ">$main::tmpfile_sym");
  1.3308 +    print POSTFILE $post_data;
  1.3309 +    close(POSTFILE);
  1.3310 +
  1.3311 +    my $url = SymbolPageURL();
  1.3312 +
  1.3313 +    my $command_line;
  1.3314 +    if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) {
  1.3315 +      $url = ResolveRedirectionForCurl($url);
  1.3316 +      $command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym",
  1.3317 +                                  $url);
  1.3318 +    } else {
  1.3319 +      $command_line = (ShellEscape(@URL_FETCHER, "--post", $url)
  1.3320 +                       . " < " . ShellEscape($main::tmpfile_sym));
  1.3321 +    }
  1.3322 +    # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.
  1.3323 +    my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"});
  1.3324 +    open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line);
  1.3325 +    $symbol_map = ReadSymbols(*SYMBOL{IO});
  1.3326 +    close(SYMBOL);
  1.3327 +  }
  1.3328 +
  1.3329 +  my $symbols = {};
  1.3330 +  foreach my $pc (@pcs) {
  1.3331 +    my $fullname;
  1.3332 +    # For 64 bits binaries, symbols are extracted with 8 leading zeroes.
  1.3333 +    # Then /symbol reads the long symbols in as uint64, and outputs
  1.3334 +    # the result with a "0x%08llx" format which get rid of the zeroes.
  1.3335 +    # By removing all the leading zeroes in both $pc and the symbols from
  1.3336 +    # /symbol, the symbols match and are retrievable from the map.
  1.3337 +    my $shortpc = $pc;
  1.3338 +    $shortpc =~ s/^0*//;
  1.3339 +    # Each line may have a list of names, which includes the function
  1.3340 +    # and also other functions it has inlined.  They are separated (in
  1.3341 +    # PrintSymbolizedProfile), by --, which is illegal in function names.
  1.3342 +    my $fullnames;
  1.3343 +    if (defined($symbol_map->{$shortpc})) {
  1.3344 +      $fullnames = $symbol_map->{$shortpc};
  1.3345 +    } else {
  1.3346 +      $fullnames = "0x" . $pc;  # Just use addresses
  1.3347 +    }
  1.3348 +    my $sym = [];
  1.3349 +    $symbols->{$pc} = $sym;
  1.3350 +    foreach my $fullname (split("--", $fullnames)) {
  1.3351 +      my $name = ShortFunctionName($fullname);
  1.3352 +      push(@{$sym}, $name, "?", $fullname);
  1.3353 +    }
  1.3354 +  }
  1.3355 +  return $symbols;
  1.3356 +}
  1.3357 +
  1.3358 +sub BaseName {
  1.3359 +  my $file_name = shift;
  1.3360 +  $file_name =~ s!^.*/!!;  # Remove directory name
  1.3361 +  return $file_name;
  1.3362 +}
  1.3363 +
  1.3364 +sub MakeProfileBaseName {
  1.3365 +  my ($binary_name, $profile_name) = @_;
  1.3366 +  my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
  1.3367 +  my $binary_shortname = BaseName($binary_name);
  1.3368 +  return sprintf("%s.%s.%s",
  1.3369 +                 $binary_shortname, $main::op_time, $host);
  1.3370 +}
  1.3371 +
  1.3372 +sub FetchDynamicProfile {
  1.3373 +  my $binary_name = shift;
  1.3374 +  my $profile_name = shift;
  1.3375 +  my $fetch_name_only = shift;
  1.3376 +  my $encourage_patience = shift;
  1.3377 +
  1.3378 +  if (!IsProfileURL($profile_name)) {
  1.3379 +    return $profile_name;
  1.3380 +  } else {
  1.3381 +    my ($host, $baseURL, $path) = ParseProfileURL($profile_name);
  1.3382 +    if ($path eq "" || $path eq "/") {
  1.3383 +      # Missing type specifier defaults to cpu-profile
  1.3384 +      $path = $PROFILE_PAGE;
  1.3385 +    }
  1.3386 +
  1.3387 +    my $profile_file = MakeProfileBaseName($binary_name, $profile_name);
  1.3388 +
  1.3389 +    my $url = "$baseURL$path";
  1.3390 +    my $fetch_timeout = undef;
  1.3391 +    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {
  1.3392 +      if ($path =~ m/[?]/) {
  1.3393 +        $url .= "&";
  1.3394 +      } else {
  1.3395 +        $url .= "?";
  1.3396 +      }
  1.3397 +      $url .= sprintf("seconds=%d", $main::opt_seconds);
  1.3398 +      $fetch_timeout = $main::opt_seconds * 1.01 + 60;
  1.3399 +    } else {
  1.3400 +      # For non-CPU profiles, we add a type-extension to
  1.3401 +      # the target profile file name.
  1.3402 +      my $suffix = $path;
  1.3403 +      $suffix =~ s,/,.,g;
  1.3404 +      $profile_file .= $suffix;
  1.3405 +    }
  1.3406 +
  1.3407 +    my $profile_dir = $ENV{"PPROF_TMPDIR"} || ($ENV{HOME} . "/pprof");
  1.3408 +    if (! -d $profile_dir) {
  1.3409 +      mkdir($profile_dir)
  1.3410 +          || die("Unable to create profile directory $profile_dir: $!\n");
  1.3411 +    }
  1.3412 +    my $tmp_profile = "$profile_dir/.tmp.$profile_file";
  1.3413 +    my $real_profile = "$profile_dir/$profile_file";
  1.3414 +
  1.3415 +    if ($fetch_name_only > 0) {
  1.3416 +      return $real_profile;
  1.3417 +    }
  1.3418 +
  1.3419 +    my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);
  1.3420 +    my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile);
  1.3421 +    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){
  1.3422 +      print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n  ${real_profile}\n";
  1.3423 +      if ($encourage_patience) {
  1.3424 +        print STDERR "Be patient...\n";
  1.3425 +      }
  1.3426 +    } else {
  1.3427 +      print STDERR "Fetching $path profile from $url to\n  ${real_profile}\n";
  1.3428 +    }
  1.3429 +
  1.3430 +    (system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
  1.3431 +    (system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n");
  1.3432 +    print STDERR "Wrote profile to $real_profile\n";
  1.3433 +    $main::collected_profile = $real_profile;
  1.3434 +    return $main::collected_profile;
  1.3435 +  }
  1.3436 +}
  1.3437 +
  1.3438 +# Collect profiles in parallel
  1.3439 +sub FetchDynamicProfiles {
  1.3440 +  my $items = scalar(@main::pfile_args);
  1.3441 +  my $levels = log($items) / log(2);
  1.3442 +
  1.3443 +  if ($items == 1) {
  1.3444 +    $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1);
  1.3445 +  } else {
  1.3446 +    # math rounding issues
  1.3447 +    if ((2 ** $levels) < $items) {
  1.3448 +     $levels++;
  1.3449 +    }
  1.3450 +    my $count = scalar(@main::pfile_args);
  1.3451 +    for (my $i = 0; $i < $count; $i++) {
  1.3452 +      $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0);
  1.3453 +    }
  1.3454 +    print STDERR "Fetching $count profiles, Be patient...\n";
  1.3455 +    FetchDynamicProfilesRecurse($levels, 0, 0);
  1.3456 +    $main::collected_profile = join(" \\\n    ", @main::profile_files);
  1.3457 +  }
  1.3458 +}
  1.3459 +
  1.3460 +# Recursively fork a process to get enough processes
  1.3461 +# collecting profiles
  1.3462 +sub FetchDynamicProfilesRecurse {
  1.3463 +  my $maxlevel = shift;
  1.3464 +  my $level = shift;
  1.3465 +  my $position = shift;
  1.3466 +
  1.3467 +  if (my $pid = fork()) {
  1.3468 +    $position = 0 | ($position << 1);
  1.3469 +    TryCollectProfile($maxlevel, $level, $position);
  1.3470 +    wait;
  1.3471 +  } else {
  1.3472 +    $position = 1 | ($position << 1);
  1.3473 +    TryCollectProfile($maxlevel, $level, $position);
  1.3474 +    cleanup();
  1.3475 +    exit(0);
  1.3476 +  }
  1.3477 +}
  1.3478 +
  1.3479 +# Collect a single profile
  1.3480 +sub TryCollectProfile {
  1.3481 +  my $maxlevel = shift;
  1.3482 +  my $level = shift;
  1.3483 +  my $position = shift;
  1.3484 +
  1.3485 +  if ($level >= ($maxlevel - 1)) {
  1.3486 +    if ($position < scalar(@main::pfile_args)) {
  1.3487 +      FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);
  1.3488 +    }
  1.3489 +  } else {
  1.3490 +    FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);
  1.3491 +  }
  1.3492 +}
  1.3493 +
  1.3494 +##### Parsing code #####
  1.3495 +
  1.3496 +# Provide a small streaming-read module to handle very large
  1.3497 +# cpu-profile files.  Stream in chunks along a sliding window.
  1.3498 +# Provides an interface to get one 'slot', correctly handling
  1.3499 +# endian-ness differences.  A slot is one 32-bit or 64-bit word
  1.3500 +# (depending on the input profile).  We tell endianness and bit-size
  1.3501 +# for the profile by looking at the first 8 bytes: in cpu profiles,
  1.3502 +# the second slot is always 3 (we'll accept anything that's not 0).
  1.3503 +BEGIN {
  1.3504 +  package CpuProfileStream;
  1.3505 +
  1.3506 +  sub new {
  1.3507 +    my ($class, $file, $fname) = @_;
  1.3508 +    my $self = { file        => $file,
  1.3509 +                 base        => 0,
  1.3510 +                 stride      => 512 * 1024,   # must be a multiple of bitsize/8
  1.3511 +                 slots       => [],
  1.3512 +                 unpack_code => "",           # N for big-endian, V for little
  1.3513 +                 perl_is_64bit => 1,          # matters if profile is 64-bit
  1.3514 +    };
  1.3515 +    bless $self, $class;
  1.3516 +    # Let unittests adjust the stride
  1.3517 +    if ($main::opt_test_stride > 0) {
  1.3518 +      $self->{stride} = $main::opt_test_stride;
  1.3519 +    }
  1.3520 +    # Read the first two slots to figure out bitsize and endianness.
  1.3521 +    my $slots = $self->{slots};
  1.3522 +    my $str;
  1.3523 +    read($self->{file}, $str, 8);
  1.3524 +    # Set the global $address_length based on what we see here.
  1.3525 +    # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars).
  1.3526 +    $address_length = ($str eq (chr(0)x8)) ? 16 : 8;
  1.3527 +    if ($address_length == 8) {
  1.3528 +      if (substr($str, 6, 2) eq chr(0)x2) {
  1.3529 +        $self->{unpack_code} = 'V';  # Little-endian.
  1.3530 +      } elsif (substr($str, 4, 2) eq chr(0)x2) {
  1.3531 +        $self->{unpack_code} = 'N';  # Big-endian
  1.3532 +      } else {
  1.3533 +        ::error("$fname: header size >= 2**16\n");
  1.3534 +      }
  1.3535 +      @$slots = unpack($self->{unpack_code} . "*", $str);
  1.3536 +    } else {
  1.3537 +      # If we're a 64-bit profile, check if we're a 64-bit-capable
  1.3538 +      # perl.  Otherwise, each slot will be represented as a float
  1.3539 +      # instead of an int64, losing precision and making all the
  1.3540 +      # 64-bit addresses wrong.  We won't complain yet, but will
  1.3541 +      # later if we ever see a value that doesn't fit in 32 bits.
  1.3542 +      my $has_q = 0;
  1.3543 +      eval { $has_q = pack("Q", "1") ? 1 : 1; };
  1.3544 +      if (!$has_q) {
  1.3545 +        $self->{perl_is_64bit} = 0;
  1.3546 +      }
  1.3547 +      read($self->{file}, $str, 8);
  1.3548 +      if (substr($str, 4, 4) eq chr(0)x4) {
  1.3549 +        # We'd love to use 'Q', but it's a) not universal, b) not endian-proof.
  1.3550 +        $self->{unpack_code} = 'V';  # Little-endian.
  1.3551 +      } elsif (substr($str, 0, 4) eq chr(0)x4) {
  1.3552 +        $self->{unpack_code} = 'N';  # Big-endian
  1.3553 +      } else {
  1.3554 +        ::error("$fname: header size >= 2**32\n");
  1.3555 +      }
  1.3556 +      my @pair = unpack($self->{unpack_code} . "*", $str);
  1.3557 +      # Since we know one of the pair is 0, it's fine to just add them.
  1.3558 +      @$slots = (0, $pair[0] + $pair[1]);
  1.3559 +    }
  1.3560 +    return $self;
  1.3561 +  }
  1.3562 +
  1.3563 +  # Load more data when we access slots->get(X) which is not yet in memory.
  1.3564 +  sub overflow {
  1.3565 +    my ($self) = @_;
  1.3566 +    my $slots = $self->{slots};
  1.3567 +    $self->{base} += $#$slots + 1;   # skip over data we're replacing
  1.3568 +    my $str;
  1.3569 +    read($self->{file}, $str, $self->{stride});
  1.3570 +    if ($address_length == 8) {      # the 32-bit case
  1.3571 +      # This is the easy case: unpack provides 32-bit unpacking primitives.
  1.3572 +      @$slots = unpack($self->{unpack_code} . "*", $str);
  1.3573 +    } else {
  1.3574 +      # We need to unpack 32 bits at a time and combine.
  1.3575 +      my @b32_values = unpack($self->{unpack_code} . "*", $str);
  1.3576 +      my @b64_values = ();
  1.3577 +      for (my $i = 0; $i < $#b32_values; $i += 2) {
  1.3578 +        # TODO(csilvers): if this is a 32-bit perl, the math below
  1.3579 +        #    could end up in a too-large int, which perl will promote
  1.3580 +        #    to a double, losing necessary precision.  Deal with that.
  1.3581 +        #    Right now, we just die.
  1.3582 +        my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);
  1.3583 +        if ($self->{unpack_code} eq 'N') {    # big-endian
  1.3584 +          ($lo, $hi) = ($hi, $lo);
  1.3585 +        }
  1.3586 +        my $value = $lo + $hi * (2**32);
  1.3587 +        if (!$self->{perl_is_64bit} &&   # check value is exactly represented
  1.3588 +            (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {
  1.3589 +          ::error("Need a 64-bit perl to process this 64-bit profile.\n");
  1.3590 +        }
  1.3591 +        push(@b64_values, $value);
  1.3592 +      }
  1.3593 +      @$slots = @b64_values;
  1.3594 +    }
  1.3595 +  }
  1.3596 +
  1.3597 +  # Access the i-th long in the file (logically), or -1 at EOF.
  1.3598 +  sub get {
  1.3599 +    my ($self, $idx) = @_;
  1.3600 +    my $slots = $self->{slots};
  1.3601 +    while ($#$slots >= 0) {
  1.3602 +      if ($idx < $self->{base}) {
  1.3603 +        # The only time we expect a reference to $slots[$i - something]
  1.3604 +        # after referencing $slots[$i] is reading the very first header.
  1.3605 +        # Since $stride > |header|, that shouldn't cause any lookback
  1.3606 +        # errors.  And everything after the header is sequential.
  1.3607 +        print STDERR "Unexpected look-back reading CPU profile";
  1.3608 +        return -1;   # shrug, don't know what better to return
  1.3609 +      } elsif ($idx > $self->{base} + $#$slots) {
  1.3610 +        $self->overflow();
  1.3611 +      } else {
  1.3612 +        return $slots->[$idx - $self->{base}];
  1.3613 +      }
  1.3614 +    }
  1.3615 +    # If we get here, $slots is [], which means we've reached EOF
  1.3616 +    return -1;  # unique since slots is supposed to hold unsigned numbers
  1.3617 +  }
  1.3618 +}
  1.3619 +
  1.3620 +# Reads the top, 'header' section of a profile, and returns the last
  1.3621 +# line of the header, commonly called a 'header line'.  The header
  1.3622 +# section of a profile consists of zero or more 'command' lines that
  1.3623 +# are instructions to pprof, which pprof executes when reading the
  1.3624 +# header.  All 'command' lines start with a %.  After the command
  1.3625 +# lines is the 'header line', which is a profile-specific line that
  1.3626 +# indicates what type of profile it is, and perhaps other global
  1.3627 +# information about the profile.  For instance, here's a header line
  1.3628 +# for a heap profile:
  1.3629 +#   heap profile:     53:    38236 [  5525:  1284029] @ heapprofile
  1.3630 +# For historical reasons, the CPU profile does not contain a text-
  1.3631 +# readable header line.  If the profile looks like a CPU profile,
  1.3632 +# this function returns "".  If no header line could be found, this
  1.3633 +# function returns undef.
  1.3634 +#
  1.3635 +# The following commands are recognized:
  1.3636 +#   %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'
  1.3637 +#
  1.3638 +# The input file should be in binmode.
  1.3639 +sub ReadProfileHeader {
  1.3640 +  local *PROFILE = shift;
  1.3641 +  my $firstchar = "";
  1.3642 +  my $line = "";
  1.3643 +  read(PROFILE, $firstchar, 1);
  1.3644 +  seek(PROFILE, -1, 1);                    # unread the firstchar
  1.3645 +  if ($firstchar !~ /[[:print:]]/) {       # is not a text character
  1.3646 +    return "";
  1.3647 +  }
  1.3648 +  while (defined($line = <PROFILE>)) {
  1.3649 +    $line =~ s/\r//g;   # turn windows-looking lines into unix-looking lines
  1.3650 +    if ($line =~ /^%warn\s+(.*)/) {        # 'warn' command
  1.3651 +      # Note this matches both '%warn blah\n' and '%warn\n'.
  1.3652 +      print STDERR "WARNING: $1\n";        # print the rest of the line
  1.3653 +    } elsif ($line =~ /^%/) {
  1.3654 +      print STDERR "Ignoring unknown command from profile header: $line";
  1.3655 +    } else {
  1.3656 +      # End of commands, must be the header line.
  1.3657 +      return $line;
  1.3658 +    }
  1.3659 +  }
  1.3660 +  return undef;     # got to EOF without seeing a header line
  1.3661 +}
  1.3662 +
  1.3663 +sub IsSymbolizedProfileFile {
  1.3664 +  my $file_name = shift;
  1.3665 +  if (!(-e $file_name) || !(-r $file_name)) {
  1.3666 +    return 0;
  1.3667 +  }
  1.3668 +  # Check if the file contains a symbol-section marker.
  1.3669 +  open(TFILE, "<$file_name");
  1.3670 +  binmode TFILE;
  1.3671 +  my $firstline = ReadProfileHeader(*TFILE);
  1.3672 +  close(TFILE);
  1.3673 +  if (!$firstline) {
  1.3674 +    return 0;
  1.3675 +  }
  1.3676 +  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.3677 +  my $symbol_marker = $&;
  1.3678 +  return $firstline =~ /^--- *$symbol_marker/;
  1.3679 +}
  1.3680 +
  1.3681 +# Parse profile generated by common/profiler.cc and return a reference
  1.3682 +# to a map:
  1.3683 +#      $result->{version}     Version number of profile file
  1.3684 +#      $result->{period}      Sampling period (in microseconds)
  1.3685 +#      $result->{profile}     Profile object
  1.3686 +#      $result->{map}         Memory map info from profile
  1.3687 +#      $result->{pcs}         Hash of all PC values seen, key is hex address
  1.3688 +sub ReadProfile {
  1.3689 +  my $prog = shift;
  1.3690 +  my $fname = shift;
  1.3691 +  my $result;            # return value
  1.3692 +
  1.3693 +  $CONTENTION_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.3694 +  my $contention_marker = $&;
  1.3695 +  $GROWTH_PAGE  =~ m,[^/]+$,;    # matches everything after the last slash
  1.3696 +  my $growth_marker = $&;
  1.3697 +  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.3698 +  my $symbol_marker = $&;
  1.3699 +  $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash
  1.3700 +  my $profile_marker = $&;
  1.3701 +
  1.3702 +  # Look at first line to see if it is a heap or a CPU profile.
  1.3703 +  # CPU profile may start with no header at all, and just binary data
  1.3704 +  # (starting with \0\0\0\0) -- in that case, don't try to read the
  1.3705 +  # whole firstline, since it may be gigabytes(!) of data.
  1.3706 +  open(PROFILE, "<$fname") || error("$fname: $!\n");
  1.3707 +  binmode PROFILE;      # New perls do UTF-8 processing
  1.3708 +  my $header = ReadProfileHeader(*PROFILE);
  1.3709 +  if (!defined($header)) {   # means "at EOF"
  1.3710 +    error("Profile is empty.\n");
  1.3711 +  }
  1.3712 +
  1.3713 +  my $symbols;
  1.3714 +  if ($header =~ m/^--- *$symbol_marker/o) {
  1.3715 +    # Verify that the user asked for a symbolized profile
  1.3716 +    if (!$main::use_symbolized_profile) {
  1.3717 +      # we have both a binary and symbolized profiles, abort
  1.3718 +      error("FATAL ERROR: Symbolized profile\n   $fname\ncannot be used with " .
  1.3719 +            "a binary arg. Try again without passing\n   $prog\n");
  1.3720 +    }
  1.3721 +    # Read the symbol section of the symbolized profile file.
  1.3722 +    $symbols = ReadSymbols(*PROFILE{IO});
  1.3723 +    # Read the next line to get the header for the remaining profile.
  1.3724 +    $header = ReadProfileHeader(*PROFILE) || "";
  1.3725 +  }
  1.3726 +
  1.3727 +  $main::profile_type = '';
  1.3728 +  if ($header =~ m/^heap profile:.*$growth_marker/o) {
  1.3729 +    $main::profile_type = 'growth';
  1.3730 +    $result =  ReadHeapProfile($prog, *PROFILE, $header);
  1.3731 +  } elsif ($header =~ m/^heap profile:/) {
  1.3732 +    $main::profile_type = 'heap';
  1.3733 +    $result =  ReadHeapProfile($prog, *PROFILE, $header);
  1.3734 +  } elsif ($header =~ m/^--- *$contention_marker/o) {
  1.3735 +    $main::profile_type = 'contention';
  1.3736 +    $result = ReadSynchProfile($prog, *PROFILE);
  1.3737 +  } elsif ($header =~ m/^--- *Stacks:/) {
  1.3738 +    print STDERR
  1.3739 +      "Old format contention profile: mistakenly reports " .
  1.3740 +      "condition variable signals as lock contentions.\n";
  1.3741 +    $main::profile_type = 'contention';
  1.3742 +    $result = ReadSynchProfile($prog, *PROFILE);
  1.3743 +  } elsif ($header =~ m/^--- *$profile_marker/) {
  1.3744 +    # the binary cpu profile data starts immediately after this line
  1.3745 +    $main::profile_type = 'cpu';
  1.3746 +    $result = ReadCPUProfile($prog, $fname, *PROFILE);
  1.3747 +  } else {
  1.3748 +    if (defined($symbols)) {
  1.3749 +      # a symbolized profile contains a format we don't recognize, bail out
  1.3750 +      error("$fname: Cannot recognize profile section after symbols.\n");
  1.3751 +    }
  1.3752 +    # no ascii header present -- must be a CPU profile
  1.3753 +    $main::profile_type = 'cpu';
  1.3754 +    $result = ReadCPUProfile($prog, $fname, *PROFILE);
  1.3755 +  }
  1.3756 +
  1.3757 +  close(PROFILE);
  1.3758 +
  1.3759 +  # if we got symbols along with the profile, return those as well
  1.3760 +  if (defined($symbols)) {
  1.3761 +    $result->{symbols} = $symbols;
  1.3762 +  }
  1.3763 +
  1.3764 +  return $result;
  1.3765 +}
  1.3766 +
  1.3767 +# Subtract one from caller pc so we map back to call instr.
  1.3768 +# However, don't do this if we're reading a symbolized profile
  1.3769 +# file, in which case the subtract-one was done when the file
  1.3770 +# was written.
  1.3771 +#
  1.3772 +# We apply the same logic to all readers, though ReadCPUProfile uses an
  1.3773 +# independent implementation.
  1.3774 +sub FixCallerAddresses {
  1.3775 +  my $stack = shift;
  1.3776 +  if ($main::use_symbolized_profile) {
  1.3777 +    return $stack;
  1.3778 +  } else {
  1.3779 +    $stack =~ /(\s)/;
  1.3780 +    my $delimiter = $1;
  1.3781 +    my @addrs = split(' ', $stack);
  1.3782 +    my @fixedaddrs;
  1.3783 +    $#fixedaddrs = $#addrs;
  1.3784 +    if ($#addrs >= 0) {
  1.3785 +      $fixedaddrs[0] = $addrs[0];
  1.3786 +    }
  1.3787 +    for (my $i = 1; $i <= $#addrs; $i++) {
  1.3788 +      $fixedaddrs[$i] = AddressSub($addrs[$i], "0x1");
  1.3789 +    }
  1.3790 +    return join $delimiter, @fixedaddrs;
  1.3791 +  }
  1.3792 +}
  1.3793 +
  1.3794 +# CPU profile reader
  1.3795 +sub ReadCPUProfile {
  1.3796 +  my $prog = shift;
  1.3797 +  my $fname = shift;       # just used for logging
  1.3798 +  local *PROFILE = shift;
  1.3799 +  my $version;
  1.3800 +  my $period;
  1.3801 +  my $i;
  1.3802 +  my $profile = {};
  1.3803 +  my $pcs = {};
  1.3804 +
  1.3805 +  # Parse string into array of slots.
  1.3806 +  my $slots = CpuProfileStream->new(*PROFILE, $fname);
  1.3807 +
  1.3808 +  # Read header.  The current header version is a 5-element structure
  1.3809 +  # containing:
  1.3810 +  #   0: header count (always 0)
  1.3811 +  #   1: header "words" (after this one: 3)
  1.3812 +  #   2: format version (0)
  1.3813 +  #   3: sampling period (usec)
  1.3814 +  #   4: unused padding (always 0)
  1.3815 +  if ($slots->get(0) != 0 ) {
  1.3816 +    error("$fname: not a profile file, or old format profile file\n");
  1.3817 +  }
  1.3818 +  $i = 2 + $slots->get(1);
  1.3819 +  $version = $slots->get(2);
  1.3820 +  $period = $slots->get(3);
  1.3821 +  # Do some sanity checking on these header values.
  1.3822 +  if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {
  1.3823 +    error("$fname: not a profile file, or corrupted profile file\n");
  1.3824 +  }
  1.3825 +
  1.3826 +  # Parse profile
  1.3827 +  while ($slots->get($i) != -1) {
  1.3828 +    my $n = $slots->get($i++);
  1.3829 +    my $d = $slots->get($i++);
  1.3830 +    if ($d > (2**16)) {  # TODO(csilvers): what's a reasonable max-stack-depth?
  1.3831 +      my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8));
  1.3832 +      print STDERR "At index $i (address $addr):\n";
  1.3833 +      error("$fname: stack trace depth >= 2**32\n");
  1.3834 +    }
  1.3835 +    if ($slots->get($i) == 0) {
  1.3836 +      # End of profile data marker
  1.3837 +      $i += $d;
  1.3838 +      last;
  1.3839 +    }
  1.3840 +
  1.3841 +    # Make key out of the stack entries
  1.3842 +    my @k = ();
  1.3843 +    for (my $j = 0; $j < $d; $j++) {
  1.3844 +      my $pc = $slots->get($i+$j);
  1.3845 +      # Subtract one from caller pc so we map back to call instr.
  1.3846 +      # However, don't do this if we're reading a symbolized profile
  1.3847 +      # file, in which case the subtract-one was done when the file
  1.3848 +      # was written.
  1.3849 +      if ($j > 0 && !$main::use_symbolized_profile) {
  1.3850 +        $pc--;
  1.3851 +      }
  1.3852 +      $pc = sprintf("%0*x", $address_length, $pc);
  1.3853 +      $pcs->{$pc} = 1;
  1.3854 +      push @k, $pc;
  1.3855 +    }
  1.3856 +
  1.3857 +    AddEntry($profile, (join "\n", @k), $n);
  1.3858 +    $i += $d;
  1.3859 +  }
  1.3860 +
  1.3861 +  # Parse map
  1.3862 +  my $map = '';
  1.3863 +  seek(PROFILE, $i * 4, 0);
  1.3864 +  read(PROFILE, $map, (stat PROFILE)[7]);
  1.3865 +
  1.3866 +  my $r = {};
  1.3867 +  $r->{version} = $version;
  1.3868 +  $r->{period} = $period;
  1.3869 +  $r->{profile} = $profile;
  1.3870 +  $r->{libs} = ParseLibraries($prog, $map, $pcs);
  1.3871 +  $r->{pcs} = $pcs;
  1.3872 +
  1.3873 +  return $r;
  1.3874 +}
  1.3875 +
  1.3876 +sub ReadHeapProfile {
  1.3877 +  my $prog = shift;
  1.3878 +  local *PROFILE = shift;
  1.3879 +  my $header = shift;
  1.3880 +
  1.3881 +  my $index = 1;
  1.3882 +  if ($main::opt_inuse_space) {
  1.3883 +    $index = 1;
  1.3884 +  } elsif ($main::opt_inuse_objects) {
  1.3885 +    $index = 0;
  1.3886 +  } elsif ($main::opt_alloc_space) {
  1.3887 +    $index = 3;
  1.3888 +  } elsif ($main::opt_alloc_objects) {
  1.3889 +    $index = 2;
  1.3890 +  }
  1.3891 +
  1.3892 +  # Find the type of this profile.  The header line looks like:
  1.3893 +  #    heap profile:   1246:  8800744 [  1246:  8800744] @ <heap-url>/266053
  1.3894 +  # There are two pairs <count: size>, the first inuse objects/space, and the
  1.3895 +  # second allocated objects/space.  This is followed optionally by a profile
  1.3896 +  # type, and if that is present, optionally by a sampling frequency.
  1.3897 +  # For remote heap profiles (v1):
  1.3898 +  # The interpretation of the sampling frequency is that the profiler, for
  1.3899 +  # each sample, calculates a uniformly distributed random integer less than
  1.3900 +  # the given value, and records the next sample after that many bytes have
  1.3901 +  # been allocated.  Therefore, the expected sample interval is half of the
  1.3902 +  # given frequency.  By default, if not specified, the expected sample
  1.3903 +  # interval is 128KB.  Only remote-heap-page profiles are adjusted for
  1.3904 +  # sample size.
  1.3905 +  # For remote heap profiles (v2):
  1.3906 +  # The sampling frequency is the rate of a Poisson process. This means that
  1.3907 +  # the probability of sampling an allocation of size X with sampling rate Y
  1.3908 +  # is 1 - exp(-X/Y)
  1.3909 +  # For version 2, a typical header line might look like this:
  1.3910 +  # heap profile:   1922: 127792360 [  1922: 127792360] @ <heap-url>_v2/524288
  1.3911 +  # the trailing number (524288) is the sampling rate. (Version 1 showed
  1.3912 +  # double the 'rate' here)
  1.3913 +  my $sampling_algorithm = 0;
  1.3914 +  my $sample_adjustment = 0;
  1.3915 +  chomp($header);
  1.3916 +  my $type = "unknown";
  1.3917 +  if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") {
  1.3918 +    if (defined($6) && ($6 ne '')) {
  1.3919 +      $type = $6;
  1.3920 +      my $sample_period = $8;
  1.3921 +      # $type is "heapprofile" for profiles generated by the
  1.3922 +      # heap-profiler, and either "heap" or "heap_v2" for profiles
  1.3923 +      # generated by sampling directly within tcmalloc.  It can also
  1.3924 +      # be "growth" for heap-growth profiles.  The first is typically
  1.3925 +      # found for profiles generated locally, and the others for
  1.3926 +      # remote profiles.
  1.3927 +      if (($type eq "heapprofile") || ($type !~ /heap/) ) {
  1.3928 +        # No need to adjust for the sampling rate with heap-profiler-derived data
  1.3929 +        $sampling_algorithm = 0;
  1.3930 +      } elsif ($type =~ /_v2/) {
  1.3931 +        $sampling_algorithm = 2;     # version 2 sampling
  1.3932 +        if (defined($sample_period) && ($sample_period ne '')) {
  1.3933 +          $sample_adjustment = int($sample_period);
  1.3934 +        }
  1.3935 +      } else {
  1.3936 +        $sampling_algorithm = 1;     # version 1 sampling
  1.3937 +        if (defined($sample_period) && ($sample_period ne '')) {
  1.3938 +          $sample_adjustment = int($sample_period)/2;
  1.3939 +        }
  1.3940 +      }
  1.3941 +    } else {
  1.3942 +      # We detect whether or not this is a remote-heap profile by checking
  1.3943 +      # that the total-allocated stats ($n2,$s2) are exactly the
  1.3944 +      # same as the in-use stats ($n1,$s1).  It is remotely conceivable
  1.3945 +      # that a non-remote-heap profile may pass this check, but it is hard
  1.3946 +      # to imagine how that could happen.
  1.3947 +      # In this case it's so old it's guaranteed to be remote-heap version 1.
  1.3948 +      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
  1.3949 +      if (($n1 == $n2) && ($s1 == $s2)) {
  1.3950 +        # This is likely to be a remote-heap based sample profile
  1.3951 +        $sampling_algorithm = 1;
  1.3952 +      }
  1.3953 +    }
  1.3954 +  }
  1.3955 +
  1.3956 +  if ($sampling_algorithm > 0) {
  1.3957 +    # For remote-heap generated profiles, adjust the counts and sizes to
  1.3958 +    # account for the sample rate (we sample once every 128KB by default).
  1.3959 +    if ($sample_adjustment == 0) {
  1.3960 +      # Turn on profile adjustment.
  1.3961 +      $sample_adjustment = 128*1024;
  1.3962 +      print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n";
  1.3963 +    } else {
  1.3964 +      printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n",
  1.3965 +                     $sample_adjustment);
  1.3966 +    }
  1.3967 +    if ($sampling_algorithm > 1) {
  1.3968 +      # We don't bother printing anything for the original version (version 1)
  1.3969 +      printf STDERR "Heap version $sampling_algorithm\n";
  1.3970 +    }
  1.3971 +  }
  1.3972 +
  1.3973 +  my $profile = {};
  1.3974 +  my $pcs = {};
  1.3975 +  my $map = "";
  1.3976 +
  1.3977 +  while (<PROFILE>) {
  1.3978 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3979 +    if (/^MAPPED_LIBRARIES:/) {
  1.3980 +      # Read the /proc/self/maps data
  1.3981 +      while (<PROFILE>) {
  1.3982 +        s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3983 +        $map .= $_;
  1.3984 +      }
  1.3985 +      last;
  1.3986 +    }
  1.3987 +
  1.3988 +    if (/^--- Memory map:/) {
  1.3989 +      # Read /proc/self/maps data as formatted by DumpAddressMap()
  1.3990 +      my $buildvar = "";
  1.3991 +      while (<PROFILE>) {
  1.3992 +        s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.3993 +        # Parse "build=<dir>" specification if supplied
  1.3994 +        if (m/^\s*build=(.*)\n/) {
  1.3995 +          $buildvar = $1;
  1.3996 +        }
  1.3997 +
  1.3998 +        # Expand "$build" variable if available
  1.3999 +        $_ =~ s/\$build\b/$buildvar/g;
  1.4000 +
  1.4001 +        $map .= $_;
  1.4002 +      }
  1.4003 +      last;
  1.4004 +    }
  1.4005 +
  1.4006 +    # Read entry of the form:
  1.4007 +    #  <count1>: <bytes1> [<count2>: <bytes2>] @ a1 a2 a3 ... an
  1.4008 +    s/^\s*//;
  1.4009 +    s/\s*$//;
  1.4010 +    if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) {
  1.4011 +      my $stack = $5;
  1.4012 +      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);
  1.4013 +
  1.4014 +      if ($sample_adjustment) {
  1.4015 +        if ($sampling_algorithm == 2) {
  1.4016 +          # Remote-heap version 2
  1.4017 +          # The sampling frequency is the rate of a Poisson process.
  1.4018 +          # This means that the probability of sampling an allocation of
  1.4019 +          # size X with sampling rate Y is 1 - exp(-X/Y)
  1.4020 +          if ($n1 != 0) {
  1.4021 +            my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
  1.4022 +            my $scale_factor = 1/(1 - exp(-$ratio));
  1.4023 +            $n1 *= $scale_factor;
  1.4024 +            $s1 *= $scale_factor;
  1.4025 +          }
  1.4026 +          if ($n2 != 0) {
  1.4027 +            my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
  1.4028 +            my $scale_factor = 1/(1 - exp(-$ratio));
  1.4029 +            $n2 *= $scale_factor;
  1.4030 +            $s2 *= $scale_factor;
  1.4031 +          }
  1.4032 +        } else {
  1.4033 +          # Remote-heap version 1
  1.4034 +          my $ratio;
  1.4035 +          $ratio = (($s1*1.0)/$n1)/($sample_adjustment);
  1.4036 +          if ($ratio < 1) {
  1.4037 +            $n1 /= $ratio;
  1.4038 +            $s1 /= $ratio;
  1.4039 +          }
  1.4040 +          $ratio = (($s2*1.0)/$n2)/($sample_adjustment);
  1.4041 +          if ($ratio < 1) {
  1.4042 +            $n2 /= $ratio;
  1.4043 +            $s2 /= $ratio;
  1.4044 +          }
  1.4045 +        }
  1.4046 +      }
  1.4047 +
  1.4048 +      my @counts = ($n1, $s1, $n2, $s2);
  1.4049 +      AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);
  1.4050 +    }
  1.4051 +  }
  1.4052 +
  1.4053 +  my $r = {};
  1.4054 +  $r->{version} = "heap";
  1.4055 +  $r->{period} = 1;
  1.4056 +  $r->{profile} = $profile;
  1.4057 +  $r->{libs} = ParseLibraries($prog, $map, $pcs);
  1.4058 +  $r->{pcs} = $pcs;
  1.4059 +  return $r;
  1.4060 +}
  1.4061 +
  1.4062 +sub ReadSynchProfile {
  1.4063 +  my $prog = shift;
  1.4064 +  local *PROFILE = shift;
  1.4065 +  my $header = shift;
  1.4066 +
  1.4067 +  my $map = '';
  1.4068 +  my $profile = {};
  1.4069 +  my $pcs = {};
  1.4070 +  my $sampling_period = 1;
  1.4071 +  my $cyclespernanosec = 2.8;   # Default assumption for old binaries
  1.4072 +  my $seen_clockrate = 0;
  1.4073 +  my $line;
  1.4074 +
  1.4075 +  my $index = 0;
  1.4076 +  if ($main::opt_total_delay) {
  1.4077 +    $index = 0;
  1.4078 +  } elsif ($main::opt_contentions) {
  1.4079 +    $index = 1;
  1.4080 +  } elsif ($main::opt_mean_delay) {
  1.4081 +    $index = 2;
  1.4082 +  }
  1.4083 +
  1.4084 +  while ( $line = <PROFILE> ) {
  1.4085 +    $line =~ s/\r//g;      # turn windows-looking lines into unix-looking lines
  1.4086 +    if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) {
  1.4087 +      my ($cycles, $count, $stack) = ($1, $2, $3);
  1.4088 +
  1.4089 +      # Convert cycles to nanoseconds
  1.4090 +      $cycles /= $cyclespernanosec;
  1.4091 +
  1.4092 +      # Adjust for sampling done by application
  1.4093 +      $cycles *= $sampling_period;
  1.4094 +      $count *= $sampling_period;
  1.4095 +
  1.4096 +      my @values = ($cycles, $count, $cycles / $count);
  1.4097 +      AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);
  1.4098 +
  1.4099 +    } elsif ( $line =~ /^(slow release).*thread \d+  \@\s*(.*?)\s*$/ ||
  1.4100 +              $line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) {
  1.4101 +      my ($cycles, $stack) = ($1, $2);
  1.4102 +      if ($cycles !~ /^\d+$/) {
  1.4103 +        next;
  1.4104 +      }
  1.4105 +
  1.4106 +      # Convert cycles to nanoseconds
  1.4107 +      $cycles /= $cyclespernanosec;
  1.4108 +
  1.4109 +      # Adjust for sampling done by application
  1.4110 +      $cycles *= $sampling_period;
  1.4111 +
  1.4112 +      AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);
  1.4113 +
  1.4114 +    } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {
  1.4115 +      my ($variable, $value) = ($1,$2);
  1.4116 +      for ($variable, $value) {
  1.4117 +        s/^\s+//;
  1.4118 +        s/\s+$//;
  1.4119 +      }
  1.4120 +      if ($variable eq "cycles/second") {
  1.4121 +        $cyclespernanosec = $value / 1e9;
  1.4122 +        $seen_clockrate = 1;
  1.4123 +      } elsif ($variable eq "sampling period") {
  1.4124 +        $sampling_period = $value;
  1.4125 +      } elsif ($variable eq "ms since reset") {
  1.4126 +        # Currently nothing is done with this value in pprof
  1.4127 +        # So we just silently ignore it for now
  1.4128 +      } elsif ($variable eq "discarded samples") {
  1.4129 +        # Currently nothing is done with this value in pprof
  1.4130 +        # So we just silently ignore it for now
  1.4131 +      } else {
  1.4132 +        printf STDERR ("Ignoring unnknown variable in /contention output: " .
  1.4133 +                       "'%s' = '%s'\n",$variable,$value);
  1.4134 +      }
  1.4135 +    } else {
  1.4136 +      # Memory map entry
  1.4137 +      $map .= $line;
  1.4138 +    }
  1.4139 +  }
  1.4140 +
  1.4141 +  if (!$seen_clockrate) {
  1.4142 +    printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n",
  1.4143 +                   $cyclespernanosec);
  1.4144 +  }
  1.4145 +
  1.4146 +  my $r = {};
  1.4147 +  $r->{version} = 0;
  1.4148 +  $r->{period} = $sampling_period;
  1.4149 +  $r->{profile} = $profile;
  1.4150 +  $r->{libs} = ParseLibraries($prog, $map, $pcs);
  1.4151 +  $r->{pcs} = $pcs;
  1.4152 +  return $r;
  1.4153 +}
  1.4154 +
  1.4155 +# Given a hex value in the form "0x1abcd" or "1abcd", return either
  1.4156 +# "0001abcd" or "000000000001abcd", depending on the current (global)
  1.4157 +# address length.
  1.4158 +sub HexExtend {
  1.4159 +  my $addr = shift;
  1.4160 +
  1.4161 +  $addr =~ s/^(0x)?0*//;
  1.4162 +  my $zeros_needed = $address_length - length($addr);
  1.4163 +  if ($zeros_needed < 0) {
  1.4164 +    printf STDERR "Warning: address $addr is longer than address length $address_length\n";
  1.4165 +    return $addr;
  1.4166 +  }
  1.4167 +  return ("0" x $zeros_needed) . $addr;
  1.4168 +}
  1.4169 +
  1.4170 +##### Symbol extraction #####
  1.4171 +
  1.4172 +# Aggressively search the lib_prefix values for the given library
  1.4173 +# If all else fails, just return the name of the library unmodified.
  1.4174 +# If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so"
  1.4175 +# it will search the following locations in this order, until it finds a file:
  1.4176 +#   /my/path/lib/dir/mylib.so
  1.4177 +#   /other/path/lib/dir/mylib.so
  1.4178 +#   /my/path/dir/mylib.so
  1.4179 +#   /other/path/dir/mylib.so
  1.4180 +#   /my/path/mylib.so
  1.4181 +#   /other/path/mylib.so
  1.4182 +#   /lib/dir/mylib.so              (returned as last resort)
  1.4183 +sub FindLibrary {
  1.4184 +  my $file = shift;
  1.4185 +  my $suffix = $file;
  1.4186 +
  1.4187 +  # Search for the library as described above
  1.4188 +  do {
  1.4189 +    foreach my $prefix (@prefix_list) {
  1.4190 +      my $fullpath = $prefix . $suffix;
  1.4191 +      if (-e $fullpath) {
  1.4192 +        return $fullpath;
  1.4193 +      }
  1.4194 +    }
  1.4195 +  } while ($suffix =~ s|^/[^/]+/|/|);
  1.4196 +  return $file;
  1.4197 +}
  1.4198 +
  1.4199 +# Return path to library with debugging symbols.
  1.4200 +# For libc libraries, the copy in /usr/lib/debug contains debugging symbols
  1.4201 +sub DebuggingLibrary {
  1.4202 +  my $file = shift;
  1.4203 +  if ($file =~ m|^/| && -f "/usr/lib/debug$file") {
  1.4204 +    return "/usr/lib/debug$file";
  1.4205 +  }
  1.4206 +  return undef;
  1.4207 +}
  1.4208 +
  1.4209 +# Parse text section header of a library using objdump
  1.4210 +sub ParseTextSectionHeaderFromObjdump {
  1.4211 +  my $lib = shift;
  1.4212 +
  1.4213 +  my $size = undef;
  1.4214 +  my $vma;
  1.4215 +  my $file_offset;
  1.4216 +  # Get objdump output from the library file to figure out how to
  1.4217 +  # map between mapped addresses and addresses in the library.
  1.4218 +  my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib);
  1.4219 +  open(OBJDUMP, "$cmd |") || error("$cmd: $!\n");
  1.4220 +  while (<OBJDUMP>) {
  1.4221 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.4222 +    # Idx Name          Size      VMA       LMA       File off  Algn
  1.4223 +    #  10 .text         00104b2c  420156f0  420156f0  000156f0  2**4
  1.4224 +    # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file
  1.4225 +    # offset may still be 8.  But AddressSub below will still handle that.
  1.4226 +    my @x = split;
  1.4227 +    if (($#x >= 6) && ($x[1] eq '.text')) {
  1.4228 +      $size = $x[2];
  1.4229 +      $vma = $x[3];
  1.4230 +      $file_offset = $x[5];
  1.4231 +      last;
  1.4232 +    }
  1.4233 +  }
  1.4234 +  close(OBJDUMP);
  1.4235 +
  1.4236 +  if (!defined($size)) {
  1.4237 +    return undef;
  1.4238 +  }
  1.4239 +
  1.4240 +  my $r = {};
  1.4241 +  $r->{size} = $size;
  1.4242 +  $r->{vma} = $vma;
  1.4243 +  $r->{file_offset} = $file_offset;
  1.4244 +
  1.4245 +  return $r;
  1.4246 +}
  1.4247 +
  1.4248 +# Parse text section header of a library using otool (on OS X)
  1.4249 +sub ParseTextSectionHeaderFromOtool {
  1.4250 +  my $lib = shift;
  1.4251 +
  1.4252 +  my $size = undef;
  1.4253 +  my $vma = undef;
  1.4254 +  my $file_offset = undef;
  1.4255 +  # Get otool output from the library file to figure out how to
  1.4256 +  # map between mapped addresses and addresses in the library.
  1.4257 +  my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib);
  1.4258 +  open(OTOOL, "$command |") || error("$command: $!\n");
  1.4259 +  my $cmd = "";
  1.4260 +  my $sectname = "";
  1.4261 +  my $segname = "";
  1.4262 +  foreach my $line (<OTOOL>) {
  1.4263 +    $line =~ s/\r//g;      # turn windows-looking lines into unix-looking lines
  1.4264 +    # Load command <#>
  1.4265 +    #       cmd LC_SEGMENT
  1.4266 +    # [...]
  1.4267 +    # Section
  1.4268 +    #   sectname __text
  1.4269 +    #    segname __TEXT
  1.4270 +    #       addr 0x000009f8
  1.4271 +    #       size 0x00018b9e
  1.4272 +    #     offset 2552
  1.4273 +    #      align 2^2 (4)
  1.4274 +    # We will need to strip off the leading 0x from the hex addresses,
  1.4275 +    # and convert the offset into hex.
  1.4276 +    if ($line =~ /Load command/) {
  1.4277 +      $cmd = "";
  1.4278 +      $sectname = "";
  1.4279 +      $segname = "";
  1.4280 +    } elsif ($line =~ /Section/) {
  1.4281 +      $sectname = "";
  1.4282 +      $segname = "";
  1.4283 +    } elsif ($line =~ /cmd (\w+)/) {
  1.4284 +      $cmd = $1;
  1.4285 +    } elsif ($line =~ /sectname (\w+)/) {
  1.4286 +      $sectname = $1;
  1.4287 +    } elsif ($line =~ /segname (\w+)/) {
  1.4288 +      $segname = $1;
  1.4289 +    } elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") &&
  1.4290 +               $sectname eq "__text" &&
  1.4291 +               $segname eq "__TEXT")) {
  1.4292 +      next;
  1.4293 +    } elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) {
  1.4294 +      $vma = $1;
  1.4295 +    } elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) {
  1.4296 +      $size = $1;
  1.4297 +    } elsif ($line =~ /\boffset ([0-9]+)/) {
  1.4298 +      $file_offset = sprintf("%016x", $1);
  1.4299 +    }
  1.4300 +    if (defined($vma) && defined($size) && defined($file_offset)) {
  1.4301 +      last;
  1.4302 +    }
  1.4303 +  }
  1.4304 +  close(OTOOL);
  1.4305 +
  1.4306 +  if (!defined($vma) || !defined($size) || !defined($file_offset)) {
  1.4307 +     return undef;
  1.4308 +  }
  1.4309 +
  1.4310 +  my $r = {};
  1.4311 +  $r->{size} = $size;
  1.4312 +  $r->{vma} = $vma;
  1.4313 +  $r->{file_offset} = $file_offset;
  1.4314 +
  1.4315 +  return $r;
  1.4316 +}
  1.4317 +
  1.4318 +sub ParseTextSectionHeader {
  1.4319 +  # obj_tool_map("otool") is only defined if we're in a Mach-O environment
  1.4320 +  if (defined($obj_tool_map{"otool"})) {
  1.4321 +    my $r = ParseTextSectionHeaderFromOtool(@_);
  1.4322 +    if (defined($r)){
  1.4323 +      return $r;
  1.4324 +    }
  1.4325 +  }
  1.4326 +  # If otool doesn't work, or we don't have it, fall back to objdump
  1.4327 +  return ParseTextSectionHeaderFromObjdump(@_);
  1.4328 +}
  1.4329 +
  1.4330 +# Split /proc/pid/maps dump into a list of libraries
  1.4331 +sub ParseLibraries {
  1.4332 +  return if $main::use_symbol_page;  # We don't need libraries info.
  1.4333 +  my $prog = shift;
  1.4334 +  my $map = shift;
  1.4335 +  my $pcs = shift;
  1.4336 +
  1.4337 +  my $result = [];
  1.4338 +  my $h = "[a-f0-9]+";
  1.4339 +  my $zero_offset = HexExtend("0");
  1.4340 +
  1.4341 +  my $buildvar = "";
  1.4342 +  foreach my $l (split("\n", $map)) {
  1.4343 +    if ($l =~ m/^\s*build=(.*)$/) {
  1.4344 +      $buildvar = $1;
  1.4345 +    }
  1.4346 +
  1.4347 +    my $start;
  1.4348 +    my $finish;
  1.4349 +    my $offset;
  1.4350 +    my $lib;
  1.4351 +    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) {
  1.4352 +      # Full line from /proc/self/maps.  Example:
  1.4353 +      #   40000000-40015000 r-xp 00000000 03:01 12845071   /lib/ld-2.3.2.so
  1.4354 +      $start = HexExtend($1);
  1.4355 +      $finish = HexExtend($2);
  1.4356 +      $offset = HexExtend($3);
  1.4357 +      $lib = $4;
  1.4358 +      $lib =~ s|\\|/|g;     # turn windows-style paths into unix-style paths
  1.4359 +    } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) {
  1.4360 +      # Cooked line from DumpAddressMap.  Example:
  1.4361 +      #   40000000-40015000: /lib/ld-2.3.2.so
  1.4362 +      $start = HexExtend($1);
  1.4363 +      $finish = HexExtend($2);
  1.4364 +      $offset = $zero_offset;
  1.4365 +      $lib = $3;
  1.4366 +    } else {
  1.4367 +      next;
  1.4368 +    }
  1.4369 +
  1.4370 +    # Expand "$build" variable if available
  1.4371 +    $lib =~ s/\$build\b/$buildvar/g;
  1.4372 +
  1.4373 +    $lib = FindLibrary($lib);
  1.4374 +
  1.4375 +    # Check for pre-relocated libraries, which use pre-relocated symbol tables
  1.4376 +    # and thus require adjusting the offset that we'll use to translate
  1.4377 +    # VM addresses into symbol table addresses.
  1.4378 +    # Only do this if we're not going to fetch the symbol table from a
  1.4379 +    # debugging copy of the library.
  1.4380 +    if (!DebuggingLibrary($lib)) {
  1.4381 +      my $text = ParseTextSectionHeader($lib);
  1.4382 +      if (defined($text)) {
  1.4383 +         my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});
  1.4384 +         $offset = AddressAdd($offset, $vma_offset);
  1.4385 +      }
  1.4386 +    }
  1.4387 +
  1.4388 +    push(@{$result}, [$lib, $start, $finish, $offset]);
  1.4389 +  }
  1.4390 +
  1.4391 +  # Append special entry for additional library (not relocated)
  1.4392 +  if ($main::opt_lib ne "") {
  1.4393 +    my $text = ParseTextSectionHeader($main::opt_lib);
  1.4394 +    if (defined($text)) {
  1.4395 +       my $start = $text->{vma};
  1.4396 +       my $finish = AddressAdd($start, $text->{size});
  1.4397 +
  1.4398 +       push(@{$result}, [$main::opt_lib, $start, $finish, $start]);
  1.4399 +    }
  1.4400 +  }
  1.4401 +
  1.4402 +  # Append special entry for the main program.  This covers
  1.4403 +  # 0..max_pc_value_seen, so that we assume pc values not found in one
  1.4404 +  # of the library ranges will be treated as coming from the main
  1.4405 +  # program binary.
  1.4406 +  my $min_pc = HexExtend("0");
  1.4407 +  my $max_pc = $min_pc;          # find the maximal PC value in any sample
  1.4408 +  foreach my $pc (keys(%{$pcs})) {
  1.4409 +    if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }
  1.4410 +  }
  1.4411 +  push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);
  1.4412 +
  1.4413 +  return $result;
  1.4414 +}
  1.4415 +
  1.4416 +# Add two hex addresses of length $address_length.
  1.4417 +# Run pprof --test for unit test if this is changed.
  1.4418 +sub AddressAdd {
  1.4419 +  my $addr1 = shift;
  1.4420 +  my $addr2 = shift;
  1.4421 +  my $sum;
  1.4422 +
  1.4423 +  if ($address_length == 8) {
  1.4424 +    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
  1.4425 +    $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);
  1.4426 +    return sprintf("%08x", $sum);
  1.4427 +
  1.4428 +  } else {
  1.4429 +    # Do the addition in 7-nibble chunks to trivialize carry handling.
  1.4430 +
  1.4431 +    if ($main::opt_debug and $main::opt_test) {
  1.4432 +      print STDERR "AddressAdd $addr1 + $addr2 = ";
  1.4433 +    }
  1.4434 +
  1.4435 +    my $a1 = substr($addr1,-7);
  1.4436 +    $addr1 = substr($addr1,0,-7);
  1.4437 +    my $a2 = substr($addr2,-7);
  1.4438 +    $addr2 = substr($addr2,0,-7);
  1.4439 +    $sum = hex($a1) + hex($a2);
  1.4440 +    my $c = 0;
  1.4441 +    if ($sum > 0xfffffff) {
  1.4442 +      $c = 1;
  1.4443 +      $sum -= 0x10000000;
  1.4444 +    }
  1.4445 +    my $r = sprintf("%07x", $sum);
  1.4446 +
  1.4447 +    $a1 = substr($addr1,-7);
  1.4448 +    $addr1 = substr($addr1,0,-7);
  1.4449 +    $a2 = substr($addr2,-7);
  1.4450 +    $addr2 = substr($addr2,0,-7);
  1.4451 +    $sum = hex($a1) + hex($a2) + $c;
  1.4452 +    $c = 0;
  1.4453 +    if ($sum > 0xfffffff) {
  1.4454 +      $c = 1;
  1.4455 +      $sum -= 0x10000000;
  1.4456 +    }
  1.4457 +    $r = sprintf("%07x", $sum) . $r;
  1.4458 +
  1.4459 +    $sum = hex($addr1) + hex($addr2) + $c;
  1.4460 +    if ($sum > 0xff) { $sum -= 0x100; }
  1.4461 +    $r = sprintf("%02x", $sum) . $r;
  1.4462 +
  1.4463 +    if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; }
  1.4464 +
  1.4465 +    return $r;
  1.4466 +  }
  1.4467 +}
  1.4468 +
  1.4469 +
  1.4470 +# Subtract two hex addresses of length $address_length.
  1.4471 +# Run pprof --test for unit test if this is changed.
  1.4472 +sub AddressSub {
  1.4473 +  my $addr1 = shift;
  1.4474 +  my $addr2 = shift;
  1.4475 +  my $diff;
  1.4476 +
  1.4477 +  if ($address_length == 8) {
  1.4478 +    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
  1.4479 +    $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);
  1.4480 +    return sprintf("%08x", $diff);
  1.4481 +
  1.4482 +  } else {
  1.4483 +    # Do the addition in 7-nibble chunks to trivialize borrow handling.
  1.4484 +    # if ($main::opt_debug) { print STDERR "AddressSub $addr1 - $addr2 = "; }
  1.4485 +
  1.4486 +    my $a1 = hex(substr($addr1,-7));
  1.4487 +    $addr1 = substr($addr1,0,-7);
  1.4488 +    my $a2 = hex(substr($addr2,-7));
  1.4489 +    $addr2 = substr($addr2,0,-7);
  1.4490 +    my $b = 0;
  1.4491 +    if ($a2 > $a1) {
  1.4492 +      $b = 1;
  1.4493 +      $a1 += 0x10000000;
  1.4494 +    }
  1.4495 +    $diff = $a1 - $a2;
  1.4496 +    my $r = sprintf("%07x", $diff);
  1.4497 +
  1.4498 +    $a1 = hex(substr($addr1,-7));
  1.4499 +    $addr1 = substr($addr1,0,-7);
  1.4500 +    $a2 = hex(substr($addr2,-7)) + $b;
  1.4501 +    $addr2 = substr($addr2,0,-7);
  1.4502 +    $b = 0;
  1.4503 +    if ($a2 > $a1) {
  1.4504 +      $b = 1;
  1.4505 +      $a1 += 0x10000000;
  1.4506 +    }
  1.4507 +    $diff = $a1 - $a2;
  1.4508 +    $r = sprintf("%07x", $diff) . $r;
  1.4509 +
  1.4510 +    $a1 = hex($addr1);
  1.4511 +    $a2 = hex($addr2) + $b;
  1.4512 +    if ($a2 > $a1) { $a1 += 0x100; }
  1.4513 +    $diff = $a1 - $a2;
  1.4514 +    $r = sprintf("%02x", $diff) . $r;
  1.4515 +
  1.4516 +    # if ($main::opt_debug) { print STDERR "$r\n"; }
  1.4517 +
  1.4518 +    return $r;
  1.4519 +  }
  1.4520 +}
  1.4521 +
  1.4522 +# Increment a hex addresses of length $address_length.
  1.4523 +# Run pprof --test for unit test if this is changed.
  1.4524 +sub AddressInc {
  1.4525 +  my $addr = shift;
  1.4526 +  my $sum;
  1.4527 +
  1.4528 +  if ($address_length == 8) {
  1.4529 +    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:
  1.4530 +    $sum = (hex($addr)+1) % (0x10000000 * 16);
  1.4531 +    return sprintf("%08x", $sum);
  1.4532 +
  1.4533 +  } else {
  1.4534 +    # Do the addition in 7-nibble chunks to trivialize carry handling.
  1.4535 +    # We are always doing this to step through the addresses in a function,
  1.4536 +    # and will almost never overflow the first chunk, so we check for this
  1.4537 +    # case and exit early.
  1.4538 +
  1.4539 +    # if ($main::opt_debug) { print STDERR "AddressInc $addr1 = "; }
  1.4540 +
  1.4541 +    my $a1 = substr($addr,-7);
  1.4542 +    $addr = substr($addr,0,-7);
  1.4543 +    $sum = hex($a1) + 1;
  1.4544 +    my $r = sprintf("%07x", $sum);
  1.4545 +    if ($sum <= 0xfffffff) {
  1.4546 +      $r = $addr . $r;
  1.4547 +      # if ($main::opt_debug) { print STDERR "$r\n"; }
  1.4548 +      return HexExtend($r);
  1.4549 +    } else {
  1.4550 +      $r = "0000000";
  1.4551 +    }
  1.4552 +
  1.4553 +    $a1 = substr($addr,-7);
  1.4554 +    $addr = substr($addr,0,-7);
  1.4555 +    $sum = hex($a1) + 1;
  1.4556 +    $r = sprintf("%07x", $sum) . $r;
  1.4557 +    if ($sum <= 0xfffffff) {
  1.4558 +      $r = $addr . $r;
  1.4559 +      # if ($main::opt_debug) { print STDERR "$r\n"; }
  1.4560 +      return HexExtend($r);
  1.4561 +    } else {
  1.4562 +      $r = "00000000000000";
  1.4563 +    }
  1.4564 +
  1.4565 +    $sum = hex($addr) + 1;
  1.4566 +    if ($sum > 0xff) { $sum -= 0x100; }
  1.4567 +    $r = sprintf("%02x", $sum) . $r;
  1.4568 +
  1.4569 +    # if ($main::opt_debug) { print STDERR "$r\n"; }
  1.4570 +    return $r;
  1.4571 +  }
  1.4572 +}
  1.4573 +
  1.4574 +# Extract symbols for all PC values found in profile
  1.4575 +sub ExtractSymbols {
  1.4576 +  my $libs = shift;
  1.4577 +  my $pcset = shift;
  1.4578 +
  1.4579 +  my $symbols = {};
  1.4580 +
  1.4581 +  # Map each PC value to the containing library.  To make this faster,
  1.4582 +  # we sort libraries by their starting pc value (highest first), and
  1.4583 +  # advance through the libraries as we advance the pc.  Sometimes the
  1.4584 +  # addresses of libraries may overlap with the addresses of the main
  1.4585 +  # binary, so to make sure the libraries 'win', we iterate over the
  1.4586 +  # libraries in reverse order (which assumes the binary doesn't start
  1.4587 +  # in the middle of a library, which seems a fair assumption).
  1.4588 +  my @pcs = (sort { $a cmp $b } keys(%{$pcset}));  # pcset is 0-extended strings
  1.4589 +  foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {
  1.4590 +    my $libname = $lib->[0];
  1.4591 +    my $start = $lib->[1];
  1.4592 +    my $finish = $lib->[2];
  1.4593 +    my $offset = $lib->[3];
  1.4594 +
  1.4595 +    # Get list of pcs that belong in this library.
  1.4596 +    my $contained = [];
  1.4597 +    my ($start_pc_index, $finish_pc_index);
  1.4598 +    # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].
  1.4599 +    for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;
  1.4600 +         $finish_pc_index--) {
  1.4601 +      last if $pcs[$finish_pc_index - 1] le $finish;
  1.4602 +    }
  1.4603 +    # Find smallest start_pc_index such that $start <= $pc[$start_pc_index].
  1.4604 +    for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;
  1.4605 +         $start_pc_index--) {
  1.4606 +      last if $pcs[$start_pc_index - 1] lt $start;
  1.4607 +    }
  1.4608 +    # This keeps PC values higher than $pc[$finish_pc_index] in @pcs,
  1.4609 +    # in case there are overlaps in libraries and the main binary.
  1.4610 +    @{$contained} = splice(@pcs, $start_pc_index,
  1.4611 +                           $finish_pc_index - $start_pc_index);
  1.4612 +    # Map to symbols
  1.4613 +    MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);
  1.4614 +  }
  1.4615 +
  1.4616 +  return $symbols;
  1.4617 +}
  1.4618 +
  1.4619 +# Map list of PC values to symbols for a given image
  1.4620 +sub MapToSymbols {
  1.4621 +  my $image = shift;
  1.4622 +  my $offset = shift;
  1.4623 +  my $pclist = shift;
  1.4624 +  my $symbols = shift;
  1.4625 +
  1.4626 +  my $debug = 0;
  1.4627 +
  1.4628 +  # Ignore empty binaries
  1.4629 +  if ($#{$pclist} < 0) { return; }
  1.4630 +
  1.4631 +  # Figure out the addr2line command to use
  1.4632 +  my $addr2line = $obj_tool_map{"addr2line"};
  1.4633 +  my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image);
  1.4634 +  if (exists $obj_tool_map{"addr2line_pdb"}) {
  1.4635 +    $addr2line = $obj_tool_map{"addr2line_pdb"};
  1.4636 +    $cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image);
  1.4637 +  }
  1.4638 +
  1.4639 +  # If "addr2line" isn't installed on the system at all, just use
  1.4640 +  # nm to get what info we can (function names, but not line numbers).
  1.4641 +  if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) {
  1.4642 +    MapSymbolsWithNM($image, $offset, $pclist, $symbols);
  1.4643 +    return;
  1.4644 +  }
  1.4645 +
  1.4646 +  # "addr2line -i" can produce a variable number of lines per input
  1.4647 +  # address, with no separator that allows us to tell when data for
  1.4648 +  # the next address starts.  So we find the address for a special
  1.4649 +  # symbol (_fini) and interleave this address between all real
  1.4650 +  # addresses passed to addr2line.  The name of this special symbol
  1.4651 +  # can then be used as a separator.
  1.4652 +  $sep_address = undef;  # May be filled in by MapSymbolsWithNM()
  1.4653 +  my $nm_symbols = {};
  1.4654 +  MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);
  1.4655 +  if (defined($sep_address)) {
  1.4656 +    # Only add " -i" to addr2line if the binary supports it.
  1.4657 +    # addr2line --help returns 0, but not if it sees an unknown flag first.
  1.4658 +    if (system("$cmd -i --help >$dev_null 2>&1") == 0) {
  1.4659 +      $cmd .= " -i";
  1.4660 +    } else {
  1.4661 +      $sep_address = undef;   # no need for sep_address if we don't support -i
  1.4662 +    }
  1.4663 +  }
  1.4664 +
  1.4665 +  # Make file with all PC values with intervening 'sep_address' so
  1.4666 +  # that we can reliably detect the end of inlined function list
  1.4667 +  open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n");
  1.4668 +  if ($debug) { print("---- $image ---\n"); }
  1.4669 +  for (my $i = 0; $i <= $#{$pclist}; $i++) {
  1.4670 +    # addr2line always reads hex addresses, and does not need '0x' prefix.
  1.4671 +    if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); }
  1.4672 +    printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset));
  1.4673 +    if (defined($sep_address)) {
  1.4674 +      printf ADDRESSES ("%s\n", $sep_address);
  1.4675 +    }
  1.4676 +  }
  1.4677 +  close(ADDRESSES);
  1.4678 +  if ($debug) {
  1.4679 +    print("----\n");
  1.4680 +    system("cat", $main::tmpfile_sym);
  1.4681 +    print("----\n");
  1.4682 +    system("$cmd < " . ShellEscape($main::tmpfile_sym));
  1.4683 +    print("----\n");
  1.4684 +  }
  1.4685 +
  1.4686 +  open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |")
  1.4687 +      || error("$cmd: $!\n");
  1.4688 +  my $count = 0;   # Index in pclist
  1.4689 +  while (<SYMBOLS>) {
  1.4690 +    # Read fullfunction and filelineinfo from next pair of lines
  1.4691 +    s/\r?\n$//g;
  1.4692 +    my $fullfunction = $_;
  1.4693 +    $_ = <SYMBOLS>;
  1.4694 +    s/\r?\n$//g;
  1.4695 +    my $filelinenum = $_;
  1.4696 +
  1.4697 +    if (defined($sep_address) && $fullfunction eq $sep_symbol) {
  1.4698 +      # Terminating marker for data for this address
  1.4699 +      $count++;
  1.4700 +      next;
  1.4701 +    }
  1.4702 +
  1.4703 +    $filelinenum =~ s|\\|/|g; # turn windows-style paths into unix-style paths
  1.4704 +
  1.4705 +    my $pcstr = $pclist->[$count];
  1.4706 +    my $function = ShortFunctionName($fullfunction);
  1.4707 +    my $nms = $nm_symbols->{$pcstr};
  1.4708 +    if (defined($nms)) {
  1.4709 +      if ($fullfunction eq '??') {
  1.4710 +        # nm found a symbol for us.
  1.4711 +        $function = $nms->[0];
  1.4712 +        $fullfunction = $nms->[2];
  1.4713 +      } else {
  1.4714 +	# MapSymbolsWithNM tags each routine with its starting address,
  1.4715 +	# useful in case the image has multiple occurrences of this
  1.4716 +	# routine.  (It uses a syntax that resembles template paramters,
  1.4717 +	# that are automatically stripped out by ShortFunctionName().)
  1.4718 +	# addr2line does not provide the same information.  So we check
  1.4719 +	# if nm disambiguated our symbol, and if so take the annotated
  1.4720 +	# (nm) version of the routine-name.  TODO(csilvers): this won't
  1.4721 +	# catch overloaded, inlined symbols, which nm doesn't see.
  1.4722 +	# Better would be to do a check similar to nm's, in this fn.
  1.4723 +	if ($nms->[2] =~ m/^\Q$function\E/) {  # sanity check it's the right fn
  1.4724 +	  $function = $nms->[0];
  1.4725 +	  $fullfunction = $nms->[2];
  1.4726 +	}
  1.4727 +      }
  1.4728 +    }
  1.4729 +    
  1.4730 +    # Prepend to accumulated symbols for pcstr
  1.4731 +    # (so that caller comes before callee)
  1.4732 +    my $sym = $symbols->{$pcstr};
  1.4733 +    if (!defined($sym)) {
  1.4734 +      $sym = [];
  1.4735 +      $symbols->{$pcstr} = $sym;
  1.4736 +    }
  1.4737 +    unshift(@{$sym}, $function, $filelinenum, $fullfunction);
  1.4738 +    if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); }
  1.4739 +    if (!defined($sep_address)) {
  1.4740 +      # Inlining is off, so this entry ends immediately
  1.4741 +      $count++;
  1.4742 +    }
  1.4743 +  }
  1.4744 +  close(SYMBOLS);
  1.4745 +}
  1.4746 +
  1.4747 +# Use nm to map the list of referenced PCs to symbols.  Return true iff we
  1.4748 +# are able to read procedure information via nm.
  1.4749 +sub MapSymbolsWithNM {
  1.4750 +  my $image = shift;
  1.4751 +  my $offset = shift;
  1.4752 +  my $pclist = shift;
  1.4753 +  my $symbols = shift;
  1.4754 +
  1.4755 +  # Get nm output sorted by increasing address
  1.4756 +  my $symbol_table = GetProcedureBoundaries($image, ".");
  1.4757 +  if (!%{$symbol_table}) {
  1.4758 +    return 0;
  1.4759 +  }
  1.4760 +  # Start addresses are already the right length (8 or 16 hex digits).
  1.4761 +  my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }
  1.4762 +    keys(%{$symbol_table});
  1.4763 +
  1.4764 +  if ($#names < 0) {
  1.4765 +    # No symbols: just use addresses
  1.4766 +    foreach my $pc (@{$pclist}) {
  1.4767 +      my $pcstr = "0x" . $pc;
  1.4768 +      $symbols->{$pc} = [$pcstr, "?", $pcstr];
  1.4769 +    }
  1.4770 +    return 0;
  1.4771 +  }
  1.4772 +
  1.4773 +  # Sort addresses so we can do a join against nm output
  1.4774 +  my $index = 0;
  1.4775 +  my $fullname = $names[0];
  1.4776 +  my $name = ShortFunctionName($fullname);
  1.4777 +  foreach my $pc (sort { $a cmp $b } @{$pclist}) {
  1.4778 +    # Adjust for mapped offset
  1.4779 +    my $mpc = AddressSub($pc, $offset);
  1.4780 +    while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){
  1.4781 +      $index++;
  1.4782 +      $fullname = $names[$index];
  1.4783 +      $name = ShortFunctionName($fullname);
  1.4784 +    }
  1.4785 +    if ($mpc lt $symbol_table->{$fullname}->[1]) {
  1.4786 +      $symbols->{$pc} = [$name, "?", $fullname];
  1.4787 +    } else {
  1.4788 +      my $pcstr = "0x" . $pc;
  1.4789 +      $symbols->{$pc} = [$pcstr, "?", $pcstr];
  1.4790 +    }
  1.4791 +  }
  1.4792 +  return 1;
  1.4793 +}
  1.4794 +
  1.4795 +sub ShortFunctionName {
  1.4796 +  my $function = shift;
  1.4797 +  while ($function =~ s/\([^()]*\)(\s*const)?//g) { }   # Argument types
  1.4798 +  while ($function =~ s/<[^<>]*>//g)  { }    # Remove template arguments
  1.4799 +  $function =~ s/^.*\s+(\w+::)/$1/;          # Remove leading type
  1.4800 +  return $function;
  1.4801 +}
  1.4802 +
  1.4803 +# Trim overly long symbols found in disassembler output
  1.4804 +sub CleanDisassembly {
  1.4805 +  my $d = shift;
  1.4806 +  while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax)
  1.4807 +  while ($d =~ s/(\w+)<[^<>]*>/$1/g)  { }       # Remove template arguments
  1.4808 +  return $d;
  1.4809 +}
  1.4810 +
  1.4811 +# Clean file name for display
  1.4812 +sub CleanFileName {
  1.4813 +  my ($f) = @_;
  1.4814 +  $f =~ s|^/proc/self/cwd/||;
  1.4815 +  $f =~ s|^\./||;
  1.4816 +  return $f;
  1.4817 +}
  1.4818 +
  1.4819 +# Make address relative to section and clean up for display
  1.4820 +sub UnparseAddress {
  1.4821 +  my ($offset, $address) = @_;
  1.4822 +  $address = AddressSub($address, $offset);
  1.4823 +  $address =~ s/^0x//;
  1.4824 +  $address =~ s/^0*//;
  1.4825 +  return $address;
  1.4826 +}
  1.4827 +
  1.4828 +##### Miscellaneous #####
  1.4829 +
  1.4830 +# Find the right versions of the above object tools to use.  The
  1.4831 +# argument is the program file being analyzed, and should be an ELF
  1.4832 +# 32-bit or ELF 64-bit executable file.  The location of the tools
  1.4833 +# is determined by considering the following options in this order:
  1.4834 +#   1) --tools option, if set
  1.4835 +#   2) PPROF_TOOLS environment variable, if set
  1.4836 +#   3) the environment
  1.4837 +sub ConfigureObjTools {
  1.4838 +  my $prog_file = shift;
  1.4839 +
  1.4840 +  # Check for the existence of $prog_file because /usr/bin/file does not
  1.4841 +  # predictably return error status in prod.
  1.4842 +  (-e $prog_file)  || error("$prog_file does not exist.\n");
  1.4843 +
  1.4844 +  my $file_type = undef;
  1.4845 +  if (-e "/usr/bin/file") {
  1.4846 +    # Follow symlinks (at least for systems where "file" supports that).
  1.4847 +    my $escaped_prog_file = ShellEscape($prog_file);
  1.4848 +    $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||
  1.4849 +                  /usr/bin/file $escaped_prog_file`;
  1.4850 +  } elsif ($^O == "MSWin32") {
  1.4851 +    $file_type = "MS Windows";
  1.4852 +  } else {
  1.4853 +    print STDERR "WARNING: Can't determine the file type of $prog_file";
  1.4854 +  }
  1.4855 +
  1.4856 +  if ($file_type =~ /64-bit/) {
  1.4857 +    # Change $address_length to 16 if the program file is ELF 64-bit.
  1.4858 +    # We can't detect this from many (most?) heap or lock contention
  1.4859 +    # profiles, since the actual addresses referenced are generally in low
  1.4860 +    # memory even for 64-bit programs.
  1.4861 +    $address_length = 16;
  1.4862 +  }
  1.4863 +
  1.4864 +  if ($file_type =~ /MS Windows/) {
  1.4865 +    # For windows, we provide a version of nm and addr2line as part of
  1.4866 +    # the opensource release, which is capable of parsing
  1.4867 +    # Windows-style PDB executables.  It should live in the path, or
  1.4868 +    # in the same directory as pprof.
  1.4869 +    $obj_tool_map{"nm_pdb"} = "nm-pdb";
  1.4870 +    $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
  1.4871 +  }
  1.4872 +
  1.4873 +  if ($file_type =~ /Mach-O/) {
  1.4874 +    # OS X uses otool to examine Mach-O files, rather than objdump.
  1.4875 +    $obj_tool_map{"otool"} = "otool";
  1.4876 +    $obj_tool_map{"addr2line"} = "false";  # no addr2line
  1.4877 +    $obj_tool_map{"objdump"} = "false";  # no objdump
  1.4878 +  }
  1.4879 +
  1.4880 +  # Go fill in %obj_tool_map with the pathnames to use:
  1.4881 +  foreach my $tool (keys %obj_tool_map) {
  1.4882 +    $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});
  1.4883 +  }
  1.4884 +}
  1.4885 +
  1.4886 +# Returns the path of a caller-specified object tool.  If --tools or
  1.4887 +# PPROF_TOOLS are specified, then returns the full path to the tool
  1.4888 +# with that prefix.  Otherwise, returns the path unmodified (which
  1.4889 +# means we will look for it on PATH).
  1.4890 +sub ConfigureTool {
  1.4891 +  my $tool = shift;
  1.4892 +  my $path;
  1.4893 +
  1.4894 +  # --tools (or $PPROF_TOOLS) is a comma separated list, where each
  1.4895 +  # item is either a) a pathname prefix, or b) a map of the form
  1.4896 +  # <tool>:<path>.  First we look for an entry of type (b) for our
  1.4897 +  # tool.  If one is found, we use it.  Otherwise, we consider all the
  1.4898 +  # pathname prefixes in turn, until one yields an existing file.  If
  1.4899 +  # none does, we use a default path.
  1.4900 +  my $tools = $main::opt_tools || $ENV{"PPROF_TOOLS"} || "";
  1.4901 +  if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) {
  1.4902 +    $path = $2;
  1.4903 +    # TODO(csilvers): sanity-check that $path exists?  Hard if it's relative.
  1.4904 +  } elsif ($tools ne '') {
  1.4905 +    foreach my $prefix (split(',', $tools)) {
  1.4906 +      next if ($prefix =~ /:/);    # ignore "tool:fullpath" entries in the list
  1.4907 +      if (-x $prefix . $tool) {
  1.4908 +        $path = $prefix . $tool;
  1.4909 +        last;
  1.4910 +      }
  1.4911 +    }
  1.4912 +    if (!$path) {
  1.4913 +      error("No '$tool' found with prefix specified by " .
  1.4914 +            "--tools (or \$PPROF_TOOLS) '$tools'\n");
  1.4915 +    }
  1.4916 +  } else {
  1.4917 +    # ... otherwise use the version that exists in the same directory as
  1.4918 +    # pprof.  If there's nothing there, use $PATH.
  1.4919 +    $0 =~ m,[^/]*$,;     # this is everything after the last slash
  1.4920 +    my $dirname = $`;    # this is everything up to and including the last slash
  1.4921 +    if (-x "$dirname$tool") {
  1.4922 +      $path = "$dirname$tool";
  1.4923 +    } else { 
  1.4924 +      $path = $tool;
  1.4925 +    }
  1.4926 +  }
  1.4927 +  if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; }
  1.4928 +  return $path;
  1.4929 +}
  1.4930 +
  1.4931 +sub ShellEscape {
  1.4932 +  my @escaped_words = ();
  1.4933 +  foreach my $word (@_) {
  1.4934 +    my $escaped_word = $word;
  1.4935 +    if ($word =~ m![^a-zA-Z0-9/.,_=-]!) {  # check for anything not in whitelist
  1.4936 +      $escaped_word =~ s/'/'\\''/;
  1.4937 +      $escaped_word = "'$escaped_word'";
  1.4938 +    }
  1.4939 +    push(@escaped_words, $escaped_word);
  1.4940 +  }
  1.4941 +  return join(" ", @escaped_words);
  1.4942 +}
  1.4943 +
  1.4944 +sub cleanup {
  1.4945 +  unlink($main::tmpfile_sym);
  1.4946 +  unlink(keys %main::tempnames);
  1.4947 +
  1.4948 +  # We leave any collected profiles in $HOME/pprof in case the user wants
  1.4949 +  # to look at them later.  We print a message informing them of this.
  1.4950 +  if ((scalar(@main::profile_files) > 0) &&
  1.4951 +      defined($main::collected_profile)) {
  1.4952 +    if (scalar(@main::profile_files) == 1) {
  1.4953 +      print STDERR "Dynamically gathered profile is in $main::collected_profile\n";
  1.4954 +    }
  1.4955 +    print STDERR "If you want to investigate this profile further, you can do:\n";
  1.4956 +    print STDERR "\n";
  1.4957 +    print STDERR "  pprof \\\n";
  1.4958 +    print STDERR "    $main::prog \\\n";
  1.4959 +    print STDERR "    $main::collected_profile\n";
  1.4960 +    print STDERR "\n";
  1.4961 +  }
  1.4962 +}
  1.4963 +
  1.4964 +sub sighandler {
  1.4965 +  cleanup();
  1.4966 +  exit(1);
  1.4967 +}
  1.4968 +
  1.4969 +sub error {
  1.4970 +  my $msg = shift;
  1.4971 +  print STDERR $msg;
  1.4972 +  cleanup();
  1.4973 +  exit(1);
  1.4974 +}
  1.4975 +
  1.4976 +
  1.4977 +# Run $nm_command and get all the resulting procedure boundaries whose
  1.4978 +# names match "$regexp" and returns them in a hashtable mapping from
  1.4979 +# procedure name to a two-element vector of [start address, end address]
  1.4980 +sub GetProcedureBoundariesViaNm {
  1.4981 +  my $escaped_nm_command = shift;    # shell-escaped
  1.4982 +  my $regexp = shift;
  1.4983 +
  1.4984 +  my $symbol_table = {};
  1.4985 +  open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n");
  1.4986 +  my $last_start = "0";
  1.4987 +  my $routine = "";
  1.4988 +  while (<NM>) {
  1.4989 +    s/\r//g;         # turn windows-looking lines into unix-looking lines
  1.4990 +    if (m/^\s*([0-9a-f]+) (.) (..*)/) {
  1.4991 +      my $start_val = $1;
  1.4992 +      my $type = $2;
  1.4993 +      my $this_routine = $3;
  1.4994 +
  1.4995 +      # It's possible for two symbols to share the same address, if
  1.4996 +      # one is a zero-length variable (like __start_google_malloc) or
  1.4997 +      # one symbol is a weak alias to another (like __libc_malloc).
  1.4998 +      # In such cases, we want to ignore all values except for the
  1.4999 +      # actual symbol, which in nm-speak has type "T".  The logic
  1.5000 +      # below does this, though it's a bit tricky: what happens when
  1.5001 +      # we have a series of lines with the same address, is the first
  1.5002 +      # one gets queued up to be processed.  However, it won't
  1.5003 +      # *actually* be processed until later, when we read a line with
  1.5004 +      # a different address.  That means that as long as we're reading
  1.5005 +      # lines with the same address, we have a chance to replace that
  1.5006 +      # item in the queue, which we do whenever we see a 'T' entry --
  1.5007 +      # that is, a line with type 'T'.  If we never see a 'T' entry,
  1.5008 +      # we'll just go ahead and process the first entry (which never
  1.5009 +      # got touched in the queue), and ignore the others.
  1.5010 +      if ($start_val eq $last_start && $type =~ /t/i) {
  1.5011 +        # We are the 'T' symbol at this address, replace previous symbol.
  1.5012 +        $routine = $this_routine;
  1.5013 +        next;
  1.5014 +      } elsif ($start_val eq $last_start) {
  1.5015 +        # We're not the 'T' symbol at this address, so ignore us.
  1.5016 +        next;
  1.5017 +      }
  1.5018 +
  1.5019 +      if ($this_routine eq $sep_symbol) {
  1.5020 +        $sep_address = HexExtend($start_val);
  1.5021 +      }
  1.5022 +
  1.5023 +      # Tag this routine with the starting address in case the image
  1.5024 +      # has multiple occurrences of this routine.  We use a syntax
  1.5025 +      # that resembles template paramters that are automatically
  1.5026 +      # stripped out by ShortFunctionName()
  1.5027 +      $this_routine .= "<$start_val>";
  1.5028 +
  1.5029 +      if (defined($routine) && $routine =~ m/$regexp/) {
  1.5030 +        $symbol_table->{$routine} = [HexExtend($last_start),
  1.5031 +                                     HexExtend($start_val)];
  1.5032 +      }
  1.5033 +      $last_start = $start_val;
  1.5034 +      $routine = $this_routine;
  1.5035 +    } elsif (m/^Loaded image name: (.+)/) {
  1.5036 +      # The win32 nm workalike emits information about the binary it is using.
  1.5037 +      if ($main::opt_debug) { print STDERR "Using Image $1\n"; }
  1.5038 +    } elsif (m/^PDB file name: (.+)/) {
  1.5039 +      # The win32 nm workalike emits information about the pdb it is using.
  1.5040 +      if ($main::opt_debug) { print STDERR "Using PDB $1\n"; }
  1.5041 +    }
  1.5042 +  }
  1.5043 +  close(NM);
  1.5044 +  # Handle the last line in the nm output.  Unfortunately, we don't know
  1.5045 +  # how big this last symbol is, because we don't know how big the file
  1.5046 +  # is.  For now, we just give it a size of 0.
  1.5047 +  # TODO(csilvers): do better here.
  1.5048 +  if (defined($routine) && $routine =~ m/$regexp/) {
  1.5049 +    $symbol_table->{$routine} = [HexExtend($last_start),
  1.5050 +                                 HexExtend($last_start)];
  1.5051 +  }
  1.5052 +  return $symbol_table;
  1.5053 +}
  1.5054 +
  1.5055 +# Gets the procedure boundaries for all routines in "$image" whose names
  1.5056 +# match "$regexp" and returns them in a hashtable mapping from procedure
  1.5057 +# name to a two-element vector of [start address, end address].
  1.5058 +# Will return an empty map if nm is not installed or not working properly.
  1.5059 +sub GetProcedureBoundaries {
  1.5060 +  my $image = shift;
  1.5061 +  my $regexp = shift;
  1.5062 +
  1.5063 +  # If $image doesn't start with /, then put ./ in front of it.  This works
  1.5064 +  # around an obnoxious bug in our probing of nm -f behavior.
  1.5065 +  # "nm -f $image" is supposed to fail on GNU nm, but if:
  1.5066 +  #
  1.5067 +  # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND
  1.5068 +  # b. you have a.out in your current directory (a not uncommon occurence)
  1.5069 +  #
  1.5070 +  # then "nm -f $image" succeeds because -f only looks at the first letter of
  1.5071 +  # the argument, which looks valid because it's [BbSsPp], and then since
  1.5072 +  # there's no image provided, it looks for a.out and finds it.
  1.5073 +  #
  1.5074 +  # This regex makes sure that $image starts with . or /, forcing the -f
  1.5075 +  # parsing to fail since . and / are not valid formats.
  1.5076 +  $image =~ s#^[^/]#./$&#;
  1.5077 +
  1.5078 +  # For libc libraries, the copy in /usr/lib/debug contains debugging symbols
  1.5079 +  my $debugging = DebuggingLibrary($image);
  1.5080 +  if ($debugging) {
  1.5081 +    $image = $debugging;
  1.5082 +  }
  1.5083 +
  1.5084 +  my $nm = $obj_tool_map{"nm"};
  1.5085 +  my $cppfilt = $obj_tool_map{"c++filt"};
  1.5086 +
  1.5087 +  # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm
  1.5088 +  # binary doesn't support --demangle.  In addition, for OS X we need
  1.5089 +  # to use the -f flag to get 'flat' nm output (otherwise we don't sort
  1.5090 +  # properly and get incorrect results).  Unfortunately, GNU nm uses -f
  1.5091 +  # in an incompatible way.  So first we test whether our nm supports
  1.5092 +  # --demangle and -f.
  1.5093 +  my $demangle_flag = "";
  1.5094 +  my $cppfilt_flag = "";
  1.5095 +  my $to_devnull = ">$dev_null 2>&1";
  1.5096 +  if (system(ShellEscape($nm, "--demangle", "image") . $to_devnull) == 0) {
  1.5097 +    # In this mode, we do "nm --demangle <foo>"
  1.5098 +    $demangle_flag = "--demangle";
  1.5099 +    $cppfilt_flag = "";
  1.5100 +  } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {
  1.5101 +    # In this mode, we do "nm <foo> | c++filt"
  1.5102 +    $cppfilt_flag = " | " . ShellEscape($cppfilt);
  1.5103 +  };
  1.5104 +  my $flatten_flag = "";
  1.5105 +  if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) {
  1.5106 +    $flatten_flag = "-f";
  1.5107 +  }
  1.5108 +
  1.5109 +  # Finally, in the case $imagie isn't a debug library, we try again with
  1.5110 +  # -D to at least get *exported* symbols.  If we can't use --demangle,
  1.5111 +  # we use c++filt instead, if it exists on this system.
  1.5112 +  my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag,
  1.5113 +                                 $image) . " 2>$dev_null $cppfilt_flag",
  1.5114 +                     ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag,
  1.5115 +                                 $image) . " 2>$dev_null $cppfilt_flag",
  1.5116 +                     # 6nm is for Go binaries
  1.5117 +                     ShellEscape("6nm", "$image") . " 2>$dev_null | sort",
  1.5118 +                     );
  1.5119 +
  1.5120 +  # If the executable is an MS Windows PDB-format executable, we'll
  1.5121 +  # have set up obj_tool_map("nm_pdb").  In this case, we actually
  1.5122 +  # want to use both unix nm and windows-specific nm_pdb, since
  1.5123 +  # PDB-format executables can apparently include dwarf .o files.
  1.5124 +  if (exists $obj_tool_map{"nm_pdb"}) {
  1.5125 +    push(@nm_commands,
  1.5126 +         ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image)
  1.5127 +         . " 2>$dev_null");
  1.5128 +  }
  1.5129 +
  1.5130 +  foreach my $nm_command (@nm_commands) {
  1.5131 +    my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);
  1.5132 +    return $symbol_table if (%{$symbol_table});
  1.5133 +  }
  1.5134 +  my $symbol_table = {};
  1.5135 +  return $symbol_table;
  1.5136 +}
  1.5137 +
  1.5138 +
  1.5139 +# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.
  1.5140 +# To make them more readable, we add underscores at interesting places.
  1.5141 +# This routine removes the underscores, producing the canonical representation
  1.5142 +# used by pprof to represent addresses, particularly in the tested routines.
  1.5143 +sub CanonicalHex {
  1.5144 +  my $arg = shift;
  1.5145 +  return join '', (split '_',$arg);
  1.5146 +}
  1.5147 +
  1.5148 +
  1.5149 +# Unit test for AddressAdd:
  1.5150 +sub AddressAddUnitTest {
  1.5151 +  my $test_data_8 = shift;
  1.5152 +  my $test_data_16 = shift;
  1.5153 +  my $error_count = 0;
  1.5154 +  my $fail_count = 0;
  1.5155 +  my $pass_count = 0;
  1.5156 +  # print STDERR "AddressAddUnitTest: ", 1+$#{$test_data_8}, " tests\n";
  1.5157 +
  1.5158 +  # First a few 8-nibble addresses.  Note that this implementation uses
  1.5159 +  # plain old arithmetic, so a quick sanity check along with verifying what
  1.5160 +  # happens to overflow (we want it to wrap):
  1.5161 +  $address_length = 8;
  1.5162 +  foreach my $row (@{$test_data_8}) {
  1.5163 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5164 +    my $sum = AddressAdd ($row->[0], $row->[1]);
  1.5165 +    if ($sum ne $row->[2]) {
  1.5166 +      printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
  1.5167 +             $row->[0], $row->[1], $row->[2];
  1.5168 +      ++$fail_count;
  1.5169 +    } else {
  1.5170 +      ++$pass_count;
  1.5171 +    }
  1.5172 +  }
  1.5173 +  printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n",
  1.5174 +         $pass_count, $fail_count;
  1.5175 +  $error_count = $fail_count;
  1.5176 +  $fail_count = 0;
  1.5177 +  $pass_count = 0;
  1.5178 +
  1.5179 +  # Now 16-nibble addresses.
  1.5180 +  $address_length = 16;
  1.5181 +  foreach my $row (@{$test_data_16}) {
  1.5182 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5183 +    my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
  1.5184 +    my $expected = join '', (split '_',$row->[2]);
  1.5185 +    if ($sum ne CanonicalHex($row->[2])) {
  1.5186 +      printf STDERR "ERROR: %s != %s + %s = %s\n", $sum,
  1.5187 +             $row->[0], $row->[1], $row->[2];
  1.5188 +      ++$fail_count;
  1.5189 +    } else {
  1.5190 +      ++$pass_count;
  1.5191 +    }
  1.5192 +  }
  1.5193 +  printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n",
  1.5194 +         $pass_count, $fail_count;
  1.5195 +  $error_count += $fail_count;
  1.5196 +
  1.5197 +  return $error_count;
  1.5198 +}
  1.5199 +
  1.5200 +
  1.5201 +# Unit test for AddressSub:
  1.5202 +sub AddressSubUnitTest {
  1.5203 +  my $test_data_8 = shift;
  1.5204 +  my $test_data_16 = shift;
  1.5205 +  my $error_count = 0;
  1.5206 +  my $fail_count = 0;
  1.5207 +  my $pass_count = 0;
  1.5208 +  # print STDERR "AddressSubUnitTest: ", 1+$#{$test_data_8}, " tests\n";
  1.5209 +
  1.5210 +  # First a few 8-nibble addresses.  Note that this implementation uses
  1.5211 +  # plain old arithmetic, so a quick sanity check along with verifying what
  1.5212 +  # happens to overflow (we want it to wrap):
  1.5213 +  $address_length = 8;
  1.5214 +  foreach my $row (@{$test_data_8}) {
  1.5215 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5216 +    my $sum = AddressSub ($row->[0], $row->[1]);
  1.5217 +    if ($sum ne $row->[3]) {
  1.5218 +      printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
  1.5219 +             $row->[0], $row->[1], $row->[3];
  1.5220 +      ++$fail_count;
  1.5221 +    } else {
  1.5222 +      ++$pass_count;
  1.5223 +    }
  1.5224 +  }
  1.5225 +  printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n",
  1.5226 +         $pass_count, $fail_count;
  1.5227 +  $error_count = $fail_count;
  1.5228 +  $fail_count = 0;
  1.5229 +  $pass_count = 0;
  1.5230 +
  1.5231 +  # Now 16-nibble addresses.
  1.5232 +  $address_length = 16;
  1.5233 +  foreach my $row (@{$test_data_16}) {
  1.5234 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5235 +    my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));
  1.5236 +    if ($sum ne CanonicalHex($row->[3])) {
  1.5237 +      printf STDERR "ERROR: %s != %s - %s = %s\n", $sum,
  1.5238 +             $row->[0], $row->[1], $row->[3];
  1.5239 +      ++$fail_count;
  1.5240 +    } else {
  1.5241 +      ++$pass_count;
  1.5242 +    }
  1.5243 +  }
  1.5244 +  printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n",
  1.5245 +         $pass_count, $fail_count;
  1.5246 +  $error_count += $fail_count;
  1.5247 +
  1.5248 +  return $error_count;
  1.5249 +}
  1.5250 +
  1.5251 +
  1.5252 +# Unit test for AddressInc:
  1.5253 +sub AddressIncUnitTest {
  1.5254 +  my $test_data_8 = shift;
  1.5255 +  my $test_data_16 = shift;
  1.5256 +  my $error_count = 0;
  1.5257 +  my $fail_count = 0;
  1.5258 +  my $pass_count = 0;
  1.5259 +  # print STDERR "AddressIncUnitTest: ", 1+$#{$test_data_8}, " tests\n";
  1.5260 +
  1.5261 +  # First a few 8-nibble addresses.  Note that this implementation uses
  1.5262 +  # plain old arithmetic, so a quick sanity check along with verifying what
  1.5263 +  # happens to overflow (we want it to wrap):
  1.5264 +  $address_length = 8;
  1.5265 +  foreach my $row (@{$test_data_8}) {
  1.5266 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5267 +    my $sum = AddressInc ($row->[0]);
  1.5268 +    if ($sum ne $row->[4]) {
  1.5269 +      printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
  1.5270 +             $row->[0], $row->[4];
  1.5271 +      ++$fail_count;
  1.5272 +    } else {
  1.5273 +      ++$pass_count;
  1.5274 +    }
  1.5275 +  }
  1.5276 +  printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n",
  1.5277 +         $pass_count, $fail_count;
  1.5278 +  $error_count = $fail_count;
  1.5279 +  $fail_count = 0;
  1.5280 +  $pass_count = 0;
  1.5281 +
  1.5282 +  # Now 16-nibble addresses.
  1.5283 +  $address_length = 16;
  1.5284 +  foreach my $row (@{$test_data_16}) {
  1.5285 +    if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; }
  1.5286 +    my $sum = AddressInc (CanonicalHex($row->[0]));
  1.5287 +    if ($sum ne CanonicalHex($row->[4])) {
  1.5288 +      printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum,
  1.5289 +             $row->[0], $row->[4];
  1.5290 +      ++$fail_count;
  1.5291 +    } else {
  1.5292 +      ++$pass_count;
  1.5293 +    }
  1.5294 +  }
  1.5295 +  printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n",
  1.5296 +         $pass_count, $fail_count;
  1.5297 +  $error_count += $fail_count;
  1.5298 +
  1.5299 +  return $error_count;
  1.5300 +}
  1.5301 +
  1.5302 +
  1.5303 +# Driver for unit tests.
  1.5304 +# Currently just the address add/subtract/increment routines for 64-bit.
  1.5305 +sub RunUnitTests {
  1.5306 +  my $error_count = 0;
  1.5307 +
  1.5308 +  # This is a list of tuples [a, b, a+b, a-b, a+1]
  1.5309 +  my $unit_test_data_8 = [
  1.5310 +    [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],
  1.5311 +    [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],
  1.5312 +    [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],
  1.5313 +    [qw(00000001 ffffffff 00000000 00000002 00000002)],
  1.5314 +    [qw(00000001 fffffff0 fffffff1 00000011 00000002)],
  1.5315 +  ];
  1.5316 +  my $unit_test_data_16 = [
  1.5317 +    # The implementation handles data in 7-nibble chunks, so those are the
  1.5318 +    # interesting boundaries.
  1.5319 +    [qw(aaaaaaaa 50505050
  1.5320 +        00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],
  1.5321 +    [qw(50505050 aaaaaaaa
  1.5322 +        00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],
  1.5323 +    [qw(ffffffff aaaaaaaa
  1.5324 +        00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],
  1.5325 +    [qw(00000001 ffffffff
  1.5326 +        00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],
  1.5327 +    [qw(00000001 fffffff0
  1.5328 +        00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],
  1.5329 +
  1.5330 +    [qw(00_a00000a_aaaaaaa 50505050
  1.5331 +        00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],
  1.5332 +    [qw(0f_fff0005_0505050 aaaaaaaa
  1.5333 +        0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],
  1.5334 +    [qw(00_000000f_fffffff 01_800000a_aaaaaaa
  1.5335 +        01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],
  1.5336 +    [qw(00_0000000_0000001 ff_fffffff_fffffff
  1.5337 +        00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],
  1.5338 +    [qw(00_0000000_0000001 ff_fffffff_ffffff0
  1.5339 +        ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],
  1.5340 +  ];
  1.5341 +
  1.5342 +  $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);
  1.5343 +  $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);
  1.5344 +  $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);
  1.5345 +  if ($error_count > 0) {
  1.5346 +    print STDERR $error_count, " errors: FAILED\n";
  1.5347 +  } else {
  1.5348 +    print STDERR "PASS\n";
  1.5349 +  }
  1.5350 +  exit ($error_count);
  1.5351 +}

mercurial