From fb21dec0ed0bff50db7248be626e38e819967ecf Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Sun, 5 Apr 2026 18:00:59 +0200 Subject: [PATCH] Expose queue stats via status endpoint --- docs/API.md | 12 +++++++-- docs/pro-runtime-hardening-workboard.md | 2 ++ internal/control/control.go | 3 +++ internal/control/control_test.go | 36 +++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 78e0122..bd51eb2 100644 --- a/docs/API.md +++ b/docs/API.md @@ -19,7 +19,7 @@ Health check. ### `GET /status` -Current transmitter status (read-only snapshot). +Current transmitter status (read-only snapshot). Runtime indicator, alert, and queue stats from the running TX controller are mirrored here for quick health checks. **Response:** ```json @@ -31,7 +31,15 @@ Current transmitter status (read-only snapshot). "rdsEnabled": true, "preEmphasisTauUS": 50, "limiterEnabled": true, - "fmModulationEnabled": true + "fmModulationEnabled": true, + "runtimeIndicator": "normal", + "runtimeAlert": "", + "queue": { + "capacity": 3, + "depth": 1, + "fillLevel": 0.33, + "health": "low" + } } ``` diff --git a/docs/pro-runtime-hardening-workboard.md b/docs/pro-runtime-hardening-workboard.md index 6854400..e2cd6af 100644 --- a/docs/pro-runtime-hardening-workboard.md +++ b/docs/pro-runtime-hardening-workboard.md @@ -250,6 +250,7 @@ Generator/Upsampler und Hardwarewriter werden als getrennte Stufen mit kleinem, | 2026-04-05 | Queue-Health-Indikator | `QueueStats.Health` gibt `critical`/`low`/`normal` zurück und `txBridge` leitet `EngineStats.Queue` ins `/runtime`-JSON. | | 2026-04-05 | Runtime-Indikator | `EngineStats.RuntimeIndicator` kombiniert `queue.health` + `lateBuffers`, `/runtime` zeigt `engine.runtimeIndicator`. | | 2026-04-05 | /status runtime indicator | `/status` reuses `txBridge.TXStats()` and now reports `runtimeIndicator` alongside the config snapshot for quick ops. | +| 2026-04-05 | /status queue stats | `/status` spiegelt das `queue`-Objekt aus `txBridge.TXStats()` für schnelle Queue-Checks, API-Doku und `TestStatusReportsQueueStats` fangen den neuen Key ab. | ## WS-01 Verifikation | Datum | Fokus | Ergebnis | @@ -259,6 +260,7 @@ Generator/Upsampler und Hardwarewriter werden als getrennte Stufen mit kleinem, | 2026-04-05 | Runtime-Indikator | OK `go test ./...` deckt `runtimeIndicator` sowie `/runtime`-Exposition von `engine.runtimeIndicator`. | | 2026-04-05 | Runtime API queue health | ✅ `/runtime` liefert jetzt `engine.queue.health` dank `txBridge.TXStats`. | | 2026-04-05 | /status runtime indicator | ✅ `/status` gibt jetzt `runtimeIndicator` aus (`control_test` deckt den neuen Key). | +| 2026-04-05 | /status queue stats | ✅ `TestStatusReportsQueueStats` plus `docs/API.md` zeigen, dass `queue` korrekt durchgereicht wird. | --- diff --git a/internal/control/control.go b/internal/control/control.go index 278ec6a..823a8af 100644 --- a/internal/control/control.go +++ b/internal/control/control.go @@ -140,6 +140,9 @@ func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { if alert, ok := stats["runtimeAlert"]; ok { status["runtimeAlert"] = alert } + if queue, ok := stats["queue"]; ok { + status["queue"] = queue + } } } diff --git a/internal/control/control_test.go b/internal/control/control_test.go index 1a70684..93fc508 100644 --- a/internal/control/control_test.go +++ b/internal/control/control_test.go @@ -9,6 +9,7 @@ import ( "testing" cfgpkg "github.com/jan/fm-rds-tx/internal/config" + "github.com/jan/fm-rds-tx/internal/output" ) func TestHealthz(t *testing.T) { @@ -55,6 +56,41 @@ func TestStatusReportsRuntimeIndicator(t *testing.T) { } } +func TestStatusReportsQueueStats(t *testing.T) { + cfg := cfgpkg.Default() + queueStats := output.QueueStats{ + Capacity: cfg.Runtime.FrameQueueCapacity, + Depth: 1, + FillLevel: 0.25, + Health: output.QueueHealthLow, + } + srv := NewServer(cfg) + srv.SetTXController(&fakeTXController{stats: map[string]any{"queue": queueStats}}) + rec := httptest.NewRecorder() + srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/status", nil)) + if rec.Code != 200 { + t.Fatalf("status: %d", rec.Code) + } + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("unmarshal queue stats: %v", err) + } + queueRaw, ok := body["queue"] + if !ok { + t.Fatalf("missing queue in status") + } + queueMap, ok := queueRaw.(map[string]any) + if !ok { + t.Fatalf("queue stats type mismatch: %T", queueRaw) + } + if queueMap["capacity"] != float64(queueStats.Capacity) { + t.Fatalf("queue capacity mismatch: want %v got %v", queueStats.Capacity, queueMap["capacity"]) + } + if queueMap["health"] != string(queueStats.Health) { + t.Fatalf("queue health mismatch: want %s got %v", queueStats.Health, queueMap["health"]) + } +} + func TestDryRunEndpoint(t *testing.T) { srv := NewServer(cfgpkg.Default()) rec := httptest.NewRecorder()