From 6087a16e81d5c41647e05291dd25bb6eac9493eb Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Fri, 5 Nov 2010 18:10:29 +0100 Subject: [PATCH] Support both Python 2.x and 3.x --- gprof2dot.py | 138 ++++++++++++++++++++++++++++++++++------------------------ 1 files changed, 81 insertions(+), 57 deletions(-) diff --git a/gprof2dot.py b/gprof2dot.py index bf0aba8..888081f 100755 --- a/gprof2dot.py +++ b/gprof2dot.py @@ -32,6 +32,27 @@ import optparse import xml.parsers.expat +# Python 2.x/3.x compatibility +if sys.version_info[0] == 3: + PYTHON_3 = True + def compat_iteritems(x): return x.items() # No iteritems() in Python 3 + def compat_itervalues(x): return x.values() # No itervalues() in Python 3 + def compat_keys(x): return list(x.keys()) # keys() is a generator in Python 3 + compat_basestring = str # No class basestring in Python 3 + + CALL_TIMES_FORMAT = "%u\xd7" # All strings are unicode in Python 3, no u"" marking +else: + PYTHON_3 = False + def compat_iteritems(x): return x.iteritems() + def compat_itervalues(x): return x.itervalues() + def compat_keys(x): return x.keys() + compat_basestring = basestring + + # u"" strings not supported in Python 3 + # By using eval() we don't get a syntax error + CALL_TIMES_FORMAT = eval('u"%u\xd7"') + + try: # Debugging helper module import debug @@ -40,7 +61,7 @@ except ImportError: def times(x): - return u"%u\xd7" % (x,) + return CALL_TIMES_FORMAT % (x,) def percentage(p): return "%.02f%%" % (p*100.0,) @@ -236,8 +257,8 @@ class Profile(Object): def validate(self): """Validate the edges.""" - for function in self.functions.itervalues(): - for callee_id in function.calls.keys(): + for function in compat_itervalues(self.functions): + for callee_id in compat_keys(function.calls): assert function.calls[callee_id].callee_id == callee_id if callee_id not in self.functions: sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name)) @@ -248,11 +269,11 @@ class Profile(Object): # Apply the Tarjan's algorithm successively until all functions are visited visited = set() - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): if function not in visited: self._tarjan(function, 0, [], {}, {}, visited) cycles = [] - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): if function.cycle is not None and function.cycle not in cycles: cycles.append(function.cycle) self.cycles = cycles @@ -275,7 +296,7 @@ class Profile(Object): order += 1 pos = len(stack) stack.append(function) - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): callee = self.functions[call.callee_id] # TODO: use a set to optimize lookup if callee not in orders: @@ -299,10 +320,10 @@ class Profile(Object): for cycle in self.cycles: cycle_totals[cycle] = 0.0 function_totals = {} - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): function_totals[function] = 0.0 - for function in self.functions.itervalues(): - for call in function.calls.itervalues(): + for function in compat_itervalues(self.functions): + for call in compat_itervalues(function.calls): if call.callee_id != function.id: callee = self.functions[call.callee_id] function_totals[callee] += call[event] @@ -310,8 +331,8 @@ class Profile(Object): cycle_totals[callee.cycle] += call[event] # Compute the ratios - for function in self.functions.itervalues(): - for call in function.calls.itervalues(): + for function in compat_itervalues(self.functions): + for call in compat_itervalues(function.calls): assert call.ratio is None if call.callee_id != function.id: callee = self.functions[call.callee_id] @@ -332,10 +353,10 @@ class Profile(Object): # Sanity checking assert outevent not in self - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): assert outevent not in function assert inevent in function - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): assert outevent not in call if call.callee_id != function.id: assert call.ratio is not None @@ -343,13 +364,13 @@ class Profile(Object): # Aggregate the input for each cycle for cycle in self.cycles: total = inevent.null() - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): total = inevent.aggregate(total, function[inevent]) self[inevent] = total # Integrate along the edges total = inevent.null() - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): total = inevent.aggregate(total, function[inevent]) self._integrate_function(function, outevent, inevent) self[outevent] = total @@ -360,7 +381,7 @@ class Profile(Object): else: if outevent not in function: total = function[inevent] - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): if call.callee_id != function.id: total += self._integrate_call(call, outevent, inevent) function[outevent] = total @@ -381,7 +402,7 @@ class Profile(Object): total = inevent.null() for member in cycle.functions: subtotal = member[inevent] - for call in member.calls.itervalues(): + for call in compat_itervalues(member.calls): callee = self.functions[call.callee_id] if callee.cycle is not cycle: subtotal += self._integrate_call(call, outevent, inevent) @@ -390,9 +411,9 @@ class Profile(Object): # Compute the time propagated to callers of this cycle callees = {} - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): if function.cycle is not cycle: - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): callee = self.functions[call.callee_id] if callee.cycle is cycle: try: @@ -403,7 +424,7 @@ class Profile(Object): for member in cycle.functions: member[outevent] = outevent.null() - for callee, call_ratio in callees.iteritems(): + for callee, call_ratio in compat_iteritems(callees): ranks = {} call_ratios = {} partials = {} @@ -418,7 +439,7 @@ class Profile(Object): def _rank_cycle_function(self, cycle, function, rank, ranks): if function not in ranks or ranks[function] > rank: ranks[function] = rank - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): if call.callee_id != function.id: callee = self.functions[call.callee_id] if callee.cycle is cycle: @@ -427,7 +448,7 @@ class Profile(Object): def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited): if function not in visited: visited.add(function) - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): if call.callee_id != function.id: callee = self.functions[call.callee_id] if callee.cycle is cycle: @@ -438,7 +459,7 @@ class Profile(Object): def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent): if function not in partials: partial = partial_ratio*function[inevent] - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): if call.callee_id != function.id: callee = self.functions[call.callee_id] if callee.cycle is not cycle: @@ -465,7 +486,7 @@ class Profile(Object): """Aggregate an event for the whole profile.""" total = event.null() - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): try: total = event.aggregate(total, function[event]) except UndefinedEvent: @@ -475,11 +496,11 @@ class Profile(Object): def ratio(self, outevent, inevent): assert outevent not in self assert inevent in self - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): assert outevent not in function assert inevent in function function[outevent] = ratio(function[inevent], self[inevent]) - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): assert outevent not in call if inevent in call: call[outevent] = ratio(call[inevent], self[inevent]) @@ -489,13 +510,13 @@ class Profile(Object): """Prune the profile""" # compute the prune ratios - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): try: function.weight = function[TOTAL_TIME_RATIO] except UndefinedEvent: pass - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): callee = self.functions[call.callee_id] if TOTAL_TIME_RATIO in call: @@ -509,24 +530,24 @@ class Profile(Object): pass # prune the nodes - for function_id in self.functions.keys(): + for function_id in compat_keys(self.functions): function = self.functions[function_id] if function.weight is not None: if function.weight < node_thres: del self.functions[function_id] # prune the egdes - for function in self.functions.itervalues(): - for callee_id in function.calls.keys(): + for function in compat_itervalues(self.functions): + for callee_id in compat_keys(function.calls): call = function.calls[callee_id] if callee_id not in self.functions or call.weight is not None and call.weight < edge_thres: del function.calls[callee_id] def dump(self): - for function in self.functions.itervalues(): + for function in compat_itervalues(self.functions): sys.stderr.write('Function %s:\n' % (function.name,)) self._dump_events(function.events) - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): callee = self.functions[call.callee_id] sys.stderr.write(' Call %s:\n' % (callee.name,)) self._dump_events(call.events) @@ -537,7 +558,7 @@ class Profile(Object): sys.stderr.write(' Function %s\n' % (function.name,)) def _dump_events(self, events): - for event, value in events.iteritems(): + for event, value in compat_iteritems(events): sys.stderr.write(' %s: %s\n' % (event.name, event.format(value))) @@ -695,7 +716,7 @@ class XmlTokenizer: self.final = len(data) < size try: self.parser.Parse(data, self.final) - except xml.parsers.expat.ExpatError, e: + except xml.parsers.expat.ExpatError as e: #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS: if e.code == 3: pass @@ -801,7 +822,7 @@ class GprofParser(Parser): """Extract a structure from a match object, while translating the types in the process.""" attrs = {} groupdict = mo.groupdict() - for name, value in groupdict.iteritems(): + for name, value in compat_iteritems(groupdict): if value is None: value = None elif self._int_re.match(value): @@ -977,7 +998,7 @@ class GprofParser(Parser): for index in self.cycles.iterkeys(): cycles[index] = Cycle() - for entry in self.functions.itervalues(): + for entry in compat_itervalues(self.functions): # populate the function function = Function(entry.index, entry.name) function[TIME] = entry.self @@ -1019,7 +1040,7 @@ class GprofParser(Parser): profile[TIME] = profile[TIME] + function[TIME] - for cycle in cycles.itervalues(): + for cycle in compat_itervalues(cycles): profile.add_cycle(cycle) # Compute derived events @@ -1350,7 +1371,7 @@ class OprofileParser(LineParser): self.update_subentries_dict(callees_total, callees) def update_subentries_dict(self, totals, partials): - for partial in partials.itervalues(): + for partial in compat_itervalues(partials): try: total = totals[partial.id] except KeyError: @@ -1372,7 +1393,7 @@ class OprofileParser(LineParser): # populate the profile profile[SAMPLES] = 0 - for _callers, _function, _callees in self.entries.itervalues(): + for _callers, _function, _callees in compat_itervalues(self.entries): function = Function(_function.id, _function.name) function[SAMPLES] = _function.samples profile.add_function(function) @@ -1384,10 +1405,10 @@ class OprofileParser(LineParser): function.module = os.path.basename(_function.image) total_callee_samples = 0 - for _callee in _callees.itervalues(): + for _callee in compat_itervalues(_callees): total_callee_samples += _callee.samples - for _callee in _callees.itervalues(): + for _callee in compat_itervalues(_callees): if not _callee.self: call = Call(_callee.id) call[SAMPLES2] = _callee.samples @@ -1548,7 +1569,7 @@ class SysprofParser(XmlParser): profile = Profile() profile[SAMPLES] = 0 - for id, object in objects.iteritems(): + for id, object in compat_iteritems(objects): # Ignore fake objects (process names, modules, "Everything", "kernel", etc.) if object['self'] == 0: continue @@ -1558,7 +1579,7 @@ class SysprofParser(XmlParser): profile.add_function(function) profile[SAMPLES] += function[SAMPLES] - for id, node in nodes.iteritems(): + for id, node in compat_iteritems(nodes): # Ignore fake calls if node['self'] == 0: continue @@ -1672,7 +1693,7 @@ class SharkParser(LineParser): profile = Profile() profile[SAMPLES] = 0 - for _function, _callees in self.entries.itervalues(): + for _function, _callees in compat_itervalues(self.entries): function = Function(_function.id, _function.name) function[SAMPLES] = _function.samples profile.add_function(function) @@ -1681,7 +1702,7 @@ class SharkParser(LineParser): if _function.image: function.module = os.path.basename(_function.image) - for _callee in _callees.itervalues(): + for _callee in compat_itervalues(_callees): call = Call(_callee.id) call[SAMPLES] = _callee.samples function.add_call(call) @@ -1965,7 +1986,8 @@ class PstatsParser: self.profile = Profile() self.function_ids = {} - def get_function_name(self, (filename, line, name)): + def get_function_name(self, info): + filename, line, name = info module = os.path.splitext(filename)[0] module = os.path.basename(module) return "%s:%d:%s" % (module, line, name) @@ -1986,14 +2008,14 @@ class PstatsParser: def parse(self): self.profile[TIME] = 0.0 self.profile[TOTAL_TIME] = self.stats.total_tt - for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems(): + for fn, (cc, nc, tt, ct, callers) in compat_iteritems(self.stats.stats): callee = self.get_function(fn) callee.called = nc callee[TOTAL_TIME] = ct callee[TIME] = tt self.profile[TIME] += tt self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct) - for fn, value in callers.iteritems(): + for fn, value in compat_iteritems(callers): caller = self.get_function(fn) call = Call(callee.id) if isinstance(value, tuple): @@ -2190,7 +2212,7 @@ class DotWriter: self.attr('node', fontname=fontname, shape="box", style="filled", fontcolor="white", width=0, height=0) self.attr('edge', fontname=fontname) - for function in profile.functions.itervalues(): + for function in compat_itervalues(profile.functions): labels = [] if function.process is not None: labels.append(function.process) @@ -2202,7 +2224,7 @@ class DotWriter: label = event.format(function[event]) labels.append(label) if function.called is not None: - labels.append(u"%u\xd7" % (function.called,)) + labels.append(CALL_TIMES_FORMAT % (function.called,)) if function.weight is not None: weight = function.weight @@ -2217,7 +2239,7 @@ class DotWriter: fontsize = "%.2f" % theme.node_fontsize(weight), ) - for call in function.calls.itervalues(): + for call in compat_itervalues(function.calls): callee = profile.functions[call.callee_id] labels = [] @@ -2278,7 +2300,7 @@ class DotWriter: return self.write(' [') first = True - for name, value in attrs.iteritems(): + for name, value in compat_iteritems(attrs): if first: first = False else: @@ -2291,7 +2313,7 @@ class DotWriter: def id(self, id): if isinstance(id, (int, float)): s = str(id) - elif isinstance(id, basestring): + elif isinstance(id, compat_basestring): if id.isalnum() and not id.startswith('0x'): s = id else: @@ -2300,7 +2322,8 @@ class DotWriter: raise TypeError self.write(s) - def color(self, (r, g, b)): + def color(self, rgb): + r, g, b = rgb def float2int(f): if f <= 0.0: @@ -2312,7 +2335,8 @@ class DotWriter: return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)]) def escape(self, s): - s = s.encode('utf-8') + if not PYTHON_3: + s = s.encode('utf-8') s = s.replace('\\', r'\\') s = s.replace('\n', r'\n') s = s.replace('\t', r'\t') @@ -2505,7 +2529,7 @@ class Main: profile = self.profile profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0) - for function in profile.functions.itervalues(): + for function in compat_itervalues(profile.functions): function.name = self.compress_function_name(function.name) dot.graph(profile, self.theme) -- 1.7.3.2