diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 4613a3cad..8ae1db0e9 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -1,5 +1,6 @@ import cProfile import os +import tempfile from colorsys import hsv_to_rgb from pstats import Stats @@ -168,8 +169,13 @@ 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 +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}) + 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..c2aff7609 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -142,7 +142,20 @@ 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..6559d4874 100644 --- a/debug_toolbar/urls.py +++ b/debug_toolbar/urls.py @@ -1,5 +1,14 @@ -from debug_toolbar import APP_NAME +from django.urls import path + +from debug_toolbar import APP_NAME, views as debug_toolbar_views from debug_toolbar.toolbar import DebugToolbar 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..e7c2ece66 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,6 +1,9 @@ -from django.http import JsonResponse +import os + +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 from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar @@ -25,3 +28,20 @@ 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