feat(warmup): per-module canary records (thread + timing observability)
Adds a canary record for each module submitted to the warmup, tracking: canary_id, module, thread_name, thread_id, submit_ts, start_ts, end_ts, elapsed_ms, status, error. Surface: - WarmupManager.canaries() returns list[dict] (defensive copy) - AppController.warmup_canaries() returns list[dict] (delegation) - GET /api/warmup_canaries Hook API endpoint - ApiHookClient.get_warmup_canaries() returns list[dict] Example: the warmup of google.genai records a 1187ms canary on thread controller-io_0 with thread_id 50420, canary_id 1. 11 new tests (8 unit in test_warmup_canaries + 3 in test_api_hooks_warmup). All pass; live_gui smoke test confirms endpoint returns real data.
This commit is contained in:
@@ -84,3 +84,42 @@ def test_live_warmup_wait_endpoint_completes(live_gui) -> None:
|
||||
# In a live session the warmup either already finished (no pending) or
|
||||
# completed within the 2s window. Either way the response is well-formed.
|
||||
assert isinstance(result["pending"], list)
|
||||
|
||||
def test_get_warmup_canaries_calls_correct_endpoint() -> None:
|
||||
"""get_warmup_canaries() hits GET /api/warmup_canaries and unwraps the list."""
|
||||
client = ApiHookClient()
|
||||
with patch.object(client, "_make_request") as mock_make:
|
||||
mock_make.return_value = {"canaries": [{"canary_id": 1, "module": "os", "thread_name": "controller-io-0", "thread_id": 12345, "status": "completed"}]}
|
||||
result = client.get_warmup_canaries()
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0]["module"] == "os"
|
||||
assert result[0]["status"] == "completed"
|
||||
mock_make.assert_called_once_with("GET", "/api/warmup_canaries")
|
||||
|
||||
|
||||
def test_get_warmup_canaries_handles_empty_response() -> None:
|
||||
"""get_warmup_canaries() returns [] when server returns None or empty payload."""
|
||||
client = ApiHookClient()
|
||||
with patch.object(client, "_make_request") as mock_make:
|
||||
mock_make.return_value = None
|
||||
result = client.get_warmup_canaries()
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_live_warmup_canaries_endpoint(live_gui) -> None:
|
||||
"""Live: GET /api/warmup_canaries returns canary records with thread + status info."""
|
||||
client = ApiHookClient()
|
||||
assert client.wait_for_server(timeout=10)
|
||||
canaries = client.get_warmup_canaries()
|
||||
assert isinstance(canaries, list)
|
||||
assert len(canaries) >= 1, "expected at least one canary record from live warmup"
|
||||
for c in canaries:
|
||||
assert "canary_id" in c
|
||||
assert "module" in c
|
||||
assert "thread_name" in c
|
||||
assert "thread_id" in c
|
||||
assert "status" in c
|
||||
assert c["status"] in ("running", "completed", "failed", "cancelled")
|
||||
completed = [c for c in canaries if c["status"] == "completed"]
|
||||
assert len(completed) >= 1, "at least one canary should be completed"
|
||||
|
||||
Reference in New Issue
Block a user