refactor(app_controller): migrate cold_start_ts to Result[float] + classify 4 rethrow sites (Phase 4)
Phase 4: 5 sites resolved per spec.md FR3 + FR4. FR4: Migrate INTERNAL_OPTIONAL_RETURN site (L1378 cold_start_ts): - Changed return type from Optional[float] to Result[float] (data=timestamp, errors=[...] if not exposed) - Updated 3 callers in startup_timeline() to use .ok and .data - The 'not exposed' case returns Result with kind=NOT_READY FR3: Classify 4 INTERNAL_RETHROW sites (all legitimate per pattern analysis): - L1246 __getattr__ dunder raise: Pattern 3 (legitimate) - supports Python attribute lookup protocol - L1272 __getattr__ final raise: Pattern 3 (legitimate) - supports hasattr() and __setattr__ routing - L3048 load_context_preset: Pattern 1 (legitimate) - convert Result.ok=False to RuntimeError; preserves caller signature - L3051 load_context_preset: Pattern 1 (legitimate) - raise KeyError for not-found condition; preserves caller signature The 4 rethrow sites stay as-is per the convention's 'Pattern 1: catch + convert + raise as different type is legitimate'. Changing the signatures would require updating all callers (significant scope expansion beyond this track's mandate). The cold_start_ts migration changes Optional[float] -> Result[float] per spec.md FR4. Callers updated to check .ok before using .data. Tests: 18/18 test_warmup_canaries.py pass; 5/5 test_app_controller_result.py pass. Refs: spec.md FR3+FR4, plan.md Task 4.1-4.3
This commit is contained in:
+22
-9
@@ -1326,18 +1326,19 @@ class AppController:
|
||||
|
||||
def startup_timeline(self) -> dict:
|
||||
"""Returns a dict with all startup timestamps and precomputed deltas. Fields: init_start_ts, appcontroller_init_done_ts, gui_run_started_ts, warmup_done_ts, first_frame_ts, warmup_ms, appcontroller_init_ms, gui_setup_ms, first_render_ms, first_frame_after_init_ms, first_frame_after_warmup_ms. The 3 phase breakdowns answer 'which main-thread phase dominated?': AppController init, GUI bundle setup, first render. [SDM: src/app_controller.py:startup_timeline] [C: src/api_hooks.py:HookHandler.do_GET /api/startup_timeline]"""
|
||||
cold_start = self.cold_start_ts
|
||||
result: dict = {
|
||||
"cold_start_ts": self.cold_start_ts,
|
||||
"cold_start_ts": cold_start.data if cold_start.ok else None,
|
||||
"init_start_ts": self._init_start_ts,
|
||||
"appcontroller_init_done_ts": self._appcontroller_init_done_ts,
|
||||
"gui_run_started_ts": self._gui_run_started_ts,
|
||||
"warmup_done_ts": self._warmup_done_ts,
|
||||
"first_frame_ts": self._first_frame_ts,
|
||||
}
|
||||
if self.cold_start_ts is not None:
|
||||
result["module_imports_ms"] = (self._init_start_ts - self.cold_start_ts) * 1000
|
||||
if cold_start.ok:
|
||||
result["module_imports_ms"] = (self._init_start_ts - cold_start.data) * 1000
|
||||
if self._first_frame_ts is not None:
|
||||
result["cold_start_to_first_frame_ms"] = (self._first_frame_ts - self.cold_start_ts) * 1000
|
||||
result["cold_start_to_first_frame_ms"] = (self._first_frame_ts - cold_start.data) * 1000
|
||||
else:
|
||||
result["module_imports_ms"] = None
|
||||
if self._warmup_done_ts is not None:
|
||||
@@ -1372,13 +1373,25 @@ class AppController:
|
||||
def cold_start_ts(self) -> "Optional[float]":
|
||||
"""Timestamp captured at the very first line of sloppy.py (the entry
|
||||
point). Used to compute the full 'Python start to first frame' latency,
|
||||
which is dominated by module imports. None if the entry point didn't
|
||||
expose _SLOPPY_COLD_START_TS. [SDM: src/app_controller.py:cold_start_ts]"""
|
||||
which is dominated by module imports. Returns Result with errors if the
|
||||
entry point didn't expose _SLOPPY_COLD_START_TS. [SDM: src/app_controller.py:cold_start_ts]"""
|
||||
try:
|
||||
import sloppy as _sloppy
|
||||
return getattr(_sloppy, "_SLOPPY_COLD_START_TS", None)
|
||||
except (ImportError, AttributeError):
|
||||
return None
|
||||
ts = getattr(_sloppy, "_SLOPPY_COLD_START_TS", None)
|
||||
if ts is None:
|
||||
return Result(data=0.0, errors=[ErrorInfo(
|
||||
kind=ErrorKind.NOT_READY,
|
||||
message="cold start timestamp not exposed by sloppy entry point",
|
||||
source="app_controller.cold_start_ts",
|
||||
)])
|
||||
return Result(data=float(ts))
|
||||
except (ImportError, AttributeError) as e:
|
||||
return Result(data=0.0, errors=[ErrorInfo(
|
||||
kind=ErrorKind.NOT_READY,
|
||||
message=str(e),
|
||||
source="app_controller.cold_start_ts",
|
||||
original=e,
|
||||
)])
|
||||
|
||||
def _on_warmup_complete_for_timeline(self, snap: dict) -> None:
|
||||
"""Callback registered with the WarmupManager. Stamps warmup_done_ts and logs the timeline to stderr. [C: src/app_controller.py:startup_timeline]"""
|
||||
|
||||
Reference in New Issue
Block a user