diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 2fdfbf454..567784083 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -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), + ( + _("Toolbar time"), + _("%(toolbar_time)0.3f ms") % stats, + ), ( _("Context switches"), _("%(vcsw)d voluntary, %(ivcsw)d involuntary") % stats, @@ -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"): + stats["toolbar_time"] = (perf_counter() - self._toolbar_start_time) * 1000 + self.record_stats(stats) def generate_server_timing(self, request, response): @@ -120,7 +127,16 @@ 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", "Toolbar 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() + + async def aenable_instrumentation(self): + self._toolbar_start_time = perf_counter() diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 0e22c8f06..46b306e5a 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -73,7 +73,15 @@ 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] + panels = [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. + timer_panel = next( + (panel for panel in panels if panel.panel_id == "TimerPanel"), None + ) + if timer_panel: + panels.remove(timer_panel) + panels.insert(0, timer_panel) + return panels @property def csp_nonce(self) -> str | None: diff --git a/docs/changes.rst b/docs/changes.rst index 074f65016..23d113895 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -144,6 +144,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. diff --git a/tests/test_integration.py b/tests/test_integration.py index 43a5fbc0b..e428698db 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -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): @@ -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="Toolbar time", ', r'SQLPanel_sql_time;dur=(\d)*(\.(\d)*)?;desc="SQL 1 queries", ', r'CachePanel_total_time;dur=0;desc="Cache 0 Calls"', ]