Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

  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.