Skip to content

Commit dbb1790

Browse files
committed
add trace of rendering template nodes
1 parent 5d4e06b commit dbb1790

File tree

2 files changed

+127
-11
lines changed

2 files changed

+127
-11
lines changed

template_profiler_panel/panels/template.py

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,49 @@
33
from time import time
44

55
import wrapt
6-
from django.dispatch import Signal
7-
from django.utils.translation import ugettext_lazy as _
8-
96
from debug_toolbar.panels import Panel
107
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 _
1110

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
1449

1550

1651
class TemplateProfilerPanel(Panel):
@@ -49,6 +84,9 @@ def monkey_patch_template_classes(cls):
4984
else:
5085
template_classes.append(Jinja2Template)
5186

87+
from django.template import Node as DjangoNode
88+
node_classes = [DjangoNode]
89+
5290
@wrapt.decorator
5391
def render_wrapper(wrapped, instance, args, kwargs):
5492
start = time()
@@ -63,18 +101,32 @@ def render_wrapper(wrapped, instance, args, kwargs):
63101
break
64102
stack_depth += 1
65103

104+
timeline = get_nodelist_timeline(instance.nodelist, 0)
105+
66106
template_rendered.send(
67107
sender=instance.__class__,
68108
instance=instance,
69109
start=start,
70110
end=end,
111+
processing_timeline=timeline,
71112
level=stack_depth,
72113
)
73114
return result
74115

75116
for template_class in template_classes:
76117
template_class.render = render_wrapper(template_class.render)
77118

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+
78130
cls.have_monkey_patched_template_classes = True
79131

80132
@property
@@ -93,7 +145,8 @@ def title(self):
93145
def _get_color(self, level):
94146
return self.colors.setdefault(level, next(self.color_generator))
95147

96-
def record(self, instance, start, end, level, **kwargs):
148+
def record(self, instance, start, end, level,
149+
processing_timeline, **kwargs):
97150
if not self.enabled:
98151
return
99152

@@ -118,6 +171,7 @@ def record(self, instance, start, end, level, **kwargs):
118171
'end': end,
119172
'time': (end - start) * 1000.0,
120173
'level': level,
174+
'processing_timeline': processing_timeline,
121175
'name': template_name,
122176
'color': color,
123177
})
@@ -132,7 +186,7 @@ def _calc_p(self, part, whole):
132186
# return the percentage of part or 100% if whole is zero
133187
return (part / whole) * 100.0 if whole else 100.0
134188

135-
def _calc_timeline(self, start, end):
189+
def _calc_timeline(self, start, end, processing_timeline):
136190
result = {}
137191
result['offset_p'] = self._calc_p(
138192
start - self.t_min, self.t_max - self.t_min)
@@ -143,6 +197,38 @@ def _calc_timeline(self, start, end):
143197
result['rel_duration_p'] = self._calc_p(
144198
result['duration_p'], 100 - result['offset_p'])
145199

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+
146232
return result
147233

148234
def process_request(self, request):
@@ -165,7 +251,9 @@ def process_request(self, request):
165251
# Calc timelines
166252
for template in self.templates:
167253
template.update(
168-
self._calc_timeline(template['start'], template['end']))
254+
self._calc_timeline(
255+
template['start'], template['end'],
256+
template['processing_timeline']))
169257

170258
self.total = len(self.templates)
171259

template_profiler_panel/templates/template_profiler_panel/template.html

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ <h4>{{ templates|length }} {% trans 'calls to Template.render()' %}</h4>
44
<table>
55
<colgroup>
66
<col style="width:5%" />
7-
<col style="width:30%" />
7+
<col style="width:10%" />
8+
<col style="width:1%" />
89
<col style="width:60%" />
910
<col style="width:5%" />
1011
</colgroup>
1112
<thead>
1213
<tr>
1314
<th>{% trans 'Stack Level' %}</th>
1415
<th>{% trans 'Template Name' %}</th>
16+
<th>{% trans 'Trace' %}</th>
1517
<th>{% trans 'Timeline' %}</th>
1618
<th>{% trans 'Time (ms)' %}</th>
1719
</tr>
@@ -23,10 +25,36 @@ <h4>{{ templates|length }} {% trans 'calls to Template.render()' %}</h4>
2325
<div style="background-color: {{ template.color.bg }}; color: {{ template.color.text }}; width: {{ template.level }}px; margin-right: 4px; padding-left: 2px;">{{ template.level }}</div>
2426
</td>
2527
<td>{{ template.name }}</td>
28+
<td>
29+
<a href="javascript:$('.timeline_toggle_{{ forloop.counter }}').toggle();">
30+
<div class="timeline_toggle_{{ forloop.counter }}">&#9658;</div>
31+
<div class="timeline_toggle_{{ forloop.counter }}" style="display: none;">&#9660;</div>
32+
</a>
33+
</td>
2634
<td class="timeline">
27-
<div class="djDebugTimeline">
35+
<div class="djDebugTimeline" style="position:relative;">
2836
<div class="djDebugLineChart" style="margin-left: {{ template.offset_p|stringformat:'f' }}%;">
29-
<div title="Start {{ template.start }}" style="min-width: 1px; width: {{ template.rel_duration_p|stringformat:'f' }}%; background-color: {{ template.color.bg }};">&nbsp;</div>
37+
<div title="Runtime {{ template.relative_start|floatformat:2 }} - {{ template.relative_end|floatformat:2 }} ms" style="min-width: 1px; width: {{ template.rel_duration_p|stringformat:'f' }}%; background-color: {{ template.color.bg }};">
38+
&nbsp;</div>
39+
</div>
40+
<div style="height: 200px; position:relative; display: none;" class="timeline_toggle_{{ forloop.counter }}">
41+
{% for time_item in template.processing_timeline %}
42+
<span title="{{ time_item.name }}
43+
Runtime {{ time_item.relative_start|floatformat:2 }} - {{ time_item.relative_end|floatformat:2 }} ms
44+
Duration {{ time_item.duration|floatformat:2 }} ms
45+
{% if time_item.position %}Lines {{ time_item.position.0 }} - {{ time_item.position.1 }}{% endif %}
46+
Depth {{ time_item.level|add:1 }}
47+
" style="
48+
min-width: 1px;
49+
margin-left: {{ time_item.offset_p|stringformat:'f' }}%;
50+
width: {{ time_item.rel_duration_p|stringformat:'f' }}%;
51+
background-color: {{ time_item.bg_color }};
52+
top: calc({{ time_item.level }}px * 18);
53+
position: absolute;
54+
{# border: red 0.1px solid; #}
55+
opacity: 0.9;
56+
">&nbsp;</span>
57+
{% endfor %}
3058
</div>
3159
</div>
3260
</td>

0 commit comments

Comments
 (0)