feat: add container restart count and build duration metrics - #560
Conversation
Docker's own container-inspect RestartCount field is unusable here: every levelrail-managed container disables Docker's restart policy by design, so it never increments. Wires the real signal that already existed for crashloop detection (alerting.RestartTracker's Docker event-stream watcher) through as a persisted container_restart_count metric instead. Build duration was already recorded backend-side; this surfaces it on the per-app metrics dashboard as a real chart. Request rate, latency percentiles, and error rate are skipped this pass: they need Caddy ingress instrumentation that doesn't exist yet.
|
Warning Review limit reachedNext included review available in 39 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
| 'container_restart_count', | ||
| range, | ||
| ) | ||
| const restartCount = restartCountQuery.data?.points.length ?? null |
There was a problem hiding this comment.
For the 6h, 24h, and 7d ranges, the API groups restart samples into time buckets and exposes the number of samples in each point's count field. Using points.length counts non-empty buckets instead, so multiple restarts in one bucket are displayed as a single restart.
| const restartCount = restartCountQuery.data?.points.length ?? null | |
| const restartCount = | |
| restartCountQuery.data?.points.reduce( | |
| (total, point) => total + point.count, | |
| 0, | |
| ) ?? null |
| 'network_tx_bytes', | ||
| 'disk_read_bytes', | ||
| 'disk_write_bytes', | ||
| 'container_restart_count', |
There was a problem hiding this comment.
Restart thresholds cannot count
This exposes container_restart_count in the generic threshold-rule picker, but every restart is stored as a separate sample with value 1 and threshold evaluation checks only the latest sample. A rule such as “container restarts > 3” therefore cannot fire even when a container restarts repeatedly.
| if !t.seen[containerName] { | ||
| t.seen[containerName] = true | ||
| t.mu.Unlock() | ||
| return | ||
| } |
There was a problem hiding this comment.
The tracker starts with an empty in-memory seen map and does not seed it from containers that are already running. After Levelrail starts, the first real restart of a pre-existing container is therefore treated as that container's initial startup and returns before telemetry is recorded, causing restart totals to be undercounted across control-plane restarts.
| if !ok || serviceName == "" { | ||
| return | ||
| } | ||
| if err := recorder.RecordContainerRestart(context.Background(), serviceName, at); err != nil { |
There was a problem hiding this comment.
Telemetry blocks event processing
The telemetry write runs synchronously inside the sole restart-event consumer and uses context.Background(). If SQLite is busy, the write can stall subsequent crashloop observations and cannot be canceled with RestartTracker.Run, delaying event processing and shutdown until the database operation finishes.



Summary
Test plan