memory/jemalloc/src/bin/pprof

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial