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