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