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/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 c70eaf5ed..0aba24ab9 100644
--- a/debug_toolbar/static/debug_toolbar/css/toolbar.css
+++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css
@@ -621,6 +621,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 %}
-
+
{% if call.has_subfuncs %}
diff --git a/docs/changes.rst b/docs/changes.rst
index bd66d7658..923f18dec 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -4,6 +4,16 @@ Change log
Pending
-------
+* Added Profiling panel setting ``PROFILER_THRESHOLD_RATIO`` to give users
+ better control over how many function calls are included. A higher value
+ will include more data, but increase render time.
+* Update Profiling panel to include try to always include user code. This
+ code is more important to developers than dependency code.
+* Highlight the project function calls in the profiling panel.
+* Added Profiling panel setting ``PROFILER_CAPTURE_PROJECT_CODE`` to allow
+ users to disable the inclusion of all project code. This will be useful
+ to project setups that have dependencies installed under
+ ``settings.BASE_DIR``.
* The toolbar's font stack now prefers system UI fonts.
3.6.0 (2022-08-17)
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 6f4084ad5..07e0a845c 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -250,6 +250,18 @@ Panel options
WHERE "auth_user"."username" = '''test_username'''
LIMIT 21
+* ``PROFILER_CAPTURE_PROJECT_CODE``
+
+ Default: ``True``
+
+ Panel: profiling
+
+ When enabled this setting will include all project function calls in the
+ panel. Project code is defined as files in the path defined at
+ ``settings.BASE_DIR``. If you install dependencies under
+ ``settings.BASE_DIR`` in a directory other than ``sites-packages`` or
+ ``dist-packages`` you may need to disable this setting.
+
* ``PROFILER_MAX_DEPTH``
Default: ``10``
@@ -259,6 +271,20 @@ Panel options
This setting affects the depth of function calls in the profiler's
analysis.
+* ``PROFILER_THRESHOLD_RATIO``
+
+ Default: ``8``
+
+ Panel: profiling
+
+ This setting affects the which calls are included in the profile. A higher
+ value will include more function calls. A lower value will result in a faster
+ render of the profiling panel, but will exclude data.
+
+ This value is used to determine the threshold of cumulative time to include
+ the nested functions. The threshold is calculated by the root calls'
+ cumulative time divided by this ratio.
+
* ``SHOW_TEMPLATE_CONTEXT``
Default: ``True``
diff --git a/docs/panels.rst b/docs/panels.rst
index 8e5558aab..09891f2e5 100644
--- a/docs/panels.rst
+++ b/docs/panels.rst
@@ -130,6 +130,12 @@ Profiling information for the processing of the request.
This panel is included but inactive by default. You can activate it by default
with the ``DISABLE_PANELS`` configuration option.
+The panel will include all function calls made by your project if you're using
+the setting ``settings.BASE_DIR`` to point to your project's root directory.
+If a function is in a file within that directory and does not include
+``"/site-packages/"`` or ``"/dist-packages/"`` in the path, it will be
+included.
+
Third-party panels
------------------
diff --git a/docs/tips.rst b/docs/tips.rst
index e6957b0c6..d5d160fb3 100644
--- a/docs/tips.rst
+++ b/docs/tips.rst
@@ -77,6 +77,8 @@ by disabling some configuration options that are enabled by default:
- ``ENABLE_STACKTRACES`` for the SQL and cache panels,
- ``SHOW_TEMPLATE_CONTEXT`` for the template panel.
+- ``PROFILER_CAPTURE_PROJECT_CODE`` and ``PROFILER_THRESHOLD_RATIO`` for the
+ profiling panel.
Also, check ``SKIP_TEMPLATE_PREFIXES`` when you're using template-based
form widgets.
diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py
index ca5c2463b..2169932b2 100644
--- a/tests/panels/test_profiling.py
+++ b/tests/panels/test_profiling.py
@@ -33,6 +33,21 @@ def test_insert_content(self):
# ensure the panel renders correctly.
content = self.panel.content
self.assertIn("regular_view", content)
+ self.assertIn("render", content)
+ self.assertValidHTML(content)
+
+ @override_settings(DEBUG_TOOLBAR_CONFIG={"PROFILER_THRESHOLD_RATIO": 1})
+ def test_cum_time_threshold(self):
+ """
+ Test that cumulative time threshold excludes calls
+ """
+ self._get_response = lambda request: regular_view(request, "profiling")
+ response = self.panel.process_request(self.request)
+ self.panel.generate_stats(self.request, response)
+ # ensure the panel renders but doesn't include our function.
+ content = self.panel.content
+ self.assertIn("regular_view", content)
+ self.assertNotIn("render", content)
self.assertValidHTML(content)
def test_listcomp_escaped(self):
|