summaryrefslogtreecommitdiffstats
path: root/library/gprof2dot.py
diff options
context:
space:
mode:
Diffstat (limited to 'library/gprof2dot.py')
-rw-r--r--library/gprof2dot.py210
1 files changed, 105 insertions, 105 deletions
diff --git a/library/gprof2dot.py b/library/gprof2dot.py
index dc7bc8d..99a8c1b 100644
--- a/library/gprof2dot.py
+++ b/library/gprof2dot.py
@@ -79,7 +79,7 @@ def ratio(numerator, denominator):
class UndefinedEvent(Exception):
"""Raised when attempting to get an event which is undefined."""
-
+
def __init__(self, event):
Exception.__init__(self)
self.event = event
@@ -111,7 +111,7 @@ class Event(object):
assert val1 is not None
assert val2 is not None
return self._aggregator(val1, val2)
-
+
def format(self, val):
"""Format an event value."""
assert val is not None
@@ -145,13 +145,13 @@ class Object(object):
def __contains__(self, event):
return event in self.events
-
+
def __getitem__(self, event):
try:
return self.events[event]
except KeyError:
raise UndefinedEvent(event)
-
+
def __setitem__(self, event, value):
if value is None:
if event in self.events:
@@ -162,7 +162,7 @@ class Object(object):
class Call(Object):
"""A call between functions.
-
+
There should be at most one call object for every pair of functions.
"""
@@ -186,7 +186,7 @@ class Function(Object):
self.called = None
self.weight = None
self.cycle = None
-
+
def add_call(self, call):
if call.callee_id in self.calls:
sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id)))
@@ -270,7 +270,7 @@ class Profile(Object):
sys.stderr.write("Cycle:\n")
for member in cycle.functions:
sys.stderr.write("\tFunction %s\n" % member.name)
-
+
def _tarjan(self, function, order, stack, orders, lowlinks, visited):
"""Tarjan's strongly connected components algorithm.
@@ -349,7 +349,7 @@ class Profile(Object):
if call.callee_id != function.id:
assert call.ratio is not None
- # Aggregate the input for each cycle
+ # Aggregate the input for each cycle
for cycle in self.cycles:
total = inevent.null()
for function in self.functions.itervalues():
@@ -374,7 +374,7 @@ class Profile(Object):
total += self._integrate_call(call, outevent, inevent)
function[outevent] = total
return function[outevent]
-
+
def _integrate_call(self, call, outevent, inevent):
assert outevent not in call
assert call.ratio is not None
@@ -396,7 +396,7 @@ class Profile(Object):
subtotal += self._integrate_call(call, outevent, inevent)
total += subtotal
cycle[outevent] = total
-
+
# Compute the time propagated to callers of this cycle
callees = {}
for function in self.functions.itervalues():
@@ -408,7 +408,7 @@ class Profile(Object):
callees[callee] += call.ratio
except KeyError:
callees[callee] = call.ratio
-
+
for member in cycle.functions:
member[outevent] = outevent.null()
@@ -509,11 +509,11 @@ class Profile(Object):
if TOTAL_TIME_RATIO in call:
# handle exact cases first
- call.weight = call[TOTAL_TIME_RATIO]
+ call.weight = call[TOTAL_TIME_RATIO]
else:
try:
# make a safe estimate
- call.weight = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO])
+ call.weight = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO])
except UndefinedEvent:
pass
@@ -530,7 +530,7 @@ class Profile(Object):
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():
sys.stderr.write('Function %s:\n' % (function.name,))
@@ -557,7 +557,7 @@ class Struct:
if attrs is None:
attrs = {}
self.__dict__['_attrs'] = attrs
-
+
def __getattr__(self, name):
try:
return self._attrs[name]
@@ -572,7 +572,7 @@ class Struct:
def __repr__(self):
return repr(self._attrs)
-
+
class ParseError(Exception):
"""Raised when parsing to signal mismatches."""
@@ -595,7 +595,7 @@ class Parser:
def parse(self):
raise NotImplementedError
-
+
class LineParser(Parser):
"""Base class for parsers that read line-based formats."""
@@ -664,21 +664,21 @@ class XmlTokenizer:
self.index = 0
self.final = False
self.skip_ws = skip_ws
-
+
self.character_pos = 0, 0
self.character_data = ''
-
+
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.handle_element_start
self.parser.EndElementHandler = self.handle_element_end
self.parser.CharacterDataHandler = self.handle_character_data
-
+
def handle_element_start(self, name, attributes):
self.finish_character_data()
line, column = self.pos()
token = XmlToken(XML_ELEMENT_START, name, attributes, line, column)
self.tokens.append(token)
-
+
def handle_element_end(self, name):
self.finish_character_data()
line, column = self.pos()
@@ -689,15 +689,15 @@ class XmlTokenizer:
if not self.character_data:
self.character_pos = self.pos()
self.character_data += data
-
+
def finish_character_data(self):
if self.character_data:
- if not self.skip_ws or not self.character_data.isspace():
+ if not self.skip_ws or not self.character_data.isspace():
line, column = self.character_pos
token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column)
self.tokens.append(token)
self.character_data = ''
-
+
def next(self):
size = 16*1024
while self.index >= len(self.tokens) and not self.final:
@@ -742,13 +742,13 @@ class XmlParser(Parser):
Parser.__init__(self)
self.tokenizer = XmlTokenizer(fp)
self.consume()
-
+
def consume(self):
self.token = self.tokenizer.next()
def match_element_start(self, name):
return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name
-
+
def match_element_end(self, name):
return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name
@@ -762,7 +762,7 @@ class XmlParser(Parser):
attrs = self.token.attrs
self.consume()
return attrs
-
+
def element_end(self, name):
while self.token.type == XML_CHARACTER_DATA:
self.consume()
@@ -840,20 +840,20 @@ class GprofParser(Parser):
)
_cg_primary_re = re.compile(
- r'^\[(?P<index>\d+)\]?' +
- r'\s+(?P<percentage_time>\d+\.\d+)' +
- r'\s+(?P<self>\d+\.\d+)' +
- r'\s+(?P<descendants>\d+\.\d+)' +
- r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
+ r'^\[(?P<index>\d+)\]?' +
+ r'\s+(?P<percentage_time>\d+\.\d+)' +
+ r'\s+(?P<self>\d+\.\d+)' +
+ r'\s+(?P<descendants>\d+\.\d+)' +
+ r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
r'\s+(?P<name>\S.*?)' +
r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
r'\s\[(\d+)\]$'
)
_cg_parent_re = re.compile(
- r'^\s+(?P<self>\d+\.\d+)?' +
- r'\s+(?P<descendants>\d+\.\d+)?' +
- r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' +
+ r'^\s+(?P<self>\d+\.\d+)?' +
+ r'\s+(?P<descendants>\d+\.\d+)?' +
+ r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' +
r'\s+(?P<name>\S.*?)' +
r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
r'\s\[(?P<index>\d+)\]$'
@@ -862,19 +862,19 @@ class GprofParser(Parser):
_cg_child_re = _cg_parent_re
_cg_cycle_header_re = re.compile(
- r'^\[(?P<index>\d+)\]?' +
- r'\s+(?P<percentage_time>\d+\.\d+)' +
- r'\s+(?P<self>\d+\.\d+)' +
- r'\s+(?P<descendants>\d+\.\d+)' +
- r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
+ r'^\[(?P<index>\d+)\]?' +
+ r'\s+(?P<percentage_time>\d+\.\d+)' +
+ r'\s+(?P<self>\d+\.\d+)' +
+ r'\s+(?P<descendants>\d+\.\d+)' +
+ r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
r'\s+<cycle\s(?P<cycle>\d+)\sas\sa\swhole>' +
r'\s\[(\d+)\]$'
)
_cg_cycle_member_re = re.compile(
- r'^\s+(?P<self>\d+\.\d+)?' +
- r'\s+(?P<descendants>\d+\.\d+)?' +
- r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' +
+ r'^\s+(?P<self>\d+\.\d+)?' +
+ r'\s+(?P<descendants>\d+\.\d+)?' +
+ r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' +
r'\s+(?P<name>\S.*?)' +
r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
r'\s\[(?P<index>\d+)\]$'
@@ -892,7 +892,7 @@ class GprofParser(Parser):
line = lines.pop(0)
if line.startswith('['):
break
-
+
# read function parent line
mo = self._cg_parent_re.match(line)
if not mo:
@@ -913,7 +913,7 @@ class GprofParser(Parser):
while lines:
line = lines.pop(0)
-
+
# read function subroutine line
mo = self._cg_child_re.match(line)
if not mo:
@@ -923,7 +923,7 @@ class GprofParser(Parser):
else:
child = self.translate(mo)
children.append(child)
-
+
function.parents = parents
function.children = children
@@ -948,7 +948,7 @@ class GprofParser(Parser):
continue
call = self.translate(mo)
cycle.functions.append(call)
-
+
self.cycles[cycle.cycle] = cycle
def parse_cg_entry(self, lines):
@@ -975,16 +975,16 @@ class GprofParser(Parser):
self.parse_cg_entry(entry_lines)
entry_lines = []
else:
- entry_lines.append(line)
+ entry_lines.append(line)
line = self.readline()
-
+
def parse(self):
self.parse_cg()
self.fp.close()
profile = Profile()
profile[TIME] = 0.0
-
+
cycles = {}
for index in self.cycles.iterkeys():
cycles[index] = Cycle()
@@ -999,16 +999,16 @@ class GprofParser(Parser):
call = Call(entry.index)
call[CALLS] = entry.called_self
function.called += entry.called_self
-
+
# populate the function calls
for child in entry.children:
call = Call(child.index)
-
+
assert child.called is not None
call[CALLS] = child.called
if child.index not in self.functions:
- # NOTE: functions that were never called but were discovered by gprof's
+ # NOTE: functions that were never called but were discovered by gprof's
# static call graph analysis dont have a call graph entry so we need
# to add them here
missing = Function(child.index, child.name)
@@ -1024,7 +1024,7 @@ class GprofParser(Parser):
try:
cycle = cycles[entry.cycle]
except KeyError:
- sys.stderr.write('warning: <cycle %u as a whole> entry missing\n' % entry.cycle)
+ sys.stderr.write('warning: <cycle %u as a whole> entry missing\n' % entry.cycle)
cycle = Cycle()
cycles[entry.cycle] = cycle
cycle.add_function(function)
@@ -1046,7 +1046,7 @@ class GprofParser(Parser):
class CallgrindParser(LineParser):
"""Parser for valgrind's callgrind tool.
-
+
See also:
- http://valgrind.org/docs/manual/cl-format.html
"""
@@ -1153,7 +1153,7 @@ class CallgrindParser(LineParser):
self.parse_association_spec()
__subpos_re = r'(0x[0-9a-fA-F]+|\d+|\+\d+|-\d+|\*)'
- _cost_re = re.compile(r'^' +
+ _cost_re = re.compile(r'^' +
__subpos_re + r'( +' + __subpos_re + r')*' +
r'( +\d+)*' +
'$')
@@ -1188,12 +1188,12 @@ class CallgrindParser(LineParser):
events = map(float, events)
if calls is None:
- function[SAMPLES] += events[0]
+ function[SAMPLES] += events[0]
self.profile[SAMPLES] += events[0]
else:
callee = self.get_callee()
callee.called += calls
-
+
try:
call = function.calls[callee.id]
except KeyError:
@@ -1255,7 +1255,7 @@ class CallgrindParser(LineParser):
def parse_position_spec(self):
line = self.lookahead()
-
+
if line.startswith('jump=') or line.startswith('jcnd='):
self.consume()
return True
@@ -1338,20 +1338,20 @@ class CallgrindParser(LineParser):
def get_function(self):
module = self.positions.get('ob', '')
- filename = self.positions.get('fl', '')
- function = self.positions.get('fn', '')
+ filename = self.positions.get('fl', '')
+ function = self.positions.get('fn', '')
return self.make_function(module, filename, function)
def get_callee(self):
module = self.positions.get('cob', '')
- filename = self.positions.get('cfi', '')
- function = self.positions.get('cfn', '')
+ filename = self.positions.get('cfi', '')
+ function = self.positions.get('cfn', '')
return self.make_function(module, filename, function)
class OprofileParser(LineParser):
"""Parser for oprofile callgraph output.
-
+
See also:
- http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph
"""
@@ -1380,7 +1380,7 @@ class OprofileParser(LineParser):
self.update_subentries_dict(callers_total, callers)
function_total.samples += function.samples
self.update_subentries_dict(callees_total, callees)
-
+
def update_subentries_dict(self, totals, partials):
for partial in partials.itervalues():
try:
@@ -1389,7 +1389,7 @@ class OprofileParser(LineParser):
totals[partial.id] = partial
else:
total.samples += partial.samples
-
+
def parse(self):
# read lookahead
self.readline()
@@ -1401,7 +1401,7 @@ class OprofileParser(LineParser):
profile = Profile()
reverse_call_samples = {}
-
+
# populate the profile
profile[SAMPLES] = 0
for _callers, _function, _callees in self.entries.itervalues():
@@ -1424,7 +1424,7 @@ class OprofileParser(LineParser):
call = Call(_callee.id)
call[SAMPLES2] = _callee.samples
function.add_call(call)
-
+
# compute derived data
profile.validate()
profile.find_cycles()
@@ -1510,7 +1510,7 @@ class OprofileParser(LineParser):
def match_primary(self):
line = self.lookahead()
return not line[:1].isspace()
-
+
def match_secondary(self):
line = self.lookahead()
return line[:1].isspace()
@@ -1518,7 +1518,7 @@ class OprofileParser(LineParser):
class HProfParser(LineParser):
"""Parser for java hprof output
-
+
See also:
- http://java.sun.com/developer/technicalArticles/Programming/HPROF.html
"""
@@ -1679,7 +1679,7 @@ class SysprofParser(XmlParser):
def build_profile(self, objects, nodes):
profile = Profile()
-
+
profile[SAMPLES] = 0
for id, object in objects.iteritems():
# Ignore fake objects (process names, modules, "Everything", "kernel", etc.)
@@ -1753,7 +1753,7 @@ class SharkParser(LineParser):
else:
function_total, callees_total = entry
function_total.samples += function.samples
-
+
def add_callee(self, function, callee):
func, callees = self.entries[function.id]
try:
@@ -1762,7 +1762,7 @@ class SharkParser(LineParser):
callees[callee.id] = callee
else:
entry.samples += callee.samples
-
+
def parse(self):
self.readline()
self.readline()
@@ -1800,9 +1800,9 @@ class SharkParser(LineParser):
# if the callstack has had an entry, it's this functions caller
if prefix > 0:
self.add_callee(self.stack[prefix - 1], entry)
-
+
self.add_entry(entry)
-
+
profile = Profile()
profile[SAMPLES] = 0
for _function, _callees in self.entries.itervalues():
@@ -1818,7 +1818,7 @@ class SharkParser(LineParser):
call = Call(_callee.id)
call[SAMPLES] = _callee.samples
function.add_call(call)
-
+
# compute derived data
profile.validate()
profile.find_cycles()
@@ -1843,7 +1843,7 @@ class XPerfParser(Parser):
def parse(self):
import csv
reader = csv.reader(
- self.stream,
+ self.stream,
delimiter = ',',
quotechar = None,
escapechar = None,
@@ -1856,7 +1856,7 @@ class XPerfParser(Parser):
self.parse_header(row)
for row in it:
self.parse_row(row)
-
+
# compute derived data
self.profile.validate()
self.profile.find_cycles()
@@ -1884,7 +1884,7 @@ class XPerfParser(Parser):
else:
break
fields[name] = value
-
+
process = fields['Process Name']
symbol = fields['Module'] + '!' + fields['Function']
weight = fields['Weight']
@@ -1950,12 +1950,12 @@ class SleepyParser(Parser):
self.calls = {}
self.profile = Profile()
-
+
_symbol_re = re.compile(
- r'^(?P<id>\w+)' +
- r'\s+"(?P<module>[^"]*)"' +
- r'\s+"(?P<procname>[^"]*)"' +
- r'\s+"(?P<sourcefile>[^"]*)"' +
+ r'^(?P<id>\w+)' +
+ r'\s+"(?P<module>[^"]*)"' +
+ r'\s+"(?P<procname>[^"]*)"' +
+ r'\s+"(?P<sourcefile>[^"]*)"' +
r'\s+(?P<sourceline>\d+)$'
)
@@ -1965,7 +1965,7 @@ class SleepyParser(Parser):
mo = self._symbol_re.match(line)
if mo:
symbol_id, module, procname, sourcefile, sourceline = mo.groups()
-
+
function_id = ':'.join([module, procname])
try:
@@ -1991,7 +1991,7 @@ class SleepyParser(Parser):
callee[SAMPLES] += samples
self.profile[SAMPLES] += samples
-
+
for caller in callstack[1:]:
try:
call = caller.calls[callee.id]
@@ -2059,7 +2059,7 @@ class AQtimeParser(XmlParser):
self.parse_headers()
results = self.parse_results()
self.element_end('AQtime_Results')
- return self.build_profile(results)
+ return self.build_profile(results)
def parse_headers(self):
self.element_start('HEADERS')
@@ -2163,7 +2163,7 @@ class AQtimeParser(XmlParser):
profile[TOTAL_TIME] = profile[TIME]
profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
return profile
-
+
def build_function(self, fields):
function = Function(self.build_id(fields), self.build_name(fields))
function[TIME] = fields['Time']
@@ -2263,7 +2263,7 @@ class PstatsParser:
class Theme:
- def __init__(self,
+ def __init__(self,
bgcolor = (0.0, 0.0, 1.0),
mincolor = (0.0, 0.0, 0.0),
maxcolor = (0.0, 0.0, 1.0),
@@ -2320,10 +2320,10 @@ class Theme:
def color(self, weight):
weight = min(max(weight, 0.0), 1.0)
-
+
hmin, smin, lmin = self.mincolor
hmax, smax, lmax = self.maxcolor
-
+
if self.skew < 0:
raise ValueError("Skew must be greater than 0")
elif self.skew == 1.0:
@@ -2446,10 +2446,10 @@ class DotWriter:
weight = 0.0
label = '\n'.join(labels)
- self.node(function.id,
- label = label,
- color = self.color(theme.node_bgcolor(weight)),
- fontcolor = self.color(theme.node_fgcolor(weight)),
+ self.node(function.id,
+ label = label,
+ color = self.color(theme.node_bgcolor(weight)),
+ fontcolor = self.color(theme.node_fgcolor(weight)),
fontsize = "%.2f" % theme.node_fontsize(weight),
)
@@ -2471,13 +2471,13 @@ class DotWriter:
label = '\n'.join(labels)
- self.edge(function.id, call.callee_id,
- label = label,
- color = self.color(theme.edge_color(weight)),
+ self.edge(function.id, call.callee_id,
+ label = label,
+ color = self.color(theme.edge_color(weight)),
fontcolor = self.color(theme.edge_color(weight)),
- fontsize = "%.2f" % theme.edge_fontsize(weight),
- penwidth = "%.2f" % theme.edge_penwidth(weight),
- labeldistance = "%.2f" % theme.edge_penwidth(weight),
+ fontsize = "%.2f" % theme.edge_fontsize(weight),
+ penwidth = "%.2f" % theme.edge_penwidth(weight),
+ labeldistance = "%.2f" % theme.edge_penwidth(weight),
arrowsize = "%.2f" % theme.edge_arrowsize(weight),
)
@@ -2621,7 +2621,7 @@ class Main:
self.theme = self.themes[self.options.theme]
except KeyError:
parser.error('invalid colormap \'%s\'' % self.options.theme)
-
+
# set skew on the theme now that it has been picked.
if self.options.theme_skew:
self.theme.skew = self.options.theme_skew
@@ -2655,7 +2655,7 @@ class Main:
fp = sys.stdin
else:
fp = open(self.args[0], 'rt')
- parser = HProfParser(fp)
+ parser = HProfParser(fp)
elif self.options.format == 'pstats':
if not self.args:
parser.error('at least a file must be specified for pstats input')
@@ -2686,7 +2686,7 @@ class Main:
parser.error('invalid format \'%s\'' % self.options.format)
self.profile = parser.parse()
-
+
if self.options.output is None:
self.output = sys.stdout
else:
@@ -2760,4 +2760,4 @@ class Main:
if __name__ == '__main__':
- Main().main() \ No newline at end of file
+ Main().main()