Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions debug_toolbar/panels/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def content(self):
(_("System CPU time"), _("%(stime)0.3f ms") % stats),
(_("Total CPU time"), _("%(total)0.3f ms") % stats),
(_("Elapsed time"), _("%(total_time)0.3f ms") % stats),
(
_("DebugToolbar time"),
_("%(toolbar_time)0.3f ms") % stats,
),
(
_("Context switches"),
_("%(vcsw)d voluntary, %(ivcsw)d involuntary") % stats,
Expand Down Expand Up @@ -109,6 +113,9 @@ def generate_stats(self, request, response):
# stats['urss'] = self._end_rusage.ru_idrss
# stats['usrss'] = self._end_rusage.ru_isrss

if hasattr(self, "_toolbar_start_time"):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EAFP: Maybe consider catching the AttributeError instead of the default if-branch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could do that, but generate_stats already uses hasattr(self, "_start_time") on line 86. elsewhere the codebase also uses this pattern so I was following that..

stats["toolbar_time"] = (perf_counter() - self._toolbar_start_time) * 1000

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider storing the duration with an explicit type using datetime.timedelta. It's always easier than remembering that toolbar_time is in milliseconds.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every other timing stat in this file is a millisecond float, and downstream code consumes ms. so I think this is a bit out of scope here.


self.record_stats(stats)

def generate_server_timing(self, request, response):
Expand All @@ -120,7 +127,13 @@ def generate_server_timing(self, request, response):
self.record_server_timing(
"total_time", "Elapsed time", stats.get("total_time", 0)
)
self.record_server_timing(
"toolbar_time", "DebugToolbar time", stats.get("toolbar_time", 0)
)

@staticmethod
def _elapsed_ru(start, end, name):
return end.get(name) - start.get(name)

def enable_instrumentation(self):
self._toolbar_start_time = perf_counter()
14 changes: 13 additions & 1 deletion debug_toolbar/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,19 @@ def enabled_panels(self) -> list[Panel]:
"""
Get a list of panels enabled for the current request.
"""
return [panel for panel in self._panels.values() if panel.enabled]
# Ensure TimerPanel is first in order to measure the full time of the toolbar's processing.
panels = []
timer_panel = None
for panel in self._panels.values():
if not panel.enabled:
continue
if panel.panel_id == "TimerPanel":
timer_panel = panel
else:
panels.append(panel)
if timer_panel is not None:
panels.insert(0, timer_panel)
return panels

@property
def csp_nonce(self) -> str | None:
Expand Down
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ Pending
* Fixed a crash which occurred when using non-``str`` static file values.
* Documented experimental async support.
* Improved troubleshooting doc for incorrect mime types for .js static files
* Added toolbar time to the timer panel.

Please see everything under 5.0.0-alpha as well.

Expand Down
5 changes: 5 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ def test_concurrent_async_sql_page(self):
len(response.toolbar.get_panel_by_id("SQLPanel").get_stats()["queries"]), 2
)

def test_timer_panel_first(self):
toolbar = DebugToolbar(self.request, self.get_response)
self.assertEqual(toolbar.enabled_panels[0].panel_id, "TimerPanel")


@override_settings(DEBUG=True)
class DebugToolbarIntegrationTestCase(IntegrationTestCase):
Expand Down Expand Up @@ -620,6 +624,7 @@ def test_server_timing_headers(self):
r'TimerPanel_stime;dur=(\d)*(\.(\d)*)?;desc="System CPU time", ',
r'TimerPanel_total;dur=(\d)*(\.(\d)*)?;desc="Total CPU time", ',
r'TimerPanel_total_time;dur=(\d)*(\.(\d)*)?;desc="Elapsed time", ',
r'TimerPanel_toolbar_time;dur=(\d)*(\.(\d)*)?;desc="DebugToolbar time", ',
r'SQLPanel_sql_time;dur=(\d)*(\.(\d)*)?;desc="SQL 1 queries", ',
r'CachePanel_total_time;dur=0;desc="Cache 0 Calls"',
]
Expand Down