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