diff --git a/cprofilev.py b/cprofilev.py index 5e38dc3..7abf355 100755 --- a/cprofilev.py +++ b/cprofilev.py @@ -34,17 +34,33 @@ {{ title }} | cProfile Results + + -
{{ !stats }}
- - % if callers: -

Called By:

-
{{ !callers }}
- - % if callees: -

Called:

-
{{ !callees }}
+
+
{{ !stats_header }}
+ {{ !stats_table }}
+ + + + + + + % if callers: +

Called By:

+
{{ !callers }}
+ + % if callees: +

Called:

+
{{ !callees }}
+
""" @@ -57,14 +73,6 @@ class Stats(object): """Wrapper around pstats.Stats class.""" IGNORE_FUNC_NAMES = ['function', ''] - DEFAULT_SORT_ARG = 'cumulative' - SORT_ARGS = { - 'ncalls': 'calls', - 'tottime': 'time', - 'cumtime': 'cumulative', - 'filename': 'module', - 'lineno': 'nfl', - } STATS_LINE_REGEX = r'(.*)\((.*)\)$' HEADER_LINE_REGEX = r'ncalls|tottime|cumtime' @@ -87,15 +95,6 @@ def read(self): @classmethod def process_line(cls, line): - # Format header lines (such that clicking on a column header sorts by - # that column). - if re.search(cls.HEADER_LINE_REGEX, line): - for key, val in cls.SORT_ARGS.items(): - url_link = bottle.template( - "{{ key }}", - url=cls.get_updated_href(SORT_KEY, val), - key=key) - line = line.replace(key, url_link) # Format stat lines (such that clicking on the function name drills into # the function call). match = re.search(cls.STATS_LINE_REGEX, line) @@ -133,11 +132,41 @@ def show_callees(self, func_name): self.stats.print_callees(func_name) return self - def sort(self, sort=''): - sort = sort or self.DEFAULT_SORT_ARG - self.stats.sort_stats(sort) - return self - + def get_stats_header(self, stats_str): + header = '' + for line in stats_str.splitlines(): + if re.search(self.HEADER_LINE_REGEX, line): + break + header += line + '\n' + return header + + def iter_stats_table_row(self, stats_str): + # rows in the stats table are formatted in a really simple way. the + # first 5 columns are separated by whitespace. the fifth column extends + # to the end of the line + in_table = False + for line in stats_str.splitlines(): + if re.search(self.HEADER_LINE_REGEX, line): + in_table = True + cols = line.split() + if in_table and cols: + row = cols[:5] + [' '.join(cols[5:])] + yield row + + def format_stats_table(self, stats_str): + table_dom = [] + col_el = 'th' + for row in self.iter_stats_table_row(stats_str): + row_dom = '' + for col in row: + row_dom += '<%s>%s' % (col_el, col, col_el) + row += '' + col_el = 'td' + table_dom.append(row_dom) + table_dom[0] = '' + table_dom[0] + '' + table_dom[1] = '' + table_dom[1] + table_dom[-1] = table_dom[-1] + '' + return '\n'.join(table_dom) class CProfileV(object): def __init__(self, profile, title, address='127.0.0.1', port=4000): @@ -154,14 +183,14 @@ def route_handler(self): self.stats = Stats(self.profile) func_name = bottle.request.query.get(FUNC_NAME_KEY) or '' - sort = bottle.request.query.get(SORT_KEY) or '' - self.stats.sort(sort) callers = self.stats.show_callers(func_name).read() if func_name else '' callees = self.stats.show_callees(func_name).read() if func_name else '' + stats_str = self.stats.show(func_name).read() data = { 'title': self.title, - 'stats': self.stats.sort(sort).show(func_name).read(), + 'stats_header': self.stats.get_stats_header(stats_str), + 'stats_table': self.stats.format_stats_table(stats_str), 'callers': callers, 'callees': callees, } @@ -210,7 +239,7 @@ def main(): if len(args.remainder) < 0: parser.print_help() sys.exit(2) - + # Note: The info message is sent to stderr to keep stdout clean in case # the profiled script writes some output to stdout sys.stderr.write(info + "\n")