Skip to content

Commit a7dbc61

Browse files
committed
Move support for line_profiler to a 3rd party panel.
Many thanks to Dave McLain. Fix #477.
1 parent 2135eac commit a7dbc61

File tree

6 files changed

+18
-86
lines changed

6 files changed

+18
-86
lines changed

debug_toolbar/panels/profiling.py

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,8 @@
22

33
from django.utils.translation import ugettext_lazy as _
44
from django.utils.safestring import mark_safe
5-
from django.utils.six.moves import cStringIO
65
from debug_toolbar.panels import Panel
76

8-
try:
9-
from line_profiler import LineProfiler, show_func
10-
DJ_PROFILE_USE_LINE_PROFILER = True
11-
except ImportError:
12-
DJ_PROFILE_USE_LINE_PROFILER = False
13-
14-
157
import cProfile
168
from pstats import Stats
179
from colorsys import hsv_to_rgb
@@ -43,7 +35,6 @@ def __init__(self, statobj, func, depth=0, stats=None,
4335
self.id = id
4436
self.parent_ids = parent_ids
4537
self.hsv = hsv
46-
self._line_stats_text = None
4738

4839
def parent_classes(self):
4940
return self.parent_classes
@@ -127,18 +118,6 @@ def cumtime_per_call(self):
127118
def indent(self):
128119
return 16 * self.depth
129120

130-
def line_stats_text(self):
131-
if self._line_stats_text is None and DJ_PROFILE_USE_LINE_PROFILER:
132-
lstats = self.statobj.line_stats
133-
if self.func in lstats.timings:
134-
out = cStringIO()
135-
fn, lineno, name = self.func
136-
show_func(fn, lineno, name, lstats.timings[self.func], lstats.unit, stream=out)
137-
self._line_stats_text = out.getvalue()
138-
else:
139-
self._line_stats_text = False
140-
return self._line_stats_text
141-
142121

143122
class ProfilingPanel(Panel):
144123
"""
@@ -148,37 +127,17 @@ class ProfilingPanel(Panel):
148127

149128
template = 'debug_toolbar/panels/profiling.html'
150129

151-
def _unwrap_closure_and_profile(self, func):
152-
if not hasattr(func, '__code__'):
153-
return
154-
self.line_profiler.add_function(func)
155-
if func.__closure__:
156-
for cell in func.__closure__:
157-
if hasattr(cell.cell_contents, '__code__'):
158-
self._unwrap_closure_and_profile(cell.cell_contents)
159-
160130
def process_view(self, request, view_func, view_args, view_kwargs):
161131
self.profiler = cProfile.Profile()
162132
args = (request,) + view_args
163-
if DJ_PROFILE_USE_LINE_PROFILER:
164-
self.line_profiler = LineProfiler()
165-
self._unwrap_closure_and_profile(view_func)
166-
self.line_profiler.enable_by_count()
167-
out = self.profiler.runcall(view_func, *args, **view_kwargs)
168-
self.line_profiler.disable_by_count()
169-
else:
170-
self.line_profiler = None
171-
out = self.profiler.runcall(view_func, *args, **view_kwargs)
172-
return out
133+
return self.profiler.runcall(view_func, *args, **view_kwargs)
173134

174135
def add_node(self, func_list, func, max_depth, cum_time=0.1):
175136
func_list.append(func)
176137
func.has_subfuncs = False
177138
if func.depth < max_depth:
178139
for subfunc in func.subfuncs():
179-
if (subfunc.stats[3] >= cum_time or
180-
(hasattr(self.stats, 'line_stats') and
181-
(subfunc.func in self.stats.line_stats.timings))):
140+
if subfunc.stats[3] >= cum_time:
182141
func.has_subfuncs = True
183142
self.add_node(func_list, subfunc, max_depth, cum_time=cum_time)
184143

@@ -188,8 +147,6 @@ def process_response(self, request, response):
188147
# Could be delayed until the panel content is requested (perf. optim.)
189148
self.profiler.create_stats()
190149
self.stats = DjangoDebugToolbarStats(self.profiler)
191-
if DJ_PROFILE_USE_LINE_PROFILER:
192-
self.stats.line_stats = self.line_profiler.get_stats()
193150
self.stats.calc_callees()
194151

195152
root = FunctionCall(self.stats, self.stats.get_root_func(), depth=0)

debug_toolbar/templates/debug_toolbar/panels/profiling.html

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@
3030
<td>{{ call.tottime_per_call|floatformat:3 }}</td>
3131
<td>{{ call.count }}</td>
3232
</tr>
33-
{% if call.line_stats_text %}
34-
<tr class="djToggleDetails_{{ call.id }}{% for parent_id in call.parent_ids %} djToggleDetails_{{ parent_id }}{% endfor %}">
35-
<td colspan="6">
36-
<div style="padding-left: {{ call.indent }}px;"><pre>{{ call.line_stats_text }}</pre></div>
37-
</td>
38-
</tr>
39-
{% endif %}
4033
{% endfor %}
4134
</tbody>
4235
</table>

docs/panels.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ Retrieves and displays information you specify using the ``debug`` statement.
155155
Inspector panel also logs to the console by default, but may be instructed not
156156
to.
157157

158+
Line Profiler
159+
~~~~~~~~~~~~~
160+
161+
URL: https://github.com/dmclain/django-debug-toolbar-line-profiler
162+
163+
Path: ``debug_toolbar_line_profiler.panel.ProfilingPanel``
164+
165+
This package provides a profiling panel that incorporates output from
166+
line_profiler_.
167+
168+
.. _line_profiler: http://pythonhosted.org/line_profiler/
169+
158170
Memcache
159171
~~~~~~~~
160172

requirements_dev.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
Django
88
sqlparse
99

10-
# Optional runtime dependencies
11-
12-
line_profiler
13-
1410
# Testing
1511

1612
coverage

tests/panels/test_profiling.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@
66
from django.test.utils import override_settings
77
from django.utils import unittest
88

9-
try:
10-
import line_profiler
11-
except ImportError:
12-
line_profiler = None
13-
14-
from debug_toolbar.panels import profiling
15-
169
from ..base import BaseTestCase
1710
from ..views import regular_view
1811

@@ -24,28 +17,15 @@ def setUp(self):
2417
super(ProfilingPanelTestCase, self).setUp()
2518
self.panel = self.toolbar.get_panel_by_id('ProfilingPanel')
2619

27-
def _test_render_with_or_without_line_profiler(self):
20+
# This test fails randomly for a reason I don't understand.
21+
22+
@unittest.expectedFailure
23+
def test_regular_view(self):
2824
self.panel.process_view(self.request, regular_view, ('profiling',), {})
2925
self.panel.process_response(self.request, self.response)
3026
self.assertIn('func_list', self.panel.get_stats())
3127
self.assertIn('regular_view', self.panel.content)
3228

33-
# These two tests fail randomly for a reason I don't understand.
34-
35-
@unittest.expectedFailure
36-
@unittest.skipIf(line_profiler is None, "line_profiler isn't available")
37-
def test_render_with_line_profiler(self):
38-
self._test_render_with_or_without_line_profiler()
39-
40-
@unittest.expectedFailure
41-
def test_without_line_profiler(self):
42-
_use_line_profiler = profiling.DJ_PROFILE_USE_LINE_PROFILER
43-
profiling.DJ_PROFILE_USE_LINE_PROFILER = False
44-
try:
45-
self._test_render_with_or_without_line_profiler()
46-
finally:
47-
profiling.DJ_PROFILE_USE_LINE_PROFILER = _use_line_profiler
48-
4929

5030
@override_settings(DEBUG=True,
5131
DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingPanel'])

tox.ini

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,24 @@ whitelist_externals = make
2525
basepython = python2.6
2626
deps =
2727
Django>=1.4,<1.5
28-
line_profiler
2928
{[testenv]deps}
3029

3130
[testenv:py27-django14]
3231
basepython = python2.7
3332
deps =
3433
Django>=1.4,<1.5
35-
line_profiler
3634
{[testenv]deps}
3735

3836
[testenv:py26-django15]
3937
basepython = python2.6
4038
deps =
4139
Django>=1.5,<1.6
42-
line_profiler
4340
{[testenv]deps}
4441

4542
[testenv:py27-django15]
4643
basepython = python2.7
4744
deps =
4845
Django>=1.5,<1.6
49-
line_profiler
5046
{[testenv]deps}
5147

5248
[testenv:py32-django15]
@@ -65,14 +61,12 @@ deps =
6561
basepython = python2.6
6662
deps =
6763
Django>=1.6,<1.7
68-
line_profiler
6964
{[testenv]deps}
7065

7166
[testenv:py27-django16]
7267
basepython = python2.7
7368
deps =
7469
Django>=1.6,<1.7
75-
line_profiler
7670
{[testenv]deps}
7771

7872
[testenv:py32-django16]

0 commit comments

Comments
 (0)