From 6066be569f700209979d480a58ead39b3bb7e1f8 Mon Sep 17 00:00:00 2001 From: Dean Malmgren Date: Mon, 14 Dec 2015 08:37:52 -0600 Subject: [PATCH 1/2] make cprofile results sortable using datatables jquery plugin and bootstrap theme --- cprofilev.py | 83 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/cprofilev.py b/cprofilev.py index 5e38dc3..590d540 100755 --- a/cprofilev.py +++ b/cprofilev.py @@ -34,17 +34,31 @@ {{ 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 }}
+
""" @@ -87,15 +101,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) @@ -138,6 +143,41 @@ def sort(self, sort=''): 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): @@ -159,9 +199,12 @@ def route_handler(self): 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 = self.stats.sort(sort).show(func_name).read() data = { 'title': self.title, - 'stats': self.stats.sort(sort).show(func_name).read(), + 'stats': stats, + 'stats_header': self.stats.get_stats_header(stats), + 'stats_table': self.stats.format_stats_table(stats), 'callers': callers, 'callees': callees, } @@ -210,7 +253,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") From b5771eafcbd4d507b1f5a3673ab103ec2f292b91 Mon Sep 17 00:00:00 2001 From: Dean Malmgren Date: Mon, 14 Dec 2015 08:46:05 -0600 Subject: [PATCH 2/2] sorting now handled by datatables; removing depricated functionality --- cprofilev.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/cprofilev.py b/cprofilev.py index 590d540..7abf355 100755 --- a/cprofilev.py +++ b/cprofilev.py @@ -47,7 +47,9 @@ @@ -71,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' @@ -138,11 +132,6 @@ 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(): @@ -194,17 +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 = self.stats.sort(sort).show(func_name).read() + stats_str = self.stats.show(func_name).read() data = { 'title': self.title, - 'stats': stats, - 'stats_header': self.stats.get_stats_header(stats), - 'stats_table': self.stats.format_stats_table(stats), + 'stats_header': self.stats.get_stats_header(stats_str), + 'stats_table': self.stats.format_stats_table(stats_str), 'callers': callers, 'callees': callees, }