diff --git a/iommi/profiling.py b/iommi/profiling.py index 536b826e..e601ca7d 100644 --- a/iommi/profiling.py +++ b/iommi/profiling.py @@ -2,6 +2,7 @@ import cProfile import os +import pstats import subprocess import sys from io import StringIO @@ -10,8 +11,12 @@ from django.http import StreamingHttpResponse -from iommi._web_compat import HttpResponse -from ._web_compat import settings +from iommi._web_compat import ( + HttpResponse, + settings, +) +from iommi.debug import src_debug_url_builder + MEDIA_PREFIXES = ['/static/'] @@ -35,6 +40,99 @@ def should_profile(request): return ('_iommi_prof' in request.GET or '_iommi_prof' in request.POST) and ((not disabled and is_staff) or settings.DEBUG) +def strip_extra_path(s, token): + if token not in s: + return s + pre, _, post = s.rpartition(' ') + post = post[post.rindex(token) + len(token) :] + return f'{pre} {post}' + + +class HTMLStats(pstats.Stats): + def print_title(self): + print(''' + + + ncalls + tottime + percall + cumtime + percall + function + filename + lineno + + + ''', file=self.stream) + + def print_stats(self, *amount): + for filename in self.files: + print(filename, file=self.stream) + if self.files: + print(file=self.stream) + indent = ' ' * 8 + for func in self.top_level: + print(indent, func[2], file=self.stream) + + print(indent, self.total_calls, "function calls", end=' ', file=self.stream) + if self.total_calls != self.prim_calls: + print("(%d primitive calls)" % self.prim_calls, end=' ', file=self.stream) + print("in %.3f seconds" % self.total_tt, file=self.stream) + print(file=self.stream) + + # this call prints... + width, list = self.get_print_list(amount) + + print('', file=self.stream) + if list: + self.print_title() + limit = 280 + for func in list[:limit]: + self.print_line(func) + print(file=self.stream) + print(file=self.stream) + + print('
', file=self.stream) + return self + + def print_line(self, func): + path, line_number, function_name = func + + base_dir = str(settings.BASE_DIR) + should_bold = base_dir in path and '/site-packages/' not in path + nice_path = path.replace(base_dir, '') + nice_path = strip_extra_path(nice_path, '/site-packages') + nice_path = strip_extra_path(nice_path, '/Python.framework/Versions') + + if should_bold: + print(f'', file=self.stream) + else: + print(f'', file=self.stream) + + def f8(x): + return "%8.3f" % x + + cc, nc, tt, ct, callers = self.stats[func] + c = str(nc) + if nc != cc: + c = c + '/' + str(cc) + print(f'{c}', file=self.stream) + print(f'{f8(tt)}', file=self.stream) + if nc == 0: + print('', file=self.stream) + else: + print(f'{f8(tt/nc)}', file=self.stream) + print(f'{f8(ct)}', file=self.stream) + if cc == 0: + print('', file=self.stream) + else: + print(f'{f8(ct/cc)}', file=self.stream) + print(f'{function_name}', file=self.stream) + print(f'{nice_path}', file=self.stream) + print(f'{line_number}', file=self.stream) + print(f'', file=self.stream) + + class Middleware: def __init__(self, get_response): self.get_response = get_response @@ -70,7 +168,7 @@ def __call__(self, request): import pstats s = StringIO() - ps = pstats.Stats(*request._iommi_prof, stream=s) + ps = HTMLStats(*request._iommi_prof, stream=s) prof_command = request.GET.get('_iommi_prof') @@ -130,36 +228,37 @@ def __call__(self, request): ps = ps.sort_stats(prof_command or 'cumulative') ps.print_stats() - stats_str = s.getvalue() - - limit = 280 - result = [] - - def strip_extra_path(s, token): - if token not in s: - return s - pre, _, post = s.rpartition(' ') - post = post[post.rindex(token) + len(token) :] - return f'{pre} {post}' - - base_dir = str(settings.BASE_DIR) - for line in stats_str.split("\n")[:limit]: - should_bold = base_dir in line and '/site-packages/' not in line - line = line.replace(base_dir, '') - line = strip_extra_path(line, '/site-packages') - line = strip_extra_path(line, '/Python.framework/Versions') - if should_bold: - line = f'{line}' - - line = line.replace(' ', ' ') - result.append(line) + result = s.getvalue() # language=html start_html = ''' @@ -177,13 +287,11 @@ def strip_extra_path(s, token): graph snakeviz - -
+ +

''' - lines_html = "
\n".join(result) - end_html = '
' - response.content = start_html + lines_html + end_html + response.content = start_html.strip() + result response['Content-Type'] = 'text/html'