1 #!/usr/bin/env python 2 # 3 # Copyright 2012 VMware Inc 4 # Copyright 2008-2009 Jose Fonseca 5 # 6 # Permission is hereby granted, free of charge, to any person obtaining a copy 7 # of this software and associated documentation files (the "Software"), to deal 8 # in the Software without restriction, including without limitation the rights 9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 # copies of the Software, and to permit persons to whom the Software is 11 # furnished to do so, subject to the following conditions: 12 # 13 # The above copyright notice and this permission notice shall be included in 14 # all copies or substantial portions of the Software. 15 # 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 # THE SOFTWARE. 23 # 24 25 """Perf annotate for JIT code. 26 27 Linux `perf annotate` does not work with JIT code. This script takes the data 28 produced by `perf script` command, plus the diassemblies outputed by gallivm 29 into /tmp/perf-XXXXX.map.asm and produces output similar to `perf annotate`. 30 31 See docs/llvmpipe.html for usage instructions. 32 33 The `perf script` output parser was derived from the gprof2dot.py script. 34 """ 35 36 37 import sys 38 import os.path 39 import re 40 import optparse 41 import subprocess 42 43 44 class Parser: 45 """Parser interface.""" 46 47 def __init__(self): 48 pass 49 50 def parse(self): 51 raise NotImplementedError 52 53 54 class LineParser(Parser): 55 """Base class for parsers that read line-based formats.""" 56 57 def __init__(self, file): 58 Parser.__init__(self) 59 self._file = file 60 self.__line = None 61 self.__eof = False 62 self.line_no = 0 63 64 def readline(self): 65 line = self._file.readline() 66 if not line: 67 self.__line = '' 68 self.__eof = True 69 else: 70 self.line_no += 1 71 self.__line = line.rstrip('\r\n') 72 73 def lookahead(self): 74 assert self.__line is not None 75 return self.__line 76 77 def consume(self): 78 assert self.__line is not None 79 line = self.__line 80 self.readline() 81 return line 82 83 def eof(self): 84 assert self.__line is not None 85 return self.__eof 86 87 88 mapFile = None 89 90 def lookupMap(filename, matchSymbol): 91 global mapFile 92 mapFile = filename 93 stream = open(filename, 'rt') 94 for line in stream: 95 start, length, symbol = line.split() 96 97 start = int(start, 16) 98 length = int(length,16) 99 100 if symbol == matchSymbol: 101 return start 102 103 return None 104 105 def lookupAsm(filename, desiredFunction): 106 stream = open(filename + '.asm', 'rt') 107 while stream.readline() != desiredFunction + ':\n': 108 pass 109 110 asm = [] 111 line = stream.readline().strip() 112 while line: 113 addr, instr = line.split(':', 1) 114 addr = int(addr) 115 asm.append((addr, instr)) 116 line = stream.readline().strip() 117 118 return asm 119 120 121 122 samples = {} 123 124 125 class PerfParser(LineParser): 126 """Parser for linux perf callgraph output. 127 128 It expects output generated with 129 130 perf record -g 131 perf script 132 """ 133 134 def __init__(self, infile, symbol): 135 LineParser.__init__(self, infile) 136 self.symbol = symbol 137 138 def readline(self): 139 # Override LineParser.readline to ignore comment lines 140 while True: 141 LineParser.readline(self) 142 if self.eof() or not self.lookahead().startswith('#'): 143 break 144 145 def parse(self): 146 # read lookahead 147 self.readline() 148 149 while not self.eof(): 150 self.parse_event() 151 152 asm = lookupAsm(mapFile, self.symbol) 153 154 addresses = samples.keys() 155 addresses.sort() 156 total_samples = 0 157 158 sys.stdout.write('%s:\n' % self.symbol) 159 for address, instr in asm: 160 try: 161 sample = samples.pop(address) 162 except KeyError: 163 sys.stdout.write(6*' ') 164 else: 165 sys.stdout.write('%6u' % (sample)) 166 total_samples += sample 167 sys.stdout.write('%6u: %s\n' % (address, instr)) 168 print 'total:', total_samples 169 assert len(samples) == 0 170 171 sys.exit(0) 172 173 def parse_event(self): 174 if self.eof(): 175 return 176 177 line = self.consume() 178 assert line 179 180 callchain = self.parse_callchain() 181 if not callchain: 182 return 183 184 def parse_callchain(self): 185 callchain = [] 186 while self.lookahead(): 187 function = self.parse_call(len(callchain) == 0) 188 if function is None: 189 break 190 callchain.append(function) 191 if self.lookahead() == '': 192 self.consume() 193 return callchain 194 195 call_re = re.compile(r'^\s+(?P<address>[0-9a-fA-F]+)\s+(?P<symbol>.*)\s+\((?P<module>[^)]*)\)$') 196 197 def parse_call(self, first): 198 line = self.consume() 199 mo = self.call_re.match(line) 200 assert mo 201 if not mo: 202 return None 203 204 if not first: 205 return None 206 207 function_name = mo.group('symbol') 208 if not function_name: 209 function_name = mo.group('address') 210 211 module = mo.group('module') 212 213 function_id = function_name + ':' + module 214 215 address = mo.group('address') 216 address = int(address, 16) 217 218 if function_name != self.symbol: 219 return None 220 221 start_address = lookupMap(module, function_name) 222 address -= start_address 223 224 #print function_name, module, address 225 226 samples[address] = samples.get(address, 0) + 1 227 228 return True 229 230 231 def main(): 232 """Main program.""" 233 234 optparser = optparse.OptionParser( 235 usage="\n\t%prog [options] symbol_name") 236 (options, args) = optparser.parse_args(sys.argv[1:]) 237 if len(args) != 1: 238 optparser.error('wrong number of arguments') 239 240 symbol = args[0] 241 242 p = subprocess.Popen(['perf', 'script'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 243 parser = PerfParser(p.stdout, symbol) 244 parser.parse() 245 246 247 if __name__ == '__main__': 248 main() 249 250 251 # vim: set sw=4 et: 252