Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions debug_toolbar/panels/profiling.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cProfile
import os
import tempfile
from colorsys import hsv_to_rgb
from pstats import Stats

Expand Down Expand Up @@ -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 = []
Expand All @@ -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}
)
15 changes: 14 additions & 1 deletion debug_toolbar/panels/sql/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions debug_toolbar/templates/debug_toolbar/panels/profiling.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{% load i18n %}

{% if prof_file_path %}
<div style="margin-bottom: 10px;">
<a href="{% url 'debug_toolbar_download_prof_file' %}?path={{ prof_file_path|urlencode }}" class="djDebugButton">
Download .prof file
</a>
</div>
{% endif %}

<table>
<thead>
<tr>
Expand Down
13 changes: 11 additions & 2 deletions debug_toolbar/urls.py
Original file line number Diff line number Diff line change
@@ -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",
),
]
22 changes: 21 additions & 1 deletion debug_toolbar/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Loading