Skip to content

Commit 94e19f0

Browse files
committed
Support Server Timing
Adds Server-Timing to response headers. Specification https://www.w3.org/TR/server-timing/ Inspired By https://twitter.com/paul_irish/status/829090506084749312
1 parent e5da8a4 commit 94e19f0

File tree

8 files changed

+113
-0
lines changed

8 files changed

+113
-0
lines changed

debug_toolbar/middleware.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,32 @@ def process_response(self, request, response):
130130
# When the toolbar will be inserted for sure, generate the stats.
131131
for panel in reversed(toolbar.enabled_panels):
132132
panel.generate_stats(request, response)
133+
panel.generate_server_timing(request, response)
134+
135+
response = self.generate_server_timing_header(response, toolbar.enabled_panels)
133136

134137
bits[-2] += toolbar.render_toolbar()
135138
response.content = insert_before.join(bits)
136139
if response.get('Content-Length', None):
137140
response['Content-Length'] = len(response.content)
138141
return response
142+
143+
@staticmethod
144+
def generate_server_timing_header(response, panels):
145+
data = []
146+
147+
for panel in panels:
148+
stats = panel.get_server_timing_stats()
149+
if not stats:
150+
continue
151+
152+
for key, record in stats.items():
153+
# example: `SQLPanel_sql_time=0; "SQL 0 queries"`
154+
data.append('{}_{}={}; "{}"'.format(panel.panel_id,
155+
key,
156+
record.get('value'),
157+
record.get('title')))
158+
159+
if data:
160+
response['Server-Timing'] = ', '.join(data)
161+
return response

debug_toolbar/panels/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ def get_stats(self):
147147
"""
148148
return self.toolbar.stats.get(self.panel_id, {})
149149

150+
def record_server_timing(self, key, title, value):
151+
"""
152+
Store data gathered by the panel. ``stats`` is a :class:`dict`.
153+
154+
Each call to ``record_stats`` updates the statistics dictionary.
155+
"""
156+
data = {key: dict(title=title, value=value)}
157+
self.toolbar.server_timing_stats.setdefault(self.panel_id, {}).update(data)
158+
159+
def get_server_timing_stats(self):
160+
"""
161+
Access data stored by the panel. Returns a :class:`dict`.
162+
"""
163+
return self.toolbar.server_timing_stats.get(self.panel_id, {})
164+
150165
# Standard middleware methods
151166

152167
def process_request(self, request):
@@ -192,6 +207,16 @@ def generate_stats(self, request, response):
192207
Does not return a value.
193208
"""
194209

210+
def generate_server_timing(self, request, response):
211+
"""
212+
Similar to :meth:`generate_stats
213+
<debug_toolbar.panels.Panel.generate_stats>`,
214+
215+
Generate stats for Server Timing https://w3c.github.io/server-timing/
216+
217+
Does not return a value.
218+
"""
219+
195220

196221
# Backward-compatibility for 1.0, remove in 2.0.
197222
class DebugPanel(Panel):

debug_toolbar/panels/cache.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,9 @@ def generate_stats(self, request, response):
244244
'misses': self.misses,
245245
'counts': self.counts,
246246
})
247+
248+
def generate_server_timing(self, request, response):
249+
stats = self.get_stats()
250+
value = stats.get('total_time', 0)
251+
title = 'Cache {} Calls'.format(stats.get('total_calls', 0))
252+
self.record_server_timing('total_time', title, value)

debug_toolbar/panels/sql/panel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,9 @@ def generate_stats(self, request, response):
240240
'queries': [q for a, q in self._queries],
241241
'sql_time': self._sql_time,
242242
})
243+
244+
def generate_server_timing(self, request, response):
245+
stats = self.get_stats()
246+
title = 'SQL {} queries'.format(len(stats.get('queries', [])))
247+
value = stats.get('sql_time', 0)
248+
self.record_server_timing('sql_time', title, value)

debug_toolbar/panels/timer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,13 @@ def generate_stats(self, request, response):
8282

8383
self.record_stats(stats)
8484

85+
def generate_server_timing(self, request, response):
86+
stats = self.get_stats()
87+
88+
self.record_server_timing('utime', 'User CPU time', stats.get('utime', 0))
89+
self.record_server_timing('stime', 'System CPU time', stats.get('stime', 0))
90+
self.record_server_timing('total', 'Total CPU time', stats.get('total', 0))
91+
self.record_server_timing('total_time', 'Elapsed time', stats.get('total_time', 0))
92+
8593
def _elapsed_ru(self, name):
8694
return getattr(self._end_rusage, name) - getattr(self._start_rusage, name)

debug_toolbar/toolbar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(self, request):
2727
panel_instance = panel_class(self)
2828
self._panels[panel_instance.panel_id] = panel_instance
2929
self.stats = {}
30+
self.server_timing_stats = {}
3031
self.store_id = None
3132

3233
# Manage panels

tests/panels/test_cache.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,25 @@ def test_insert_content(self):
4747
self.panel.generate_stats(self.request, self.response)
4848
# ensure the panel renders correctly.
4949
self.assertIn('café', self.panel.content)
50+
51+
def test_generate_server_timin(self):
52+
self.assertEqual(len(self.panel.calls), 0)
53+
cache.cache.set('foo', 'bar')
54+
cache.cache.get('foo')
55+
cache.cache.delete('foo')
56+
57+
self.assertEqual(len(self.panel.calls), 3)
58+
59+
self.panel.generate_stats(self.request, self.response)
60+
self.panel.generate_server_timing(self.request, self.response)
61+
62+
stats = self.panel.get_stats()
63+
64+
expected_data = {
65+
'total_time': {
66+
'title': 'Cache {} Calls'.format(stats['total_calls']),
67+
'value': stats['total_time']
68+
}
69+
}
70+
71+
self.assertEqual(self.panel.get_server_timing_stats(), expected_data)

tests/panels/test_sql.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ def test_recording(self):
4848
# ensure the stacktrace is populated
4949
self.assertTrue(len(query[1]['stacktrace']) > 0)
5050

51+
def test_generate_server_timing(self):
52+
self.assertEqual(len(self.panel._queries), 0)
53+
54+
list(User.objects.all())
55+
56+
self.panel.process_response(self.request, self.response)
57+
self.panel.generate_stats(self.request, self.response)
58+
self.panel.generate_server_timing(self.request, self.response)
59+
60+
# ensure query was logged
61+
self.assertEqual(len(self.panel._queries), 1)
62+
query = self.panel._queries[0]
63+
64+
expected_data = {
65+
'sql_time': {
66+
'title': 'SQL 1 queries',
67+
'value': query[1]['duration']
68+
}
69+
}
70+
71+
self.assertEqual(self.panel.get_server_timing_stats(), expected_data)
72+
5173
def test_non_ascii_query(self):
5274
self.assertEqual(len(self.panel._queries), 0)
5375

0 commit comments

Comments
 (0)