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