From 48ac250cf25bf23efb904fc5770c5c688a695d94 Mon Sep 17 00:00:00 2001 From: andoriyaprashant Date: Wed, 18 Jun 2025 15:49:24 +0530 Subject: [PATCH 1/2] Add .prof File Download Support to Profiling Panel --- debug_toolbar/panels/profiling.py | 13 +++++++++++-- debug_toolbar/panels/sql/tracking.py | 11 ++++++++++- .../debug_toolbar/panels/profiling.html | 9 +++++++++ debug_toolbar/urls.py | 12 +++++++++++- debug_toolbar/views.py | 19 ++++++++++++++++++- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 4613a3cad..2de17a81e 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -2,6 +2,7 @@ import os from colorsys import hsv_to_rgb from pstats import Stats +import tempfile from django.conf import settings from django.utils.html import format_html @@ -168,8 +169,11 @@ def generate_stats(self, request, response): self.stats = Stats(self.profiler) self.stats.calc_callees() - root_func = cProfile.label(super().process_request.__code__) + prof_file_path = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()) + ".prof") + self.profiler.dump_stats(prof_file_path) + self.prof_file_path = prof_file_path + root_func = cProfile.label(super().process_request.__code__) if root_func in self.stats.stats: root = FunctionCall(self.stats, root_func, depth=0) func_list = [] @@ -182,4 +186,9 @@ def generate_stats(self, request, response): dt_settings.get_config()["PROFILER_MAX_DEPTH"], cum_time_threshold, ) - self.record_stats({"func_list": func_list}) + self.record_stats({ + "func_list": func_list, + "prof_file_path": self.prof_file_path + }) + + diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 477106fdd..927ab1427 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -142,7 +142,16 @@ def _last_executed_query(self, sql, params): # process during the .last_executed_query() call. self.db._djdt_logger = None try: - return self.db.ops.last_executed_query(self.cursor, sql, params) + # Handle executemany: take the first set of parameters for formatting + if isinstance(params, (list, tuple)) and len(params) > 0 and isinstance(params[0], (list, tuple)): + sample_params = params[0] + else: + sample_params = params + + try: + return self.db.ops.last_executed_query(self.cursor, sql, sample_params) + except Exception: + return sql finally: self.db._djdt_logger = self.logger diff --git a/debug_toolbar/templates/debug_toolbar/panels/profiling.html b/debug_toolbar/templates/debug_toolbar/panels/profiling.html index 0c2206a13..39e8eeb93 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/profiling.html +++ b/debug_toolbar/templates/debug_toolbar/panels/profiling.html @@ -1,4 +1,13 @@ {% load i18n %} + +{% if prof_file_path %} +
+ + Download .prof file + +
+{% endif %} + diff --git a/debug_toolbar/urls.py b/debug_toolbar/urls.py index 5aa0d69e9..b7e0d3363 100644 --- a/debug_toolbar/urls.py +++ b/debug_toolbar/urls.py @@ -1,5 +1,15 @@ +from django.urls import path from debug_toolbar import APP_NAME +from debug_toolbar import views as debug_toolbar_views from debug_toolbar.toolbar import DebugToolbar +from debug_toolbar import APP_NAME app_name = APP_NAME -urlpatterns = DebugToolbar.get_urls() + +urlpatterns = DebugToolbar.get_urls() + [ + path( + "download_prof_file/", + debug_toolbar_views.download_prof_file, + name="debug_toolbar_download_prof_file" + ), +] diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index b9a410db5..ffaeb8968 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,7 +1,10 @@ -from django.http import JsonResponse +import os +from django.http import JsonResponse, FileResponse, Http404 from django.utils.html import escape from django.utils.translation import gettext as _ +from django.views.decorators.http import require_GET + from debug_toolbar._compat import login_not_required from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar from debug_toolbar.toolbar import DebugToolbar @@ -25,3 +28,17 @@ def render_panel(request): content = panel.content scripts = panel.scripts return JsonResponse({"content": content, "scripts": scripts}) + + +@require_GET +def download_prof_file(request): + file_path = request.GET.get("path") + print("Serving .prof file:", file_path) + if not file_path or not os.path.exists(file_path): + print("File does not exist:", file_path) + raise Http404("File not found.") + + response = FileResponse(open(file_path, 'rb'), content_type='application/octet-stream') + response['Content-Disposition'] = f'attachment; filename="{os.path.basename(file_path)}"' + return response + From 49f29eb7eaf7aad37c2f551034f6e30fb59a3265 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:22:32 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- debug_toolbar/panels/profiling.py | 17 ++++++++--------- debug_toolbar/panels/sql/tracking.py | 6 +++++- debug_toolbar/urls.py | 9 ++++----- debug_toolbar/views.py | 17 ++++++++++------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 2de17a81e..8ae1db0e9 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -1,8 +1,8 @@ import cProfile import os +import tempfile from colorsys import hsv_to_rgb from pstats import Stats -import tempfile from django.conf import settings from django.utils.html import format_html @@ -169,9 +169,11 @@ def generate_stats(self, request, response): self.stats = Stats(self.profiler) self.stats.calc_callees() - prof_file_path = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()) + ".prof") + prof_file_path = os.path.join( + tempfile.gettempdir(), next(tempfile._get_candidate_names()) + ".prof" + ) self.profiler.dump_stats(prof_file_path) - self.prof_file_path = prof_file_path + self.prof_file_path = prof_file_path root_func = cProfile.label(super().process_request.__code__) if root_func in self.stats.stats: @@ -186,9 +188,6 @@ def generate_stats(self, request, response): dt_settings.get_config()["PROFILER_MAX_DEPTH"], cum_time_threshold, ) - self.record_stats({ - "func_list": func_list, - "prof_file_path": self.prof_file_path - }) - - + self.record_stats( + {"func_list": func_list, "prof_file_path": self.prof_file_path} + ) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 927ab1427..c2aff7609 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -143,7 +143,11 @@ def _last_executed_query(self, sql, params): self.db._djdt_logger = None try: # Handle executemany: take the first set of parameters for formatting - if isinstance(params, (list, tuple)) and len(params) > 0 and isinstance(params[0], (list, tuple)): + if ( + isinstance(params, (list, tuple)) + and len(params) > 0 + and isinstance(params[0], (list, tuple)) + ): sample_params = params[0] else: sample_params = params diff --git a/debug_toolbar/urls.py b/debug_toolbar/urls.py index b7e0d3363..6559d4874 100644 --- a/debug_toolbar/urls.py +++ b/debug_toolbar/urls.py @@ -1,15 +1,14 @@ from django.urls import path -from debug_toolbar import APP_NAME -from debug_toolbar import views as debug_toolbar_views + +from debug_toolbar import APP_NAME, views as debug_toolbar_views from debug_toolbar.toolbar import DebugToolbar -from debug_toolbar import APP_NAME app_name = APP_NAME urlpatterns = DebugToolbar.get_urls() + [ path( "download_prof_file/", - debug_toolbar_views.download_prof_file, - name="debug_toolbar_download_prof_file" + debug_toolbar_views.download_prof_file, + name="debug_toolbar_download_prof_file", ), ] diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index ffaeb8968..e7c2ece66 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,8 +1,8 @@ import os -from django.http import JsonResponse, FileResponse, Http404 + +from django.http import FileResponse, Http404, JsonResponse from django.utils.html import escape from django.utils.translation import gettext as _ - from django.views.decorators.http import require_GET from debug_toolbar._compat import login_not_required @@ -33,12 +33,15 @@ def render_panel(request): @require_GET def download_prof_file(request): file_path = request.GET.get("path") - print("Serving .prof file:", file_path) + print("Serving .prof file:", file_path) if not file_path or not os.path.exists(file_path): - print("File does not exist:", file_path) + print("File does not exist:", file_path) raise Http404("File not found.") - response = FileResponse(open(file_path, 'rb'), content_type='application/octet-stream') - response['Content-Disposition'] = f'attachment; filename="{os.path.basename(file_path)}"' + response = FileResponse( + open(file_path, "rb"), content_type="application/octet-stream" + ) + response["Content-Disposition"] = ( + f'attachment; filename="{os.path.basename(file_path)}"' + ) return response -