fastapi_profiler

repository·main·Indexed 18 days ago

https://github.com/sunhailin-leo/fastapi_profiler

A FastAPI middleware wrapping pyinstrument to monitor service performance. Version 1.5.0 features include sampling, slow-request thresholds, structured logging, and a built-in Web UI dashboard for real-time monitoring and runtime configuration. It supports multiple output formats including text, HTML, prof, JSON, and speedscope, and allows for automatic profiling of 5xx errors regardless of sampling rates.

Tokens
4.3K
Snippets
11
Records
20
Agent score
62%

What's inside fastapi_profiler

  1. Control profiling with sampling rate and error thresholds

    main

    You can reduce overhead in production by using profiler_sample_rate (a float between 0.0 and 1.0). You can also ensure critical failures are always captured by setting always_profile_errors=True, which forces profiling on 5xx responses even if they fall outside the sampling rate or slow-request threshold.

    # Profile only 10% of requests
    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        server_app=app,
        profiler_sample_rate=0.1,
    )
    
    # Profile nothing normally, but always profile 5xx errors
    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        server_app=app,
        profiler_sample_rate=0.0,
        always_profile_errors=True,
    )
  2. Quick Start with PyInstrumentProfilerMiddleware

    main

    To get started, add PyInstrumentProfilerMiddleware to your FastAPI application using app.add_middleware(). By default, it will print profile summaries to stdout for every request.

    import uvicorn
    from fastapi import FastAPI
    from fastapi.responses import JSONResponse
    from fastapi_profiler import PyInstrumentProfilerMiddleware
    
    app = FastAPI()
    app.add_middleware(PyInstrumentProfilerMiddleware)
    
    @app.get("/test")
    async def normal_request():
        return JSONResponse({"retMsg": "Hello World!"})
    
    if __name__ == "__main__":
        uvicorn.run(app=app, host="0.0.0.0", port=8080, workers=1)
  3. Use the built-in Web UI Dashboard

    main

    Enable the dashboard by setting enable_dashboard=True. The dashboard provides a UI for viewing stats and APIs for runtime control. It is recommended to add the dashboard_path to filter_paths to avoid profiling the dashboard itself.

    Dashboard API Endpoints:

    • GET /{dashboard_path}/: HTML dashboard UI
    • GET /{dashboard_path}/stats: JSON stats for all routes
    • POST /{dashboard_path}/reset: Clear all collected stats
    • POST /{dashboard_path}/config: Update runtime configuration

    Example Configuration:

    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        enable_dashboard=True,
        dashboard_path="/__profiler__",
        filter_paths=["/__profiler__"],
    )
    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        enable_dashboard=True,
        dashboard_path="/__profiler__",
        filter_paths=["/__profiler__"],
    )
  4. Update profiler configuration at runtime

    main

    You can change the profiler's behavior (e.g., enabling/disabling it or changing the sampling rate) without restarting the server by sending a POST request to the /{dashboard_path}/config endpoint with a JSON body.

    # Disable profiling
    curl -X POST http://localhost:8080/__profiler__/config \
         -H "Content-Type: application/json" \
         -d '{"enabled": false}'
    
    # Re-enable with 50% sampling
    curl -X POST http://localhost:8080/__profiler__/config \
         -H "Content-Type: application/json" \
         -d '{"enabled": true, "sample_rate": 0.5}'
  5. Configure PyInstrumentProfilerMiddleware parameters

    main

    All configuration parameters are passed as keyword arguments to add_middleware().

    Core Parameters

    ParameterTypeDefaultDescription
    server_appFastAPI | NoneNonePass the FastAPI app instance to register a shutdown handler that writes file-based output automatically. Required for html, prof, json, speedscope output types.
    profiler_output_typestr"text"Output format. One of "text", "html", "prof", "json", "speedscope".
    is_print_each_requestboolTruePrint/log the profile summary after every request.
    profiler_intervalfloat0.0001pyinstrument sampling interval in seconds.
    async_modestr"enabled"pyinstrument async mode.
    html_file_namestr | None"./fastapi-profiler.html"Output file name for html type.
    prof_file_namestr | None"./fastapi-profiler.prof"Output file name for prof, json, and speedscope types.
    open_in_browserboolFalseAutomatically open the HTML report in a browser on shutdown.
    filter_pathslist[str] | NoneNoneList of URL path prefixes to skip profiling entirely (e.g. ["/health", "/metrics"]).

    1.5.0 New Parameters|

    ParameterTypeDefaultDescription
    slow_request_threshold_msfloat0Only emit profile output when request duration exceeds this value in milliseconds. 0 means always emit.
    profiler_sample_ratefloat1.0Fraction of requests to profile (0.01.0).
    always_profile_errorsboolTrueAlways profile 5xx responses regardless of profiler_sample_rate or slow_request_threshold_ms.
    log_formatstr"text"Log format for request lines. "text" (human-readable) or "json" (structured).
    max_profiles_per_routeint10Maximum number of ProfileRecord objects to keep in memory per route (rolling window).
    enable_dashboardboolFalseMount a built-in Web UI Dashboard with stats and runtime control APIs.
    dashboard_pathstr"/__profiler__"URL prefix for the dashboard.
    enabledboolTrueMaster switch. Controllable at runtime via the /config API.
  6. How sampling and error profiling work together

    main

    The middleware uses a combination of profiler_sample_rate, slow_request_threshold_ms, and always_profile_errors to decide when to generate a profile:

    1. Sampling: A request is sampled if a random roll is less than profiler_sample_rate.
    2. Error Capture: If always_profile_errors is True (default), any request resulting in a 5xx status code is profiled, even if it was not selected by the sampling rate.
    3. Slow Requests: If a request is sampled, it will only emit the profile output if its duration exceeds slow_request_threshold_ms.

    Logic Summary: A profile is emitted if:

    • (The request was sampled AND it exceeds the slow threshold)
    • OR (The request is an error AND always_profile_errors is True).
  7. Enable the built-in Web UI Dashboard

    main

    The middleware can mount a lightweight HTML dashboard to view request statistics and toggle profiling settings at runtime.

    To enable it, set enable_dashboard=True and you must provide the server_app argument (your FastAPI/Starlette instance). The dashboard will be available at the path specified by dashboard_path (default: "/__profiler__").

    Through the dashboard, you can:

    • Toggle the enabled master switch.
    • Adjust sample_rate.
    • Adjust slow_request_threshold_ms.
    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        server_app=app,
        enable_dashboard=True,
        dashboard_path="/my-profiler-ui"
    )
  8. Output profiles to an HTML file

    main

    To save the latest call-tree profile to an HTML file, set profiler_output_type="html". Note that server_app must be passed to enable automatic file writing on shutdown. The file is overwritten by each qualifying request, so it always contains the most recent profile.

    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        server_app=app,
        profiler_output_type="html",
        is_print_each_request=False,
        html_file_name="./fastapi-profiler.html",
    )
  9. Use PyInstrumentProfilerMiddleware to profile FastAPI applications

    main

    The PyInstrumentProfilerMiddleware is an ASGI middleware that profiles HTTP requests using pyinstrument (or cProfile if profiler_output_type is set to "prof"). It can be integrated into a FastAPI or Starlette application to monitor performance, capture slow requests, and automatically profile errors.

    To use the built-in Web UI dashboard, you must provide the server_app argument (typically your FastAPI instance) so the middleware can register shutdown handlers and mount the dashboard router.

    from fastapi import FastAPI
    from fastapi_profiler import PyInstrumentProfilerMiddleware
    
    app = FastAPI()
    app.add_middleware(
        PyInstrumentProfilerMiddleware,
        server_app=app,  # Required for dashboard and file-based output shutdown hooks
        enable_dashboard=True,
        profiler_output_type="html"
    )
  10. Create a dashboard router with `create_dashboard_router`

    main

    The create_dashboard_router function initializes a Starlette Router with the following endpoints:

    MethodPathDescription
    GET/Renders the HTML Dashboard UI
    GET/statsReturns JSON profiling statistics
    POST/resetResets all collected profiling statistics
    POST/configUpdates runtime configuration (sampling rate, threshold, etc.)

    Parameters

    • stats_collector: An object with an await get_all_stats() method and an await reset() method.
    • get_enabled: A Callable[[], bool] to check if profiling is active.
    • set_enabled: A Callable[[bool], None] to toggle profiling.
    • get_config: A Callable[[], dict] to retrieve current configuration.
    • set_config: A Callable[[dict], None] to apply configuration updates.
  11. Mount the FastAPI Profiler Web UI Dashboard

    main

    You can expose a built-in Web UI and JSON API to visualize profiling statistics and manage runtime configuration by using create_dashboard_router. This function returns a Starlette Router that you can mount onto your existing FastAPI or Starlette application.

    To use it, you must provide callback functions that allow the dashboard to interact with your profiler's state (stats collection, enabled status, and configuration).

    from fastapi import FastAPI
    from fastapi_profiler.dashboard import create_dashboard_router
    
    app = FastAPI()
    
    # Example setup (actual implementation depends on your profiler instance)
    app.mount("/profiler", create_dashboard_router(
        stats_collector=my_stats_collector,
        get_enabled=lambda: profiler.is_enabled(),
        set_enabled=lambda val: profiler.set_enabled(val),
        get_config=lambda: profiler.config,
        set_config=lambda cfg: profiler.update_config(cfg)
    ))