diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03ffbc9ec..77f6dba1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,7 @@ repos: - types-lxml - cryptography - types-croniter + - pyinstrument - repo: local hooks: - id: pylint diff --git a/backend/capellacollab/__main__.py b/backend/capellacollab/__main__.py index 2437839ce..0531d3297 100644 --- a/backend/capellacollab/__main__.py +++ b/backend/capellacollab/__main__.py @@ -93,6 +93,22 @@ async def shutdown(): ) +if config.logging.profiling: + import pyinstrument + + @app.middleware("http") + async def profile_request(request: fastapi.Request, call_next): + profiling = request.query_params.get("profile", False) + if profiling: + profiler = pyinstrument.Profiler(async_mode="enabled") + profiler.start() + await call_next(request) + profiler.stop() + return responses.HTMLResponse(profiler.output_html()) + else: + return await call_next(request) + + @app.get( "/docs", response_class=responses.RedirectResponse, include_in_schema=False ) diff --git a/backend/capellacollab/config/models.py b/backend/capellacollab/config/models.py index 95424f8ea..983b9756e 100644 --- a/backend/capellacollab/config/models.py +++ b/backend/capellacollab/config/models.py @@ -329,6 +329,10 @@ class LoggingConfig(BaseConfig): description="The path to the log file (saved as 'backend.log').", examples=["logs/"], ) + profiling: bool = pydantic.Field( + default=False, + description="Enable profiling of requests.", + ) class RequestsConfig(BaseConfig): diff --git a/backend/pyproject.toml b/backend/pyproject.toml index d5ab33f62..dcf2abc8f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -71,6 +71,7 @@ dev = [ "aioresponses", "types-lxml", "types-croniter", + "pyinstrument", ] [tool.black] diff --git a/docs/docs/development/backend/profiling.md b/docs/docs/development/backend/profiling.md new file mode 100644 index 000000000..8681c0383 --- /dev/null +++ b/docs/docs/development/backend/profiling.md @@ -0,0 +1,38 @@ + + +# Profile Backend Routes + +Profiling can be useful if you want to examine the performance of specific +backend routes. + +## Enable profiling + +In the `config.yaml`, set the `logging.profiling` key to `True`. Then, call the +route you want to profile with the `profile` query parameter set to `True`. + +For example, to profile the `/api/v1/metadata` route, you would call +`/api/v1/metadata?profile=True` in the browser. It will return a HTML report. + +!!! info + + Synchronous routes are not supported properly. If you want to profile a + synchronous route, add `async` to the route definition. + + ``` + @router.get(...) + def metadata(): + ... + ``` + + becomes + + ``` + @router.get(...) + async def metadata(): + ... + ``` + + [More Information on GitHub](https://github.com/joerick/pyinstrument/issues/257) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 53fa5662b..4f9dc42da 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -111,6 +111,7 @@ nav: - Introduction: development/index.md - Backend: - Code Style: development/backend/code-style.md + - Profiling: development/backend/profiling.md - Technology Overview: development/backend/technology.md - Extension Modules: development/backend/extensions.md - Exception Handling: development/backend/exception.md