5
5
from django .utils .safestring import mark_safe
6
6
from debug_toolbar .panels import DebugPanel
7
7
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
+
15
+ from cStringIO import StringIO
8
16
import cProfile
9
17
from pstats import Stats
10
18
from colorsys import hsv_to_rgb
@@ -20,13 +28,6 @@ def get_root_func(self):
20
28
break
21
29
return self .__root
22
30
23
- def print_call_tree_node (self , function , depth , max_depth , cum_filter = 0.1 ):
24
- self .print_line (function , depth = depth )
25
- if depth < max_depth :
26
- for called in self .all_callees [function ].keys ():
27
- if self .stats [called ][3 ] >= cum_filter :
28
- self .print_call_tree_node (called , depth + 1 , max_depth , cum_filter = cum_filter )
29
-
30
31
class FunctionCall (object ):
31
32
def __init__ (self , statobj , func , depth = 0 , stats = None , id = 0 , parent_ids = [], hsv = (0 ,0.5 ,1 )):
32
33
self .statobj = statobj
@@ -39,6 +40,7 @@ def __init__(self, statobj, func, depth=0, stats=None, id=0, parent_ids=[], hsv=
39
40
self .id = id
40
41
self .parent_ids = parent_ids
41
42
self .hsv = hsv
43
+ self ._line_stats_text = None
42
44
43
45
def parent_classes (self ):
44
46
return self .parent_classes
@@ -118,6 +120,18 @@ def cumtime_per_call(self):
118
120
119
121
def indent (self ):
120
122
return 16 * self .depth
123
+
124
+ def line_stats_text (self ):
125
+ if self ._line_stats_text is None :
126
+ lstats = self .statobj .line_stats
127
+ if self .func in lstats .timings :
128
+ out = StringIO ()
129
+ fn , lineno , name = self .func
130
+ show_func (fn , lineno , name , lstats .timings [self .func ], lstats .unit , stream = out )
131
+ self ._line_stats_text = out .getvalue ()
132
+ else :
133
+ self ._line_stats_text = False
134
+ return self ._line_stats_text
121
135
122
136
class ProfilingDebugPanel (DebugPanel ):
123
137
"""
@@ -135,23 +149,43 @@ def url(http://github.com/self):
135
149
def title (self ):
136
150
return _ ('Profiling' )
137
151
152
+ def _unwrap_closure_and_profile (self , func ):
153
+ if not hasattr (func , 'func_code' ):
154
+ return
155
+ self .line_profiler .add_function (func )
156
+ if func .func_closure :
157
+ for cell in func .func_closure :
158
+ if hasattr (cell .cell_contents , 'func_code' ):
159
+ self ._unwrap_closure_and_profile (cell .cell_contents )
160
+
138
161
def process_view (self , request , view_func , view_args , view_kwargs ):
139
162
__traceback_hide__ = True
140
163
self .profiler = cProfile .Profile ()
141
164
args = (request ,) + view_args
142
- return self .profiler .runcall (view_func , * args , ** view_kwargs )
165
+ if DJ_PROFILE_USE_LINE_PROFILER :
166
+ self .line_profiler = LineProfiler ()
167
+ self ._unwrap_closure_and_profile (view_func )
168
+ self .line_profiler .enable_by_count ()
169
+ out = self .profiler .runcall (view_func , * args , ** view_kwargs )
170
+ self .line_profiler .disable_by_count ()
171
+ else :
172
+ self .line_profiler = None
173
+ out = self .profiler .runcall (view_func , * args , ** view_kwargs )
174
+ return out
143
175
144
176
def process_response (self , request , response ):
145
177
self .profiler .create_stats ()
146
178
self .stats = DjangoDebugToolbarStats (self .profiler )
179
+ if DJ_PROFILE_USE_LINE_PROFILER :
180
+ self .stats .line_stats = self .line_profiler .get_stats ()
147
181
return response
148
182
149
183
def add_node (self , func_list , func , max_depth , cum_time = 0.1 ):
150
184
func_list .append (func )
151
185
func .has_subfuncs = False
152
186
if func .depth < max_depth :
153
187
for subfunc in func .subfuncs ():
154
- if subfunc .stats [3 ] >= cum_time :
188
+ if subfunc .stats [3 ] >= cum_time or ( subfunc . func in self . stats . line_stats . timings ) :
155
189
func .has_subfuncs = True
156
190
self .add_node (func_list , subfunc , max_depth , cum_time = cum_time )
157
191
0 commit comments