Skip to content

Commit

Permalink
add support for speedscope
Browse files Browse the repository at this point in the history
  • Loading branch information
sunhailin committed May 14, 2024
1 parent a31e140 commit 402676b
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 1 deletion.
31 changes: 31 additions & 0 deletions example/fastapi_to_speedscope_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
This example shows how to output the profile
to json file.
"""
import os
import uvicorn

from fastapi import FastAPI
from fastapi.responses import JSONResponse

from fastapi_profiler import PyInstrumentProfilerMiddleware

app = FastAPI()
app.add_middleware(
PyInstrumentProfilerMiddleware,
server_app=app,
profiler_output_type="speedscope",
prof_file_name="example_speedscope_profile.json"
)


@app.get("/test")
async def normal_request():
return JSONResponse({"retMsg": "Hello World!"})


# Or you can use the console with command "uvicorn" to run this example.
# Command: uvicorn fastapi_example:app --host="0.0.0.0" --port=8080
if __name__ == '__main__':
app_name = os.path.basename(__file__).replace(".py", "")
uvicorn.run(app=f"{app_name}:app", host="0.0.0.0", port=8080, workers=1)
17 changes: 16 additions & 1 deletion fastapi_profiler/profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from logging import getLogger

from pyinstrument import Profiler
from pyinstrument.renderers import HTMLRenderer, JSONRenderer
from pyinstrument.renderers import HTMLRenderer, JSONRenderer, SpeedscopeRenderer

from starlette.routing import Router
from starlette.requests import Request
Expand All @@ -21,6 +21,7 @@ class PyInstrumentProfilerMiddleware:
DEFAULT_HTML_FILENAME = "./fastapi-profiler.html"
DEFAULT_PROF_FILENAME = "./fastapi-profiler.prof"
DEFAULT_JSON_FILENAME = "./fastapi-profiler.json"
DEFAULT_SPEEDSCOPE_FILENAME = "./fastapi-profiler-speedscope.json"

def __init__(
self,
Expand Down Expand Up @@ -158,3 +159,17 @@ async def get_profiler_result(self):
with codecs.open(prof_file_name, "w", "utf-8") as f:
f.write(renderer.render(session=self._profiler.last_session))
logger.info("Done writing profile to %r", prof_file_name)
elif self._output_type == "speedscope":
prof_file_name = self.DEFAULT_SPEEDSCOPE_FILENAME
if self._prof_file_name is not None:
prof_file_name = self._prof_file_name

logger.info(
"Compiling and dumping final profile to %r - this may take some time",
prof_file_name,
)

renderer = SpeedscopeRenderer()
with codecs.open(prof_file_name, "w", "utf-8") as f:
f.write(renderer.render(session=self._profiler.last_session))
logger.info("Done writing profile to %r", prof_file_name)
38 changes: 38 additions & 0 deletions test/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,41 @@ def test_profiler_export_to_prof(self, test_middleware, tmpdir):
# Check if the .prof file has been created and has content
assert full_path.exists()
assert full_path.read_binary()

def test_profiler_export_to_json(self, test_middleware, tmpdir):
full_path = tmpdir / "test.json"

with TestClient(
test_middleware(
profiler_output_type="json",
is_print_each_request=False,
profiler_interval=0.0000001,
prof_file_name=str(full_path),
)
) as client:
# request
request_path = "/test"
client.get(request_path)

# Check if the .prof file has been created and has content
assert full_path.exists()
assert full_path.read_binary()

def test_profiler_export_to_speedscope(self, test_middleware, tmpdir):
full_path = tmpdir / "test_speedscope.json"

with TestClient(
test_middleware(
profiler_output_type="speedscope",
is_print_each_request=False,
profiler_interval=0.0000001,
prof_file_name=str(full_path),
)
) as client:
# request
request_path = "/test"
client.get(request_path)

# Check if the .prof file has been created and has content
assert full_path.exists()
assert full_path.read_binary()

0 comments on commit 402676b

Please sign in to comment.