#!/usr/bin/env python # # Copyright 2012 VMware Inc # Copyright 2008-2009 Jose Fonseca # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # """Perf annotate for JIT code. Linux `perf annotate` does not work with JIT code. This script takes the data produced by `perf script` command, plus the diassemblies outputed by gallivm into /tmp/perf-XXXXX.map.asm and produces output similar to `perf annotate`. See docs/llvmpipe.html for usage instructions. The `perf script` output parser was derived from the gprof2dot.py script. """ import sys import os.path import re import optparse import subprocess class Parser: """Parser interface.""" def __init__(self): pass def parse(self): raise NotImplementedError class LineParser(Parser): """Base class for parsers that read line-based formats.""" def __init__(self, file): Parser.__init__(self) self._file = file self.__line = None self.__eof = False self.line_no = 0 def readline(self): line = self._file.readline() if not line: self.__line = '' self.__eof = True else: self.line_no += 1 self.__line = line.rstrip('\r\n') def lookahead(self): assert self.__line is not None return self.__line def consume(self): assert self.__line is not None line = self.__line self.readline() return line def eof(self): assert self.__line is not None return self.__eof mapFile = None def lookupMap(filename, matchSymbol): global mapFile mapFile = filename stream = open(filename, 'rt') for line in stream: start, length, symbol = line.split() start = int(start, 16) length = int(length,16) if symbol == matchSymbol: return start return None def lookupAsm(filename, desiredFunction): stream = open(filename + '.asm', 'rt') while stream.readline() != desiredFunction + ':\n': pass asm = [] line = stream.readline().strip() while line: addr, instr = line.split(':', 1) addr = int(addr) asm.append((addr, instr)) line = stream.readline().strip() return asm samples = {} class PerfParser(LineParser): """Parser for linux perf callgraph output. It expects output generated with perf record -g perf script """ def __init__(self, infile, symbol): LineParser.__init__(self, infile) self.symbol = symbol def readline(self): # Override LineParser.readline to ignore comment lines while True: LineParser.readline(self) if self.eof() or not self.lookahead().startswith('#'): break def parse(self): # read lookahead self.readline() while not self.eof(): self.parse_event() asm = lookupAsm(mapFile, self.symbol) addresses = samples.keys() addresses.sort() total_samples = 0 sys.stdout.write('%s:\n' % self.symbol) for address, instr in asm: try: sample = samples.pop(address) except KeyError: sys.stdout.write(6*' ') else: sys.stdout.write('%6u' % (sample)) total_samples += sample sys.stdout.write('%6u: %s\n' % (address, instr)) print 'total:', total_samples assert len(samples) == 0 sys.exit(0) def parse_event(self): if self.eof(): return line = self.consume() assert line callchain = self.parse_callchain() if not callchain: return def parse_callchain(self): callchain = [] while self.lookahead(): function = self.parse_call(len(callchain) == 0) if function is None: break callchain.append(function) if self.lookahead() == '': self.consume() return callchain call_re = re.compile(r'^\s+(?P<address>[0-9a-fA-F]+)\s+(?P<symbol>.*)\s+\((?P<module>[^)]*)\)$') def parse_call(self, first): line = self.consume() mo = self.call_re.match(line) assert mo if not mo: return None if not first: return None function_name = mo.group('symbol') if not function_name: function_name = mo.group('address') module = mo.group('module') function_id = function_name + ':' + module address = mo.group('address') address = int(address, 16) if function_name != self.symbol: return None start_address = lookupMap(module, function_name) address -= start_address #print function_name, module, address samples[address] = samples.get(address, 0) + 1 return True def main(): """Main program.""" optparser = optparse.OptionParser( usage="\n\t%prog [options] symbol_name") (options, args) = optparser.parse_args(sys.argv[1:]) if len(args) != 1: optparser.error('wrong number of arguments') symbol = args[0] p = subprocess.Popen(['perf', 'script'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) parser = PerfParser(p.stdout, symbol) parser.parse() if __name__ == '__main__': main() # vim: set sw=4 et: