Skip to content
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

Add Option to configure renderers in Django settings #158

Closed
wants to merge 5 commits into from
Closed
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
4 changes: 4 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def custom_show_pyinstrument(request):
PYINSTRUMENT_SHOW_CALLBACK = "%s.custom_show_pyinstrument" % __name__
```

You can configure the profile output type using setting's variable `PYINSTRUMENT_PROFILE_DIR_RENDERER`.
Default value is `pyinstrument.renderers.HTMLRenderer`. The supported renderers are
`pyinstrument.renderers.JSONRenderer`, `pyinstrument.renderers.HTMLRenderer`.

### Profile a web request in Flask

A simple setup to profile a Flask application is the following:
Expand Down
41 changes: 33 additions & 8 deletions pyinstrument/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.utils.module_loading import import_string

from pyinstrument import Profiler
from pyinstrument.renderers import Renderer
from pyinstrument.renderers.html import HTMLRenderer

try:
Expand All @@ -16,6 +17,25 @@
MiddlewareMixin = object


def get_renderer_and_extension(path):
"""Return the renderer instance and output file extension."""
if path:
try:
renderer = import_string(path)()
except ImportError as exc:
print("Unable to import the class: %s" % path)
raise exc

else:
renderer = HTMLRenderer()

if isinstance(renderer, Renderer):
return renderer, renderer.output_file_extension
else:
print("Renderer should subclass: %s. Using HTMLRenderer" % Renderer)
return HTMLRenderer(), HTMLRenderer.output_file_extension
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pyright was pointing out this can return None. So when the renderer is not a subclass of Renderer, we will be using HTMLRenderer. Let me know if this is fine?

Copy link
Owner

Choose a reason for hiding this comment

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

I think it would be better to raise an error, instead. I'll update it.

Copy link
Owner

Choose a reason for hiding this comment

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

I've made the update. For some reason I wasn't able to push to this branch. I've pushed it to 69b195b - if you could cherry-pick that here, I think we're good to go.



class ProfilerMiddleware(MiddlewareMixin): # type: ignore
def process_request(self, request):
profile_dir = getattr(settings, "PYINSTRUMENT_PROFILE_DIR", None)
Expand All @@ -41,8 +61,10 @@ def process_response(self, request, response):
if hasattr(request, "profiler"):
profile_session = request.profiler.stop()

renderer = HTMLRenderer()
output_html = renderer.render(profile_session)
configured_renderer = getattr(settings, "PYINSTRUMENT_PROFILE_DIR_RENDERER", None)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Keeping this here since this renderer is used in the if/else block to produce output.

renderer, ext = get_renderer_and_extension(configured_renderer)

output = renderer.render(profile_session)

profile_dir = getattr(settings, "PYINSTRUMENT_PROFILE_DIR", None)

Expand All @@ -55,10 +77,8 @@ def process_response(self, request, response):
path = path.replace("?", "_qs_")

if profile_dir:
filename = "{total_time:.3f}s {path} {timestamp:.0f}.html".format(
total_time=profile_session.duration,
path=path,
timestamp=time.time(),
filename = "{total_time:.3f}s {path} {timestamp:.0f}.{ext}".format(
total_time=profile_session.duration, path=path, timestamp=time.time(), ext=ext
)

file_path = os.path.join(profile_dir, filename)
Expand All @@ -67,10 +87,15 @@ def process_response(self, request, response):
os.mkdir(profile_dir)

with open(file_path, "w", encoding="utf-8") as f:
f.write(output_html)
f.write(output)

if getattr(settings, "PYINSTRUMENT_URL_ARGUMENT", "profile") in request.GET:
return HttpResponse(output_html)
if isinstance(renderer, HTMLRenderer):
return HttpResponse(output)
else:
renderer = HTMLRenderer()
output = renderer.render(profile_session)
return HttpResponse(output)
else:
return response
else:
Expand Down
4 changes: 4 additions & 0 deletions pyinstrument/renderers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class Renderer:
Dictionary containing processor options, passed to each processor.
"""

output_file_extension: str = "txt"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added the output_file_extension attribute.

"""Renderer output file extension with out dot prefix. The default value is `txt`
"""

def __init__(
self,
show_all: bool = False,
Expand Down
2 changes: 2 additions & 0 deletions pyinstrument/renderers/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class HTMLRenderer(Renderer):
Renders a rich, interactive web page, as a string of HTML.
"""

output_file_extension = "html"

def __init__(self, **kwargs: Any):
super().__init__(**kwargs)

Expand Down
2 changes: 2 additions & 0 deletions pyinstrument/renderers/jsonrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class JSONRenderer(Renderer):
Outputs a tree of JSON, containing processed frames.
"""

output_file_extension = "json"

def __init__(self, **kwargs: Any):
super().__init__(**kwargs)

Expand Down