diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36baad66d..6e9ac0b64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,12 +15,12 @@ repos: hooks: - id: doc8 - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/adamchainz/django-upgrade - rev: 1.8.0 + rev: 1.10.0 hooks: - id: django-upgrade args: [--target-version, "3.2"] @@ -38,12 +38,14 @@ repos: - id: rst-backticks - id: rst-directive-colons - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v3.0.0-alpha.0 hooks: - id: prettier types_or: [javascript, css] + args: + - --trailing-comma=es5 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.22.0 + rev: v8.23.1 hooks: - id: eslint files: \.js?$ @@ -51,7 +53,7 @@ repos: args: - --fix - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 22.8.0 hooks: - id: black language_version: python3 diff --git a/README.rst b/README.rst index c7ea51bd6..d050f5068 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ Here's a screenshot of the toolbar in action: In addition to the built-in panels, a number of third-party panels are contributed by the community. -The current stable version of the Debug Toolbar is 3.6.0. It works on +The current stable version of the Debug Toolbar is 3.7.0. It works on Django ≥ 3.2.4. Documentation, including installation and configuration instructions, is diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py index 17f1f9e69..e9ed74ab1 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -4,7 +4,7 @@ # Do not use pkg_resources to find the version but set it here directly! # see issue #1446 -VERSION = "3.6.0" +VERSION = "3.7.0" # Code that discovers files or modules in INSTALLED_APPS imports this module. urls = "debug_toolbar.urls", APP_NAME diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 5fd5b3c84..ca32b98c2 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -3,6 +3,7 @@ from colorsys import hsv_to_rgb from pstats import Stats +from django.conf import settings from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ @@ -32,6 +33,22 @@ def background(self): r, g, b = hsv_to_rgb(*self.hsv) return f"rgb({r * 100:f}%,{g * 100:f}%,{b * 100:f}%)" + def is_project_func(self): + """ + Check if the function is from the project code. + + Project code is identified by the BASE_DIR setting + which is used in Django projects by default. + """ + if hasattr(settings, "BASE_DIR"): + file_name, _, _ = self.func + return ( + str(settings.BASE_DIR) in file_name + and "/site-packages/" not in file_name + and "/dist-packages/" not in file_name + ) + return None + def func_std_string(self): # match what old profile produced func_name = self.func if func_name[:2] == ("~", 0): @@ -123,19 +140,25 @@ class ProfilingPanel(Panel): title = _("Profiling") template = "debug_toolbar/panels/profiling.html" + capture_project_code = dt_settings.get_config()["PROFILER_CAPTURE_PROJECT_CODE"] def process_request(self, request): self.profiler = cProfile.Profile() return self.profiler.runcall(super().process_request, request) - def add_node(self, func_list, func, max_depth, cum_time=0.1): + def add_node(self, func_list, func, max_depth, cum_time): func_list.append(func) func.has_subfuncs = False if func.depth < max_depth: for subfunc in func.subfuncs(): - if subfunc.stats[3] >= cum_time: + # Always include the user's code + if subfunc.stats[3] >= cum_time or ( + self.capture_project_code + and subfunc.is_project_func() + and subfunc.stats[3] > 0 + ): func.has_subfuncs = True - self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) + self.add_node(func_list, subfunc, max_depth, cum_time) def generate_stats(self, request, response): if not hasattr(self, "profiler"): @@ -150,10 +173,13 @@ def generate_stats(self, request, response): if root_func in self.stats.stats: root = FunctionCall(self.stats, root_func, depth=0) func_list = [] + cum_time_threshold = ( + root.stats[3] / dt_settings.get_config()["PROFILER_THRESHOLD_RATIO"] + ) self.add_node( func_list, root, dt_settings.get_config()["PROFILER_MAX_DEPTH"], - root.stats[3] / 8, + cum_time_threshold, ) self.record_stats({"func_list": func_list}) diff --git a/debug_toolbar/panels/request.py b/debug_toolbar/panels/request.py index 966301d97..bfb485ae7 100644 --- a/debug_toolbar/panels/request.py +++ b/debug_toolbar/panels/request.py @@ -59,13 +59,12 @@ def generate_stats(self, request, response): self.record_stats(view_info) if hasattr(request, "session"): - self.record_stats( - { - "session": { - "list": [ - (k, request.session.get(k)) - for k in sorted(request.session.keys()) - ] - } - } - ) + try: + session_list = [ + (k, request.session.get(k)) for k in sorted(request.session.keys()) + ] + except TypeError: + session_list = [ + (k, request.session.get(k)) for k in request.session.keys() + ] + self.record_stats({"session": {"list": session_list}}) diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index 5bf9bb09f..2bad251c1 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -33,7 +33,9 @@ "django.utils.functional", ), "PRETTIFY_SQL": True, + "PROFILER_CAPTURE_PROJECT_CODE": True, "PROFILER_MAX_DEPTH": 10, + "PROFILER_THRESHOLD_RATIO": 8, "SHOW_TEMPLATE_CONTEXT": True, "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"), "SQL_WARNING_THRESHOLD": 500, # milliseconds diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index a105bfd11..f87d7a133 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -64,6 +64,7 @@ #djDebug tr, #djDebug th, #djDebug td, +#djDebug summary, #djDebug button { margin: 0; padding: 0; @@ -76,7 +77,9 @@ color: #000; vertical-align: baseline; background-color: transparent; - font-family: sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, + Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; text-align: left; text-shadow: none; white-space: normal; @@ -157,7 +160,7 @@ text-decoration: none; display: block; font-size: 16px; - padding: 10px 10px 5px 25px; + padding: 7px 10px 8px 25px; color: #fff; } #djDebug #djDebugToolbar li > div.djdt-disabled { @@ -176,6 +179,7 @@ #djDebug #djDebugToolbar li.djdt-active:before { content: "▶"; + font-family: sans-serif; position: absolute; left: 0; top: 50%; @@ -237,13 +241,30 @@ font-size: 16px; } +#djDebug pre, #djDebug code { display: block; - font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", - monospace; + font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", + "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", + "Fira Mono", "Droid Sans Mono", "Courier New", monospace, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Noto Color Emoji"; + overflow: auto; +} + +#djDebug code { font-size: 12px; white-space: pre; - overflow: auto; +} + +#djDebug pre { + white-space: pre-wrap; + color: #555; + border: 1px solid #ccc; + border-collapse: collapse; + background-color: #fff; + padding: 2px 3px; + margin-bottom: 3px; } #djDebug .djdt-panelContent { @@ -369,18 +390,19 @@ position: absolute; top: 4px; right: 15px; - height: 16px; - width: 16px; line-height: 16px; - padding: 5px; border: 6px solid #ddd; border-radius: 50%; background: #fff; color: #ddd; - text-align: center; font-weight: 900; font-size: 20px; - box-sizing: content-box; + height: 36px; + width: 36px; + padding: 0 0 5px; + box-sizing: border-box; + display: grid; + place-items: center; } #djDebug .djdt-panelContent .djDebugClose:hover { @@ -562,19 +584,7 @@ #djDebug .djSQLDetailsDiv { margin-top: 0.8em; } -#djDebug pre { - white-space: pre-wrap; - color: #555; - border: 1px solid #ccc; - border-collapse: collapse; - background-color: #fff; - display: block; - overflow: auto; - padding: 2px 3px; - margin-bottom: 3px; - font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", - monospace; -} + #djDebug .djdt-stack span { color: #000; font-weight: bold; @@ -614,6 +624,12 @@ #djDebug .djdt-highlighted { background-color: lightgrey; } +#djDebug tr.djdt-highlighted.djdt-profile-row { + background-color: #ffc; +} +#djDebug tr.djdt-highlighted.djdt-profile-row:nth-child(2n + 1) { + background-color: #dd9; +} @keyframes djdt-flash-new { from { background-color: green; diff --git a/debug_toolbar/templates/debug_toolbar/panels/profiling.html b/debug_toolbar/templates/debug_toolbar/panels/profiling.html index 837698889..4c1c3acd3 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/profiling.html +++ b/debug_toolbar/templates/debug_toolbar/panels/profiling.html @@ -12,7 +12,7 @@
{% for call in func_list %} -