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