Skip to content

Commit 48ac250

Browse files
Add .prof File Download Support to Profiling Panel
1 parent c217334 commit 48ac250

File tree

5 files changed

+59
-5
lines changed

5 files changed

+59
-5
lines changed

debug_toolbar/panels/profiling.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
from colorsys import hsv_to_rgb
44
from pstats import Stats
5+
import tempfile
56

67
from django.conf import settings
78
from django.utils.html import format_html
@@ -168,8 +169,11 @@ def generate_stats(self, request, response):
168169
self.stats = Stats(self.profiler)
169170
self.stats.calc_callees()
170171

171-
root_func = cProfile.label(super().process_request.__code__)
172+
prof_file_path = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names()) + ".prof")
173+
self.profiler.dump_stats(prof_file_path)
174+
self.prof_file_path = prof_file_path
172175

176+
root_func = cProfile.label(super().process_request.__code__)
173177
if root_func in self.stats.stats:
174178
root = FunctionCall(self.stats, root_func, depth=0)
175179
func_list = []
@@ -182,4 +186,9 @@ def generate_stats(self, request, response):
182186
dt_settings.get_config()["PROFILER_MAX_DEPTH"],
183187
cum_time_threshold,
184188
)
185-
self.record_stats({"func_list": func_list})
189+
self.record_stats({
190+
"func_list": func_list,
191+
"prof_file_path": self.prof_file_path
192+
})
193+
194+

debug_toolbar/panels/sql/tracking.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,16 @@ def _last_executed_query(self, sql, params):
142142
# process during the .last_executed_query() call.
143143
self.db._djdt_logger = None
144144
try:
145-
return self.db.ops.last_executed_query(self.cursor, sql, params)
145+
# Handle executemany: take the first set of parameters for formatting
146+
if isinstance(params, (list, tuple)) and len(params) > 0 and isinstance(params[0], (list, tuple)):
147+
sample_params = params[0]
148+
else:
149+
sample_params = params
150+
151+
try:
152+
return self.db.ops.last_executed_query(self.cursor, sql, sample_params)
153+
except Exception:
154+
return sql
146155
finally:
147156
self.db._djdt_logger = self.logger
148157

debug_toolbar/templates/debug_toolbar/panels/profiling.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
{% load i18n %}
2+
3+
{% if prof_file_path %}
4+
<div style="margin-bottom: 10px;">
5+
<a href="{% url 'debug_toolbar_download_prof_file' %}?path={{ prof_file_path|urlencode }}" class="djDebugButton">
6+
Download .prof file
7+
</a>
8+
</div>
9+
{% endif %}
10+
211
<table>
312
<thead>
413
<tr>

debug_toolbar/urls.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
from django.urls import path
12
from debug_toolbar import APP_NAME
3+
from debug_toolbar import views as debug_toolbar_views
24
from debug_toolbar.toolbar import DebugToolbar
5+
from debug_toolbar import APP_NAME
36

47
app_name = APP_NAME
5-
urlpatterns = DebugToolbar.get_urls()
8+
9+
urlpatterns = DebugToolbar.get_urls() + [
10+
path(
11+
"download_prof_file/",
12+
debug_toolbar_views.download_prof_file,
13+
name="debug_toolbar_download_prof_file"
14+
),
15+
]

debug_toolbar/views.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from django.http import JsonResponse
1+
import os
2+
from django.http import JsonResponse, FileResponse, Http404
23
from django.utils.html import escape
34
from django.utils.translation import gettext as _
45

6+
from django.views.decorators.http import require_GET
7+
58
from debug_toolbar._compat import login_not_required
69
from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
710
from debug_toolbar.toolbar import DebugToolbar
@@ -25,3 +28,17 @@ def render_panel(request):
2528
content = panel.content
2629
scripts = panel.scripts
2730
return JsonResponse({"content": content, "scripts": scripts})
31+
32+
33+
@require_GET
34+
def download_prof_file(request):
35+
file_path = request.GET.get("path")
36+
print("Serving .prof file:", file_path)
37+
if not file_path or not os.path.exists(file_path):
38+
print("File does not exist:", file_path)
39+
raise Http404("File not found.")
40+
41+
response = FileResponse(open(file_path, 'rb'), content_type='application/octet-stream')
42+
response['Content-Disposition'] = f'attachment; filename="{os.path.basename(file_path)}"'
43+
return response
44+

0 commit comments

Comments
 (0)