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