Expose Prometheus metrics for the things that fail quietly (#15) #124
No reviewers
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED!124
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/15-metrics"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #15.
GET /metrics, off unlessMETRICS_ENABLED. Counters for scheduler job outcomes and WLED push attempts, a histogram of job durations, and gauges for controller reachability, firmware support, AI usage against the cap, and the last successful run of each job. Scrape config, the full metric list and three alert rules are indocs/setup.md.The metric that matters
iris_scheduler_last_success_timestamp_seconds{job}. Iris is set-and-forget, so the failure worth paging on is the nightly push that never happened — and that's an absence, not an error. Nothing is logged for a job that didn't run, so there's no line to alert on. A last-success timestamp turns it into something Prometheus can express:Related: a down controller reports
iris_wled_up 0rather than omitting the series. An absent series can't be alerted on with== 0— it just stops matching, which is precisely the failure mode the metric exists to catch.Instrumented at chokepoints, not call sites
Every scheduler job already funnels through
log_runand every WLED write through_try_push, so the counters are incremented there — one place each. Sprinklinginc()calls next to each job would create a second accounting of the same events that can silently drift from the first.Given this milestone has already produced three variants of "the test agreed with the code rather than reality" (
leds.segs, the preset mock aliasing, the config round-trip), I wrote the drift check explicitly: it writes N schedule-log rows and asserts the counter moved by exactly N, exercising the reallog_runrather than calling the recorder. Verified it fails when the instrumentation is removed — five tests go red.State gauges are computed at scrape time from the controller status and
schedule_log, the same sources the API reports from, so a gauge can't disagree with what the UI shows.They're deliberately not derived counters:
schedule_logis pruned afterSCHEDULE_LOG_RETAIN_DAYS, and a counter that goes down breaks everyrate()query built on it.Auth
Unauthenticated when enabled, as every exporter is — Prometheus can't complete an OIDC flow, the same constraint the calendar feed hit in #16. The difference is that a feed exposes the user's calendar while this exposes counts, and nothing is labelled with a host, path or credential, so a token would add friction without protecting anything. Documented as "keep it on the container network."
Safety
Recording never raises. The instrumentation sits inside
log_runand_try_push, and an exception there would take down a scheduled job for the sake of a counter. There's a test for that too.prometheus-clientuses a private registry rather than the global default, which would otherwise pad the output with process and GC collectors that say nothing about whether the lights came on.Verification
ruff,mypycleanuvx pre-commit run --all-filespassesrecord_job_runfromlog_runfails five tests🤖 Generated with Claude Code