From b6652b6722840337838fa7498b8ccc3f8204befd Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 17 Jun 2026 03:22:10 -0700 Subject: [PATCH] [eric] backend: add per-lifespan boot timing to pin cold-start stalls - debug(sub_app.name) is a no-op in packaged builds, so the packaged backend.log had zero per-SubApp markers and a cold stall could only be guessed at - wrap each enter_async_context with time.perf_counter + a flushed [perf] print, plus a lifespans-total line, so a cold launch names the exact slow lifespan - logging only, no functional change; loop logic validated warm --- backend/config/Apps.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/config/Apps.py b/backend/config/Apps.py index be9a22cb..3ab5e12c 100644 --- a/backend/config/Apps.py +++ b/backend/config/Apps.py @@ -1,4 +1,5 @@ import os +import time from fastapi import FastAPI, APIRouter import debug @@ -29,9 +30,18 @@ class MainApp: @asynccontextmanager async def lifespan(app: FastAPI): async with AsyncExitStack() as stack: + # [perf] per-lifespan boot timing. debug() is a no-op in the + # packaged build, so without this the packaged backend.log has no + # per-SubApp markers and a cold-start stall can only be guessed at. + # One perf_counter + flushed print per app pins exactly which + # lifespan (or the cold first-touch I/O entering it) dominates. + _boot_t0 = time.perf_counter() for sub_app in sub_apps: debug(sub_app.name) + _t0 = time.perf_counter() await stack.enter_async_context(sub_app.lifespan()) + print(f"[perf] lifespan {sub_app.name} t={(time.perf_counter() - _t0) * 1000:.0f}ms", flush=True) + print(f"[perf] lifespans-total t={(time.perf_counter() - _boot_t0) * 1000:.0f}ms", flush=True) _port = os.environ.get("OPENSWARM_PORT", "8324") print(f"\nCheck out the API docs at: http://127.0.0.1:{_port}/docs\n") yield