Skip to content

Add .prof File Download Support to Profiling Panel #2146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

andoriyaprashant
Copy link
Member

Description

This PR adds a feature to download the raw .prof file generated by cProfile via the Django Debug Toolbar profiling panel.

Users can now export the profiling results to visualize or analyze them externally.

  • Added download_prof_file view to serve .prof files
  • Updated profiling.html to include a download button
  • Included prof_file_path in the panel stats to pass to the template

Status: Draft - encountering a NoReverseMatch error related to debug_toolbar_download_prof_file. Likely an issue with urls.py configuration. Seeking guidance on the correct integration approach.

Fixes #1798

Checklist:

  • I have added the relevant tests for this change.
  • I have added an item to the Pending section of docs/changes.rst.

@andoriyaprashant
Copy link
Member Author

Hey @matthiask @tim-schilling

I’ve started working on the enhancement Download profiling results (Issue #1798) and have implemented the backend and template changes to support .prof file download. However, I’m currently getting the following error:

django.urls.exceptions.NoReverseMatch: Reverse for 'debug_toolbar_download_prof_file' not found.

I’ve defined the download_prof_file view in views.py, but I suspect the issue might be with how I registered it in urls.py or the namespace being used by the toolbar.

Since this involves integrating a custom view into the debug toolbar's internal URLs, I’ve kept the PR in draft so you could please review my approach and suggest how I can resolve this.

Thank you!

Copy link
Member

@matthiask matthiask left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain more what we can do with the .prof file?

Also, I wonder if we shouldn't just write profile files always to a configured path (in the settings) and not make this a part of the interface? (Not saying that's a better idea, just thinking out loud.)

@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_candidate_names() isn't documented? I think we should only enable this feature when users provide a path where we can store profiles, because we have to ensure we're only serving files from this very path and not arbitrary files.

try:
return self.db.ops.last_executed_query(self.cursor, sql, sample_params)
except Exception:
return sql
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an unrelated change?

@@ -1,4 +1,13 @@
{% load i18n %}

{% if prof_file_path %}
<div style="margin-bottom: 10px;">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not have any inline styles in templates until now, if we need styles add them to the toolbar.css file.

debug_toolbar_views.download_prof_file,
name="debug_toolbar_download_prof_file",
),
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add the URL pattern to get_urls(), not here.


{% if prof_file_path %}
<div style="margin-bottom: 10px;">
<a href="{% url 'debug_toolbar_download_prof_file' %}?path={{ prof_file_path|urlencode }}" class="djDebugButton">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The toolbar uses the djdt: namespace for URLs; search the code base for reverse( calls to see how that can be used. (See git grep -C3 'reverse(')

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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can trivially be exploited by using something like

...?path=/etc/passwd

This view serves arbitrary files which are readable by the webserver process: Either you have to use signing (django.core.signing, also used in the templates panel) to ensure that paths cannot be tampered with and/or use a known root where you put files, and ensure that you only serve files from this root. This isn't totally straightforward because you have to protect against path traversal attacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Download profiling results
2 participants