Skip to content

Commit

Permalink
Add Option to configure renderers in Django settings [follow-on to #158
Browse files Browse the repository at this point in the history
…] (#163)

* Add Option to configure renderers in Django settings

* Run precommit hook on all files

* Incorporate the review feedback

- Add output_file_extension attribute to the Renderer class
- Use PYINSTRUMENT_PROFILE_DIR_RENDERER setting variable

* Update pyinstrument/middleware.py

* Remove exit condition and use html renderer when render is missing

* Tidy up, raise error on misconfig

* pre-commit fixes

Co-authored-by: Kracekumar <me@kracekumar.com>
Co-authored-by: pre-commit <pre-commit@example.com>
  • Loading branch information
3 people authored Nov 4, 2021
1 parent e8d2340 commit b72f089
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 5 deletions.
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
36 changes: 31 additions & 5 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,23 @@
MiddlewareMixin = object


def get_renderer(path) -> Renderer:
"""Return the renderer instance."""
if path:
try:
renderer = import_string(path)()
except ImportError as exc:
print("Unable to import the class: %s" % path)
raise exc

if not isinstance(renderer, Renderer):
raise ValueError(f"Renderer should subclass: {Renderer}")

return renderer
else:
return HTMLRenderer()


class ProfilerMiddleware(MiddlewareMixin): # type: ignore
def process_request(self, request):
profile_dir = getattr(settings, "PYINSTRUMENT_PROFILE_DIR", None)
Expand All @@ -41,8 +59,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)
renderer = get_renderer(configured_renderer)

output = renderer.render(profile_session)

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

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

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

file_path = os.path.join(profile_dir, filename)
Expand All @@ -67,10 +88,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"
"""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

0 comments on commit b72f089

Please sign in to comment.