| @@ -19,7 +19,7 @@ Health check. | |||||
| ### `GET /status` | ### `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:** | **Response:** | ||||
| ```json | ```json | ||||
| @@ -31,7 +31,15 @@ Current transmitter status (read-only snapshot). | |||||
| "rdsEnabled": true, | "rdsEnabled": true, | ||||
| "preEmphasisTauUS": 50, | "preEmphasisTauUS": 50, | ||||
| "limiterEnabled": true, | "limiterEnabled": true, | ||||
| "fmModulationEnabled": true | |||||
| "fmModulationEnabled": true, | |||||
| "runtimeIndicator": "normal", | |||||
| "runtimeAlert": "", | |||||
| "queue": { | |||||
| "capacity": 3, | |||||
| "depth": 1, | |||||
| "fillLevel": 0.33, | |||||
| "health": "low" | |||||
| } | |||||
| } | } | ||||
| ``` | ``` | ||||
| @@ -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 | 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 | 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 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 | ## WS-01 Verifikation | ||||
| | Datum | Fokus | Ergebnis | | | 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-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 | 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 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. | | |||||
| --- | --- | ||||
| @@ -140,6 +140,9 @@ func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { | |||||
| if alert, ok := stats["runtimeAlert"]; ok { | if alert, ok := stats["runtimeAlert"]; ok { | ||||
| status["runtimeAlert"] = alert | status["runtimeAlert"] = alert | ||||
| } | } | ||||
| if queue, ok := stats["queue"]; ok { | |||||
| status["queue"] = queue | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,6 +9,7 @@ import ( | |||||
| "testing" | "testing" | ||||
| cfgpkg "github.com/jan/fm-rds-tx/internal/config" | cfgpkg "github.com/jan/fm-rds-tx/internal/config" | ||||
| "github.com/jan/fm-rds-tx/internal/output" | |||||
| ) | ) | ||||
| func TestHealthz(t *testing.T) { | 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) { | func TestDryRunEndpoint(t *testing.T) { | ||||
| srv := NewServer(cfgpkg.Default()) | srv := NewServer(cfgpkg.Default()) | ||||
| rec := httptest.NewRecorder() | rec := httptest.NewRecorder() | ||||