Skip to content

Commit 6d2bd4b

Browse files
authored
Update main.py
1 parent 6bca1a0 commit 6d2bd4b

1 file changed

Lines changed: 28 additions & 1 deletion

File tree

‎app/main.py‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from contextlib import asynccontextmanager
1313
from pathlib import Path
1414

15-
from fastapi import FastAPI, Request
15+
from fastapi import FastAPI, Request, Response
1616
from fastapi.middleware.cors import CORSMiddleware
1717
from fastapi.responses import JSONResponse
1818
from fastapi.staticfiles import StaticFiles
@@ -22,6 +22,7 @@
2222
from .infra.config import get_settings
2323
from .infra.db import get_engine
2424
from .infra.logging import configure_logging, get_logger, reset_request_id, set_request_id
25+
from .infra.metrics import get_registry, normalize_method
2526
from .routes import account, auth, billing, conversations, secrets
2627

2728
_log = get_logger("request")
@@ -34,6 +35,10 @@ async def lifespan(app: FastAPI):
3435
controller.reconcile_orphaned_runs() # fail runs stranded by a restart
3536
controller.reap_idle_sandboxes()
3637
yield
38+
# graceful shutdown: stop taking new runs and let in-flight ones
39+
# finish (bounded), so a deploy does not kill short runs mid-flight.
40+
# Whatever does not finish in time is reconciled on the next start.
41+
controller.drain(timeout=get_settings().shutdown_drain_seconds)
3742

3843

3944
def create_app() -> FastAPI:
@@ -66,6 +71,22 @@ async def _request_context(request: Request, call_next):
6671
)
6772
dur_ms = (time.perf_counter() - start) * 1000
6873
response.headers["X-Request-ID"] = rid
74+
# metrics: count by method+status and record latency. The
75+
# method is normalized to a bounded set so arbitrary/garbage
76+
# verbs cannot create unbounded metric series.
77+
registry = get_registry()
78+
method = normalize_method(request.method)
79+
registry.counter(
80+
"paw_http_requests_total",
81+
labels={"method": method, "status": str(response.status_code)},
82+
help="HTTP requests by method and status.",
83+
)
84+
registry.observe(
85+
"paw_http_request_duration_seconds",
86+
dur_ms / 1000.0,
87+
labels={"method": method},
88+
help="HTTP request latency in seconds.",
89+
)
6990
_log.info(
7091
"%s %s -> %s %.1fms",
7192
request.method,
@@ -112,6 +133,12 @@ def _domain_error(_request: Request, exc: DomainError) -> JSONResponse:
112133
def healthz() -> dict:
113134
return {"ok": True, "runner": settings.runner}
114135

136+
@app.get("/metrics")
137+
def metrics() -> Response:
138+
"""Prometheus text exposition of process metrics (runs, tokens,
139+
HTTP, span durations). Scrape target for a monitoring stack."""
140+
return Response(get_registry().render(), media_type="text/plain; version=0.0.4")
141+
115142
static_dir = Path(__file__).parent / "views"
116143
if static_dir.is_dir():
117144
app.mount("/", StaticFiles(directory=static_dir, html=True), name="ui")

0 commit comments

Comments
 (0)