From a8f705d6df196ef0eaeb8b9f5b9665006920b43b Mon Sep 17 00:00:00 2001 From: Sam <78538841+spwoodcock@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:38:01 +0800 Subject: [PATCH] feat: add profiler to calculate route execution time (#1020) * build: add pyinstrument to debug dep group * feat: add route profiler in debug mode --- src/backend/app/main.py | 47 ++++++++++++++++++++++++++------------ src/backend/pdm.lock | 31 ++++++++++++++++++++++++- src/backend/pyproject.toml | 1 + 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/backend/app/main.py b/src/backend/app/main.py index a1cab84f27..852f6f71fc 100644 --- a/src/backend/app/main.py +++ b/src/backend/app/main.py @@ -26,22 +26,23 @@ from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse, JSONResponse, RedirectResponse +from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse from loguru import logger as log from osm_fieldwork.xlsforms import xlsforms_path -from .__version__ import __version__ -from .auth import auth_routes -from .central import central_routes -from .config import settings -from .db.database import get_db -from .organization import organization_routes -from .projects import project_routes -from .projects.project_crud import read_xlsforms -from .submission import submission_routes -from .tasks import tasks_routes -from .users import user_routes - +from app.__version__ import __version__ +from app.auth import auth_routes +from app.central import central_routes +from app.config import settings +from app.db.database import get_db +from app.organization import organization_routes +from app.projects import project_routes +from app.projects.project_crud import read_xlsforms +from app.submission import submission_routes +from app.tasks import tasks_routes +from app.users import user_routes + +# Add sentry tracing only in prod if not settings.DEBUG: sentry_sdk.init( dsn=settings.SENTRY_DSN, @@ -51,7 +52,7 @@ @asynccontextmanager async def lifespan(app: FastAPI): - # Startup events + """Startup events.""" log.debug("Starting up FastAPI server.") log.debug("Reading XLSForms from DB.") await read_xlsforms(next(get_db()), xlsforms_path) @@ -165,6 +166,24 @@ def get_logger(): api = get_application() +# Add endpoint profiler to check for bottlenecks +if settings.DEBUG: + from pyinstrument import Profiler + + @api.middleware("http") + async def profile_request(request: Request, call_next): + """Calculate the execution time for routes.""" + profiling = request.query_params.get("profile", False) + if profiling: + profiler = Profiler(interval=0.001, async_mode="enabled") + profiler.start() + await call_next(request) + profiler.stop() + return HTMLResponse(profiler.output_html()) + else: + return await call_next(request) + + @api.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): """Exception handler for more descriptive logging.""" diff --git a/src/backend/pdm.lock b/src/backend/pdm.lock index 4ec0a2731a..43a1697617 100644 --- a/src/backend/pdm.lock +++ b/src/backend/pdm.lock @@ -6,7 +6,7 @@ groups = ["default", "debug", "dev", "docs", "test"] cross_platform = true static_urls = false lock_version = "4.3" -content_hash = "sha256:23547250f87f2c18c99b5f09aded65d3591631198321c15c91cf966ce41a5b62" +content_hash = "sha256:af0c495aa9c9fa4fdb0e99e8a27e1b0d34eb131f9b68c6ad3bf7b01601b61e0b" [[package]] name = "annotated-types" @@ -1377,6 +1377,35 @@ files = [ {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] +[[package]] +name = "pyinstrument" +version = "4.6.1" +requires_python = ">=3.7" +summary = "Call stack profiler for Python. Shows you why your code is slow!" +files = [ + {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73476e4bc6e467ac1b2c3c0dd1f0b71c9061d4de14626676adfdfbb14aa342b4"}, + {file = "pyinstrument-4.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4d1da8efd974cf9df52ee03edaee2d3875105ddd00de35aa542760f7c612bdf7"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507be1ee2f2b0c9fba74d622a272640dd6d1b0c9ec3388b2cdeb97ad1e77125f"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cee6de08eb45754ef4f602ce52b640d1c535d934a6a8733a974daa095def37"}, + {file = "pyinstrument-4.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7873e8cec92321251fdf894a72b3c78f4c5c20afdd1fef0baf9042ec843bb04"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a242f6cac40bc83e1f3002b6b53681846dfba007f366971db0bf21e02dbb1903"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:97c9660cdb4bd2a43cf4f3ab52cffd22f3ac9a748d913b750178fb34e5e39e64"}, + {file = "pyinstrument-4.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e304cd0723e2b18ada5e63c187abf6d777949454c734f5974d64a0865859f0f4"}, + {file = "pyinstrument-4.6.1-cp310-cp310-win32.whl", hash = "sha256:cee21a2d78187dd8a80f72f5d0f1ddb767b2d9800f8bb4d94b6d11f217c22cdb"}, + {file = "pyinstrument-4.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2000712f71d693fed2f8a1c1638d37b7919124f367b37976d07128d49f1445eb"}, + {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a366c6f3dfb11f1739bdc1dee75a01c1563ad0bf4047071e5e77598087df457f"}, + {file = "pyinstrument-4.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6be327be65d934796558aa9cb0f75ce62ebd207d49ad1854610c97b0579ad47"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e160d9c5d20d3e4ef82269e4e8b246ff09bdf37af5fb8cb8ccca97936d95ad6"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ffbf56605ef21c2fcb60de2fa74ff81f417d8be0c5002a407e414d6ef6dee43"}, + {file = "pyinstrument-4.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92cc4924596d6e8f30a16182bbe90893b1572d847ae12652f72b34a9a17c24a"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f4b48a94d938cae981f6948d9ec603bab2087b178d2095d042d5a48aabaecaab"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7a386392275bdef4a1849712dc5b74f0023483fca14ef93d0ca27d453548982"}, + {file = "pyinstrument-4.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:871b131b83e9b1122f2325061c68ed1e861eebcb568c934d2fb193652f077f77"}, + {file = "pyinstrument-4.6.1-cp311-cp311-win32.whl", hash = "sha256:8d8515156dd91f5652d13b5fcc87e634f8fe1c07b68d1d0840348cdd50bf5ace"}, + {file = "pyinstrument-4.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb868fbe089036e9f32525a249f4c78b8dc46967612393f204b8234f439c9cc4"}, + {file = "pyinstrument-4.6.1.tar.gz", hash = "sha256:f4731b27121350f5a983d358d2272fe3df2f538aed058f57217eef7801a89288"}, +] + [[package]] name = "pymbtiles" version = "0.5.0" diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 322fcb68fc..fb834361be 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -75,6 +75,7 @@ test = [ debug = [ "ipdb>=0.13.13", "debugpy>=1.6.7.post1", + "pyinstrument>=4.6.1", ] docs = [ "mkdocs>=1.5.2",