Skip to content

Commit 6e4830e

Browse files
committed
Optimize render_stacktrace()
This function is called by the SQL panel to render each frame of each stack trace. Previously it used a Django template, which is slow. By generating the HTML with pure Python code, we can avoid much of this overhead. I tried this change on a demo app I built that has 96 total queries, due to N+1 queries. The queries execute in 13ms due to use of SQLite, so the whole view is a negligible concern. Nearly all the view runtime is the toolbar itself. Without this change, the runtime is ~1300ms; with the change it’s ~1100ms. That's a saving of **15%**. I also checked the appearance of the generated HTML hasn’t changed.
1 parent 5ad50f7 commit 6e4830e

File tree

3 files changed

+27
-25
lines changed

3 files changed

+27
-25
lines changed

debug_toolbar/templates/debug_toolbar/panels/sql_stacktrace.html

Lines changed: 0 additions & 4 deletions
This file was deleted.

debug_toolbar/utils.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import re
44
import sys
55
from importlib import import_module
6-
from itertools import chain
6+
from pprint import pformat
77

88
import django
99
from django.core.exceptions import ImproperlyConfigured
1010
from django.template import Node
11-
from django.template.loader import render_to_string
11+
from django.utils.html import format_html
1212
from django.utils.safestring import mark_safe
1313

1414
from debug_toolbar import settings as dt_settings
@@ -69,26 +69,31 @@ def tidy_stacktrace(stack):
6969

7070

7171
def render_stacktrace(trace):
72-
stacktrace = []
73-
for frame in trace:
74-
params = (v for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
75-
params_dict = {str(idx): v for idx, v in enumerate(params)}
76-
try:
77-
stacktrace.append(params_dict)
78-
except KeyError:
79-
# This frame doesn't have the expected format, so skip it and move
80-
# on to the next one
81-
continue
82-
83-
return mark_safe(
84-
render_to_string(
85-
"debug_toolbar/panels/sql_stacktrace.html",
86-
{
87-
"stacktrace": stacktrace,
88-
"show_locals": dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"],
89-
},
72+
show_locals = dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"]
73+
html = ""
74+
for abspath, lineno, func, code, locals_ in trace:
75+
directory, filename = abspath.rsplit(os.path.sep, 1)
76+
html += format_html(
77+
(
78+
'<span class="djdt-path">{}/</span>'
79+
+ '<span class="djdt-file">{}</span> in'
80+
+ ' <span class="djdt-func">{}</span>'
81+
+ '(<span class="djdt-lineno">{}</span>)\n'
82+
+ ' <span class="djdt-code">{}</span>\n'
83+
),
84+
directory,
85+
filename,
86+
func,
87+
lineno,
88+
code,
9089
)
91-
)
90+
if show_locals:
91+
html += format_html(
92+
' <pre class="djdt-locals">{}</pre>\n',
93+
pformat(locals_),
94+
)
95+
html += "\n"
96+
return mark_safe(html)
9297

9398

9499
def get_template_info():

docs/changes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Next version
1111
* Reset settings when overridden in tests. Packages or projects using
1212
django-debug-toolbar can now use Django’s test settings tools, like
1313
``@override_settings``, to reconfigure the toolbar during tests.
14+
* Optimize rendering of SQL panel, saving about 15% of runtime.
1415

1516
3.2.4 (2021-12-15)
1617
------------------

0 commit comments

Comments
 (0)