monitor.server¶
MonitorServer — FastAPI web server for the otto monitoring dashboard.
Endpoints¶
GET / Serves the React dashboard’s built index.html (web/, via make web)
GET /static/… Serves the built dist/ assets (JS/CSS bundles, etc.)
GET /api/meta JSON metadata (host name, metric labels, units)
GET /api/data JSON snapshot of all metric series and events
GET /api/stream SSE stream — pushes metric updates and events in real time
POST /api/event Record a manual event from the dashboard UI
-
class otto.monitor.server.SuppressASGIWarning(name=
'')¶ Bases:
Filterlogging.Filterthat drops the uvicorn ASGI callable warning on shutdown.
-
class otto.monitor.server.MonitorServer(collector: MetricCollector, host: str =
'0.0.0.0', port: int =0)¶ Bases:
objectWraps a FastAPI/uvicorn server for the monitoring dashboard.
Call
await serve()from an async context to run the server. Callstop()from any thread to trigger a graceful shutdown, orforce_stop()to abort open SSE connections immediately instead of waiting for them to drain.- force_stop() None¶
Shut down without waiting for open connections to drain (thread-safe).
SSE dashboards hold /api/stream open indefinitely, so a graceful shutdown can wait forever. This sets uvicorn’s
force_exit(skip the drain) and aborts open connection transports on the server’s own loop (h11 never closes a mid-stream transport, so clients would otherwise not see the connection die). Used by test harnesses and Ctrl+C paths; preferstop()when clients should finish cleanly.The listening sockets are closed first, in the same loop callback as the abort. Aborting only a one-shot snapshot of
state.connectionsleft a race: uvicorn keeps accepting until its own shutdown closes the sockets (~one 0.1s tick later), so a connection established in that window — e.g. the fresh EventSource a browser opens right after a page reload — was never aborted, and an un-aborted mid-stream h11 SSE keeps the server task alive (asyncio.Server.wait_closed()waits on it on 3.12+), hanging shutdown indefinitely. Because an asyncio callback runs to completion before the loop can accept another connection, closing the sockets and aborting the live transports in one callback guarantees the snapshot is complete: nothing new can slip in between the two steps.