Skip to content

Commit a43e535

Browse files
committed
Initial commit
0 parents  commit a43e535

File tree

13 files changed

+343
-0
lines changed

13 files changed

+343
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pyc
2+
django_debug_toolbar_template_profiler.egg-info
3+
dist

LICENSE

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2014, Sergej Alikov
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
2. Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
13+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23+
24+
The views and conclusions contained in the software and documentation are those
25+
of the authors and should not be interpreted as representing official policies,
26+
either expressed or implied, of the FreeBSD Project.

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include LICENSE
2+
include README.rst
3+
recursive-include template_profiler_panel/templates *

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Django Debug Toolbar Template Profiler
2+
======================================
3+
4+
This panel displays template time spent rendering each template and
5+
graphical respresentation on the timeline (see screenshot)
6+
7+
8+
Install
9+
=======
10+
11+
- Add 'template_profiler_panel.panels.template.TemplateProfilerPanel' to
12+
DEBUG_TOOLBAR_PANELS in your settings.py (see
13+
http://django-debug-toolbar.readthedocs.org/en/latest/configuration.html#debug-toolbar-panels)
14+
15+
- Add 'template_profiler_panel' to INSTALLED_APPS
16+
17+
18+
Screenshot
19+
==========
20+
.. image:: screenshot.png
21+

screenshot.png

164 KB
Loading

setup.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from setuptools import setup, find_packages
2+
from io import open
3+
4+
setup(
5+
name='django-debug-toolbar-template-profiler',
6+
version='1.0.0',
7+
description='Displays template rendering time on the timeline',
8+
long_description=open('README.rst', encoding='utf-8').read(),
9+
author='Sergej Alikov',
10+
author_email='[email protected]',
11+
url='https://github.com/node13h/django-debug-toolbartemplate-profiler',
12+
download_url='https://pypi.python.org/pypi/django-debug-toolbar-template-profiler',
13+
license='Simplified BSD License',
14+
packages=find_packages(),
15+
install_requires=[
16+
'django-debug-toolbar>=1.0',
17+
],
18+
include_package_data=True,
19+
zip_safe=False, # because we're including static files
20+
classifiers=[
21+
'Development Status :: 5 - Production/Stable',
22+
'Environment :: Web Environment',
23+
'Framework :: Django',
24+
'Intended Audience :: Developers',
25+
'License :: OSI Approved :: BSD License',
26+
'Operating System :: OS Independent',
27+
'Programming Language :: Python',
28+
'Programming Language :: Python :: 2',
29+
'Programming Language :: Python :: 2.7',
30+
'Topic :: Software Development :: Libraries :: Python Modules',
31+
],
32+
)

template_profiler_panel/__init__.py

Whitespace-only changes.

template_profiler_panel/models.py

Whitespace-only changes.

template_profiler_panel/panels/__init__.py

Whitespace-only changes.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
from time import time
2+
from collections import defaultdict
3+
from inspect import stack
4+
5+
from django.dispatch import Signal
6+
from django.template import Template
7+
from django.utils.translation import ugettext_lazy as _
8+
9+
from debug_toolbar.panels import Panel
10+
11+
12+
def dummy_color_generator():
13+
while True:
14+
yield '#bbbbbb'
15+
16+
# Contrasting_color_generator is available since debug toolbar version 1.1.
17+
try:
18+
from debug_toolbar.panels.sql.utils import contrasting_color_generator
19+
except ImportError:
20+
# Support older versions of debug toolbar
21+
contrasting_color_generator = dummy_color_generator
22+
23+
template_rendered = Signal(
24+
providing_args=['instance', 'start', 'end', 'level'])
25+
26+
27+
def template_render_wrapper(self, context):
28+
result = None
29+
if hasattr(Template, 'tp_saved_render'):
30+
t_start = time()
31+
try:
32+
result = Template.tp_saved_render(self, context)
33+
finally:
34+
t_end = time()
35+
36+
template_rendered.send(
37+
sender=Template, instance=self, start=t_start, end=t_end,
38+
level=len(stack()))
39+
40+
return result
41+
42+
# Wrap the original Template.render
43+
if not hasattr(Template, 'tp_saved_render'):
44+
Template.tp_saved_render = Template.render
45+
Template.render = template_render_wrapper
46+
47+
48+
class TemplateProfilerPanel(Panel):
49+
'''
50+
Displays template rendering times on the request timeline
51+
'''
52+
53+
template = 'template_profiler_panel/template.html'
54+
colors = None
55+
templates = None
56+
color_generator = None
57+
t_min = 0
58+
t_max = 0
59+
total = 0
60+
61+
def __init__(self, *args, **kwargs):
62+
self.colors = {}
63+
self.templates = []
64+
self.color_generator = contrasting_color_generator()
65+
return super(TemplateProfilerPanel, self).__init__(*args, **kwargs)
66+
67+
@property
68+
def nav_title(self):
69+
return _('Template Profiler')
70+
71+
@property
72+
def nav_subtitle(self):
73+
return _('{} calls in {:.2f} ms').format(
74+
self.total, (self.t_max - self.t_min) * 1000.0)
75+
76+
@property
77+
def title(self):
78+
return _('Template Rendering Time')
79+
80+
def _get_color(self, level):
81+
return self.colors.setdefault(level, next(self.color_generator))
82+
83+
def record(self, sender, instance, start, end, level, **kwargs):
84+
85+
bg = self._get_color(level)
86+
text = '#ffffff' if int(bg[1:], 16) < 0x8fffff else '#000000'
87+
color = {'bg': bg, 'text': text}
88+
89+
self.templates.append({
90+
'start': start,
91+
'end': end,
92+
'time': (end - start) * 1000.0,
93+
'level': level,
94+
'name': instance.name,
95+
'color': color,
96+
})
97+
98+
def enable_instrumentation(self):
99+
template_rendered.connect(self.record)
100+
101+
def disable_instrumentation(self):
102+
template_rendered.disconnect(self.record)
103+
104+
def _calc_p(self, part, whole):
105+
return (part / whole) * 100.0
106+
107+
def _calc_timeline(self, start, end):
108+
result = {}
109+
result['offset_p'] = self._calc_p(
110+
start - self.t_min, self.t_max - self.t_min)
111+
112+
result['duration_p'] = self._calc_p(
113+
end - start, self.t_max - self.t_min)
114+
115+
result['rel_duration_p'] = self._calc_p(
116+
result['duration_p'], 100 - result['offset_p'])
117+
118+
return result
119+
120+
def process_response(self, request, response):
121+
summary = defaultdict(float)
122+
123+
# Collect stats
124+
for template in self.templates:
125+
if self.t_min == 0:
126+
self.t_min = template['start']
127+
elif template['start'] < self.t_min:
128+
self.t_min = template['start']
129+
130+
if template['end'] > self.t_max:
131+
self.t_max = template['end']
132+
133+
summary[template['name']] += template['time']
134+
135+
# Calc timelines
136+
for template in self.templates:
137+
template.update(
138+
self._calc_timeline(template['start'], template['end']))
139+
140+
self.total = len(self.templates)
141+
142+
self.record_stats(
143+
{'templates': self.templates, 'summary': dict(summary)})

0 commit comments

Comments
 (0)