Home | History | Annotate | Download | only in bin
      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