diff options
Diffstat (limited to 'debug_toolbar/panels/profiling.py')
| -rw-r--r-- | debug_toolbar/panels/profiling.py | 109 |
1 files changed, 71 insertions, 38 deletions
diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 95a1a4d..8913621 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -1,13 +1,22 @@ from __future__ import division -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe from debug_toolbar.panels import DebugPanel +try: + from line_profiler import LineProfiler, show_func + DJ_PROFILE_USE_LINE_PROFILER = True +except ImportError: + DJ_PROFILE_USE_LINE_PROFILER = False + + +from cStringIO import StringIO import cProfile from pstats import Stats from colorsys import hsv_to_rgb +import os + class DjangoDebugToolbarStats(Stats): __root = None @@ -19,16 +28,11 @@ class DjangoDebugToolbarStats(Stats): self.__root = func break return self.__root - - def print_call_tree_node(self, function, depth, max_depth, cum_filter=0.1): - self.print_line(function, depth=depth) - if depth < max_depth: - for called in self.all_callees[function].keys(): - if self.stats[called][3] >= cum_filter: - self.print_call_tree_node(called, depth+1, max_depth, cum_filter=cum_filter) + class FunctionCall(object): - def __init__(self, statobj, func, depth=0, stats=None, id=0, parent_ids=[], hsv=(0,0.5,1)): + def __init__(self, statobj, func, depth=0, stats=None, + id=0, parent_ids=[], hsv=(0,0.5,1)): self.statobj = statobj self.func = func if stats: @@ -39,6 +43,7 @@ class FunctionCall(object): self.id = id self.parent_ids = parent_ids self.hsv = hsv + self._line_stats_text = None def parent_classes(self): return self.parent_classes @@ -60,9 +65,9 @@ class FunctionCall(object): file_name, line_num, method = self.func idx = file_name.find('/site-packages/') if idx > -1: - file_name=file_name[idx+14:] + file_name = file_name[idx+14:] - file_path, file_name = file_name.rsplit('/', 1) + file_path, file_name = file_name.rsplit(os.sep, 1) return mark_safe('<span class="path">{0}/</span><span class="file">{1}</span> in <span class="func">{3}</span>(<span class="lineno">{2}</span>)'.format( file_path, @@ -73,7 +78,7 @@ class FunctionCall(object): def subfuncs(self): i=0 - h,s,v = self.hsv + h, s, v = self.hsv count = len(self.statobj.all_callees[self.func]) for func, stats in self.statobj.all_callees[self.func].iteritems(): i += 1 @@ -83,8 +88,8 @@ class FunctionCall(object): else: s1 = s*(stats[3]/self.stats[3]) yield FunctionCall(self.statobj, - func, - self.depth+1, + func, + self.depth+1, stats=stats, id=str(self.id) + '_' + str(i), parent_ids=self.parent_ids + [self.id], @@ -102,69 +107,97 @@ class FunctionCall(object): def tottime_per_call(self): cc, nc, tt, ct = self.stats - + if nc == 0: return 0 - + return tt/nc def cumtime_per_call(self): cc, nc, tt, ct = self.stats - + if cc == 0: return 0 - + return ct/cc - + def indent(self): return 16 * self.depth + + def line_stats_text(self): + if self._line_stats_text is None and DJ_PROFILE_USE_LINE_PROFILER: + lstats = self.statobj.line_stats + if self.func in lstats.timings: + out = StringIO() + fn, lineno, name = self.func + show_func(fn, lineno, name, lstats.timings[self.func], lstats.unit, stream=out) + self._line_stats_text = out.getvalue() + else: + self._line_stats_text = False + return self._line_stats_text class ProfilingDebugPanel(DebugPanel): """ Panel that displays the Django version. """ name = 'Profiling' + template = 'debug_toolbar/panels/profiling.html' has_content = True - + def nav_title(self): return _('Profiling') - + def url(self): return '' def title(self): return _('Profiling') - + + def _unwrap_closure_and_profile(self, func): + if not hasattr(func, 'func_code'): + return + self.line_profiler.add_function(func) + if func.func_closure: + for cell in func.func_closure: + if hasattr(cell.cell_contents, 'func_code'): + self._unwrap_closure_and_profile(cell.cell_contents) + def process_view(self, request, view_func, view_args, view_kwargs): __traceback_hide__ = True self.profiler = cProfile.Profile() args = (request,) + view_args - return self.profiler.runcall(view_func, *args, **view_kwargs) - - def process_response(self, request, response): - self.profiler.create_stats() - self.stats = DjangoDebugToolbarStats(self.profiler) - return response - + if DJ_PROFILE_USE_LINE_PROFILER: + self.line_profiler = LineProfiler() + self._unwrap_closure_and_profile(view_func) + self.line_profiler.enable_by_count() + out = self.profiler.runcall(view_func, *args, **view_kwargs) + self.line_profiler.disable_by_count() + else: + self.line_profiler = None + out = self.profiler.runcall(view_func, *args, **view_kwargs) + return out + def add_node(self, func_list, func, max_depth, cum_time=0.1): func_list.append(func) func.has_subfuncs = False if func.depth < max_depth: for subfunc in func.subfuncs(): - if subfunc.stats[3] >= cum_time: + if (subfunc.stats[3] >= cum_time or + (hasattr(self.stats, 'line_stats') and + (subfunc.func in self.stats.line_stats.timings))): func.has_subfuncs = True self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) - def content(self): - + def process_response(self, request, response): + self.profiler.create_stats() + self.stats = DjangoDebugToolbarStats(self.profiler) + if DJ_PROFILE_USE_LINE_PROFILER: + self.stats.line_stats = self.line_profiler.get_stats() self.stats.calc_callees() + root = FunctionCall(self.stats, self.stats.get_root_func(), depth=0) func_list = [] self.add_node(func_list, root, 10, root.stats[3]/8) - context = self.context.copy() - context.update({ - 'func_list': func_list, - }) - - return render_to_string('debug_toolbar/panels/profiling.html', context) + + self.stats_record({'func_list': func_list}) |
