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 27Linux `perf annotate` does not work with JIT code. This script takes the data 28produced by `perf script` command, plus the diassemblies outputed by gallivm 29into /tmp/perf-XXXXX.map.asm and produces output similar to `perf annotate`. 30 31See docs/llvmpipe.rst for usage instructions. 32 33The `perf script` output parser was derived from the gprof2dot.py script. 34""" 35 36 37import sys 38import os.path 39import re 40import optparse 41import subprocess 42 43 44class Parser: 45 """Parser interface.""" 46 47 def __init__(self): 48 pass 49 50 def parse(self): 51 raise NotImplementedError 52 53 54class 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 88mapFile = None 89 90def 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 105def 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 122samples = {} 123 124 125class 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 231def 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 247if __name__ == '__main__': 248 main() 249 250 251# vim: set sw=4 et: 252