| #!/usr/bin/env python |
| ########################################################################## |
| # |
| # Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas. |
| # All Rights Reserved. |
| # |
| # 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, sub license, 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 (including the |
| # next paragraph) 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 NON-INFRINGEMENT. |
| # IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. |
| # |
| ########################################################################## |
| |
| |
| import sys |
| import optparse |
| import re |
| import struct |
| |
| from gprof2dot import Call, Function, Profile |
| from gprof2dot import CALLS, SAMPLES, TIME, TIME_RATIO, TOTAL_TIME, TOTAL_TIME_RATIO |
| from gprof2dot import DotWriter, TEMPERATURE_COLORMAP |
| |
| |
| __version__ = '0.1' |
| |
| |
| class ParseError(Exception): |
| pass |
| |
| |
| class MsvcDemangler: |
| # http://www.kegel.com/mangle.html |
| |
| def __init__(self, symbol): |
| self._symbol = symbol |
| self._pos = 0 |
| |
| def lookahead(self): |
| return self._symbol[self._pos] |
| |
| def consume(self): |
| ret = self.lookahead() |
| self._pos += 1 |
| return ret |
| |
| def match(self, c): |
| if self.lookahead() != c: |
| raise ParseError |
| self.consume() |
| |
| def parse(self): |
| self.match('?') |
| name = self.parse_name() |
| qualifications = self.parse_qualifications() |
| return '::'.join(qualifications + [name]) |
| |
| def parse_name(self): |
| if self.lookahead() == '?': |
| return self.consume() + self.consume() |
| else: |
| name = self.parse_id() |
| self.match('@') |
| return name |
| |
| def parse_qualifications(self): |
| qualifications = [] |
| while self.lookahead() != '@': |
| name = self.parse_id() |
| qualifications.append(name) |
| self.match('@') |
| return qualifications |
| |
| def parse_id(self): |
| s = '' |
| while True: |
| c = self.lookahead() |
| if c.isalnum() or c in '_': |
| s += c |
| self.consume() |
| else: |
| break |
| return s |
| |
| |
| def demangle(name): |
| if name.startswith('_'): |
| name = name[1:] |
| idx = name.rfind('@') |
| if idx != -1 and name[idx+1:].isdigit(): |
| name = name[:idx] |
| return name |
| if name.startswith('?'): |
| demangler = MsvcDemangler(name) |
| return demangler.parse() |
| return name |
| |
| |
| class Reader: |
| |
| def __init__(self): |
| self.symbols = [] |
| self.symbol_cache = {} |
| self.base_addr = None |
| |
| def read_map(self, mapfile): |
| # See http://msdn.microsoft.com/en-us/library/k7xkk3e2.aspx |
| last_addr = 0 |
| last_name = 0 |
| for line in file(mapfile, "rt"): |
| fields = line.split() |
| try: |
| section_offset, name, addr, type, lib_object = fields |
| except ValueError: |
| continue |
| if type != 'f': |
| continue |
| section, offset = section_offset.split(':') |
| addr = int(offset, 16) |
| self.symbols.append((addr, name)) |
| last_addr = addr |
| last_name = name |
| |
| # sort symbols |
| self.symbols.sort(key = lambda (addr, name): addr) |
| |
| def lookup_addr(self, addr): |
| try: |
| return self.symbol_cache[addr] |
| except KeyError: |
| pass |
| |
| tolerance = 4196 |
| s, e = 0, len(self.symbols) |
| while s != e: |
| i = (s + e)//2 |
| start_addr, name = self.symbols[i] |
| try: |
| end_addr, next_name = self.symbols[i + 1] |
| except IndexError: |
| end_addr = start_addr + tolerance |
| if addr < start_addr: |
| e = i |
| continue |
| if addr == end_addr: |
| return next_name, addr - start_addr |
| if addr > end_addr: |
| s = i |
| continue |
| return name, addr - start_addr |
| raise ValueError |
| |
| def lookup_symbol(self, name): |
| for symbol_addr, symbol_name in self.symbols: |
| if name == symbol_name: |
| return symbol_addr |
| return 0 |
| |
| def read_data(self, data): |
| profile = Profile() |
| |
| fp = file(data, "rb") |
| entry_format = "IIII" |
| entry_size = struct.calcsize(entry_format) |
| caller = None |
| caller_stack = [] |
| while True: |
| entry = fp.read(entry_size) |
| if len(entry) < entry_size: |
| break |
| caller_addr, callee_addr, samples_lo, samples_hi = struct.unpack(entry_format, entry) |
| if caller_addr == 0 and callee_addr == 0: |
| continue |
| |
| if self.base_addr is None: |
| ref_addr = self.lookup_symbol('___debug_profile_reference@0') |
| if ref_addr: |
| self.base_addr = (caller_addr - ref_addr) & ~(options.align - 1) |
| else: |
| self.base_addr = 0 |
| sys.stderr.write('Base addr: %08x\n' % self.base_addr) |
| |
| samples = (samples_hi << 32) | samples_lo |
| |
| try: |
| caller_raddr = caller_addr - self.base_addr |
| caller_sym, caller_ofs = self.lookup_addr(caller_raddr) |
| |
| try: |
| caller = profile.functions[caller_sym] |
| except KeyError: |
| caller_name = demangle(caller_sym) |
| caller = Function(caller_sym, caller_name) |
| profile.add_function(caller) |
| caller[CALLS] = 0 |
| caller[SAMPLES] = 0 |
| except ValueError: |
| caller = None |
| |
| if not callee_addr: |
| if caller: |
| caller[SAMPLES] += samples |
| else: |
| callee_raddr = callee_addr - self.base_addr |
| callee_sym, callee_ofs = self.lookup_addr(callee_raddr) |
| |
| try: |
| callee = profile.functions[callee_sym] |
| except KeyError: |
| callee_name = demangle(callee_sym) |
| callee = Function(callee_sym, callee_name) |
| profile.add_function(callee) |
| callee[CALLS] = samples |
| callee[SAMPLES] = 0 |
| else: |
| callee[CALLS] += samples |
| |
| if caller is not None: |
| try: |
| call = caller.calls[callee.id] |
| except KeyError: |
| call = Call(callee.id) |
| call[CALLS] = samples |
| caller.add_call(call) |
| else: |
| call[CALLS] += samples |
| |
| if options.verbose: |
| if not callee_addr: |
| sys.stderr.write('%s+%u: %u\n' % (caller_sym, caller_ofs, samples)) |
| else: |
| sys.stderr.write('%s+%u -> %s+%u: %u\n' % (caller_sym, caller_ofs, callee_sym, callee_ofs, samples)) |
| |
| # compute derived data |
| profile.validate() |
| profile.find_cycles() |
| profile.aggregate(SAMPLES) |
| profile.ratio(TIME_RATIO, SAMPLES) |
| profile.call_ratios(CALLS) |
| profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) |
| |
| return profile |
| |
| |
| def main(): |
| parser = optparse.OptionParser( |
| usage="\n\t%prog [options] [file] ...", |
| version="%%prog %s" % __version__) |
| parser.add_option( |
| '-a', '--align', metavar='NUMBER', |
| type="int", dest="align", default=16, |
| help="section alignment") |
| parser.add_option( |
| '-m', '--map', metavar='FILE', |
| type="string", dest="map", |
| help="map file") |
| parser.add_option( |
| '-b', '--base', metavar='FILE', |
| type="string", dest="base", |
| help="base addr") |
| parser.add_option( |
| '-n', '--node-thres', metavar='PERCENTAGE', |
| type="float", dest="node_thres", default=0.5, |
| help="eliminate nodes below this threshold [default: %default]") |
| parser.add_option( |
| '-e', '--edge-thres', metavar='PERCENTAGE', |
| type="float", dest="edge_thres", default=0.1, |
| help="eliminate edges below this threshold [default: %default]") |
| parser.add_option( |
| '-v', '--verbose', |
| action="count", |
| dest="verbose", default=0, |
| help="verbose output") |
| |
| global options |
| (options, args) = parser.parse_args(sys.argv[1:]) |
| |
| reader = Reader() |
| if options.base is not None: |
| reader.base_addr = int(options.base, 16) |
| if options.map is not None: |
| reader.read_map(options.map) |
| for arg in args: |
| profile = reader.read_data(arg) |
| profile.prune(options.node_thres/100.0, options.edge_thres/100.0) |
| output = sys.stdout |
| dot = DotWriter(output) |
| colormap = TEMPERATURE_COLORMAP |
| dot.graph(profile, colormap) |
| |
| |
| if __name__ == '__main__': |
| main() |
| |