3
3
from time import time
4
4
5
5
import wrapt
6
- from django .dispatch import Signal
7
- from django .utils .translation import ugettext_lazy as _
8
-
9
6
from debug_toolbar .panels import Panel
10
7
from debug_toolbar .panels .sql .utils import contrasting_color_generator
8
+ from django .dispatch import Signal
9
+ from django .utils .translation import ugettext_lazy as _
11
10
12
-
13
- template_rendered = Signal (providing_args = ['instance' , 'start' , 'end' , 'level' ])
11
+ template_rendered = Signal (providing_args = [
12
+ 'instance' , 'start' , 'end' , 'level' , 'processing_timeline' ,
13
+ ])
14
+
15
+
16
+ node_element_colors = {}
17
+
18
+
19
+ def get_nodelist_timeline (nodelist , level ):
20
+ timeline = []
21
+ for node in nodelist :
22
+ timeline += get_node_timeline (node , level )
23
+ return timeline
24
+
25
+
26
+ def get_node_timeline (node , level ):
27
+ """
28
+ Get timeline for node and it's children
29
+ """
30
+ timeline = []
31
+ child_nodelists = getattr (node , "child_nodelists" , None )
32
+ if child_nodelists :
33
+ for child_nodelist_str in child_nodelists :
34
+ child_nodelist = getattr (node , child_nodelist_str , None )
35
+ if child_nodelist :
36
+ timeline += get_nodelist_timeline (child_nodelist , level + 1 )
37
+
38
+ if hasattr (node , "_node_end" ):
39
+ timeline .append (
40
+ {
41
+ "node" : node ,
42
+ "name" : node ,
43
+ "start" : node ._node_start ,
44
+ "end" : node ._node_end ,
45
+ "level" : level ,
46
+ },
47
+ )
48
+ return timeline
14
49
15
50
16
51
class TemplateProfilerPanel (Panel ):
@@ -49,6 +84,9 @@ def monkey_patch_template_classes(cls):
49
84
else :
50
85
template_classes .append (Jinja2Template )
51
86
87
+ from django .template import Node as DjangoNode
88
+ node_classes = [DjangoNode ]
89
+
52
90
@wrapt .decorator
53
91
def render_wrapper (wrapped , instance , args , kwargs ):
54
92
start = time ()
@@ -63,18 +101,32 @@ def render_wrapper(wrapped, instance, args, kwargs):
63
101
break
64
102
stack_depth += 1
65
103
104
+ timeline = get_nodelist_timeline (instance .nodelist , 0 )
105
+
66
106
template_rendered .send (
67
107
sender = instance .__class__ ,
68
108
instance = instance ,
69
109
start = start ,
70
110
end = end ,
111
+ processing_timeline = timeline ,
71
112
level = stack_depth ,
72
113
)
73
114
return result
74
115
75
116
for template_class in template_classes :
76
117
template_class .render = render_wrapper (template_class .render )
77
118
119
+ @wrapt .decorator
120
+ def render_node_wrapper (wrapped , instance , args , kwargs ):
121
+ instance ._node_start = time ()
122
+ result = wrapped (* args , ** kwargs )
123
+ instance ._node_end = time ()
124
+ return result
125
+
126
+ for node_class in node_classes :
127
+ node_class .render_annotated = render_node_wrapper (
128
+ node_class .render_annotated )
129
+
78
130
cls .have_monkey_patched_template_classes = True
79
131
80
132
@property
@@ -93,7 +145,8 @@ def title(self):
93
145
def _get_color (self , level ):
94
146
return self .colors .setdefault (level , next (self .color_generator ))
95
147
96
- def record (self , instance , start , end , level , ** kwargs ):
148
+ def record (self , instance , start , end , level ,
149
+ processing_timeline , ** kwargs ):
97
150
if not self .enabled :
98
151
return
99
152
@@ -118,6 +171,7 @@ def record(self, instance, start, end, level, **kwargs):
118
171
'end' : end ,
119
172
'time' : (end - start ) * 1000.0 ,
120
173
'level' : level ,
174
+ 'processing_timeline' : processing_timeline ,
121
175
'name' : template_name ,
122
176
'color' : color ,
123
177
})
@@ -132,7 +186,7 @@ def _calc_p(self, part, whole):
132
186
# return the percentage of part or 100% if whole is zero
133
187
return (part / whole ) * 100.0 if whole else 100.0
134
188
135
- def _calc_timeline (self , start , end ):
189
+ def _calc_timeline (self , start , end , processing_timeline ):
136
190
result = {}
137
191
result ['offset_p' ] = self ._calc_p (
138
192
start - self .t_min , self .t_max - self .t_min )
@@ -143,6 +197,38 @@ def _calc_timeline(self, start, end):
143
197
result ['rel_duration_p' ] = self ._calc_p (
144
198
result ['duration_p' ], 100 - result ['offset_p' ])
145
199
200
+ result ['relative_start' ] = (start - self .t_min ) * 1000.0
201
+ result ['relative_end' ] = (end - self .t_min ) * 1000.0
202
+
203
+ result ['processing_timeline' ] = []
204
+ for time_item in processing_timeline :
205
+ if 'node' in time_item :
206
+ class_name = time_item ['node' ].__class__ .__name__
207
+ else :
208
+ class_name = time_item ['type' ]
209
+ if class_name not in node_element_colors :
210
+ node_element_colors [class_name ] = next (self .color_generator )
211
+ bg_color = node_element_colors [class_name ]
212
+ if 'node' in time_item :
213
+ position = time_item ['node' ].token .position
214
+ else :
215
+ False
216
+ result ['processing_timeline' ].append ({
217
+ 'name' : time_item ['name' ],
218
+ 'position' : position ,
219
+ 'relative_start' : (time_item ['start' ] - self .t_min ) * 1000.0 ,
220
+ 'relative_end' : (time_item ['end' ] - self .t_min ) * 1000.0 ,
221
+ 'duration' : (time_item ['end' ] - time_item ['start' ]) * 1000.0 ,
222
+ 'rel_duration_p' : self ._calc_p (
223
+ time_item ['end' ] - time_item ['start' ],
224
+ self .t_max - self .t_min ),
225
+ 'offset_p' : self ._calc_p (
226
+ time_item ['start' ]- self .t_min ,
227
+ self .t_max - self .t_min ),
228
+ 'bg_color' : bg_color ,
229
+ 'level' : time_item ['level' ] if 'level' in time_item else 0 ,
230
+ })
231
+
146
232
return result
147
233
148
234
def process_request (self , request ):
@@ -165,7 +251,9 @@ def process_request(self, request):
165
251
# Calc timelines
166
252
for template in self .templates :
167
253
template .update (
168
- self ._calc_timeline (template ['start' ], template ['end' ]))
254
+ self ._calc_timeline (
255
+ template ['start' ], template ['end' ],
256
+ template ['processing_timeline' ]))
169
257
170
258
self .total = len (self .templates )
171
259
0 commit comments