| #!/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: |