Documentation
¶
Overview ¶
Package drain computes the fleet's parked-drain metrics from the persisted vehicle_state history and exposes them to Prometheus (D1, the instrument for the battery-drain experiment's Gate A).
Everything is derived from rows the recorder already writes; nothing here talks to Rivian, so computing it can't wake a car. The definitions match the ones already in use, so the gauges, the analysis queries and the Sleep chart agree:
- Parked drain (%/day): SoC lost over consecutive sample pairs where the car is parked (gear P or unknown, under 1 mph, not charging) at both ends, less than 24 h apart, with the odometer still (< 0.2 mi) and SoC moving less than 3 points (a bigger jump is an unrecorded charge or a calibration step, not drain). 24 × ΣΔSoC / Σhours. This is devils-advocate's D3 query.
- Idle-awake (h/day): hours awake while parked and not charging, per 24 h of recorded power state. Intervals are capped at 20 min and attributed to the state at their start, as in samples.SleepActivity, except that an unknown (NULL) gear or charging state counts as parked and not charging, as for drain.
- Sleeps: a sleep runs from the first 'sleep' sample after a non-sleep one to the next non-sleep sample. A quick re-wake is a sleep that lasted under 60 s (the sleep gate's failed sleep). A sleep counts in the window it ended in; one whose start is before the window, or which hasn't ended, isn't counted.
Index ¶
Constants ¶
const ( Window24h = "24h" Window7d = "7d" )
Windows the metrics are computed over.
const DefaultInterval = 15 * time.Minute
DefaultInterval is how often Run recomputes. The windows are 24 h and 7 d, so a quarter hour of lag is invisible; one fleet query per interval is cheap.
Variables ¶
This section is empty.
Functions ¶
func ParseInterval ¶
ParseInterval reads RIVOLT_DRAIN_METRICS_INTERVAL: empty means DefaultInterval; "0", "off" or "false" disable the metrics (ok = false); anything else is a Go duration, at least a minute. An unparsable value falls back to the default and returns the error for the caller to log.
Types ¶
type Collector ¶
type Collector struct {
// contains filtered or unexported fields
}
Collector serves the last computed snapshot to Prometheus. Run refreshes it; scrapes never touch the database. Series for a vehicle with no rows in the last 7 days disappear on the next refresh.
Every replica computes and exports the same fleet-wide series (prod runs two pods), so they differ only in the scrape's instance label. Query them aggregated, max by (vehicle, window), as the dashboard in deploy/grafana/vehicle-drain.json does; never sum them.
The vehicle label is the Rivian vehicle id. That's fine for the admin-only Grafana these feed, but keep these series off any public or shared dashboard.
func NewCollector ¶
func NewCollector() *Collector
NewCollector returns a Collector with no data yet.
func (*Collector) Collect ¶
func (c *Collector) Collect(ch chan<- prometheus.Metric)
Collect implements prometheus.Collector.
func (*Collector) Describe ¶
func (c *Collector) Describe(ch chan<- *prometheus.Desc)
Describe implements prometheus.Collector.
func (*Collector) Run ¶
func (c *Collector) Run(ctx context.Context, db *sql.DB, interval time.Duration, logger *slog.Logger)
Run recomputes now and then every interval until ctx is done. A failed recompute keeps the previous snapshot (its last-success timestamp ages, which is what to alert on) and logs a warning.
type Stat ¶
type Stat struct {
VehicleID string // Rivian vehicle id, as in the logs
Window string // Window24h | Window7d
// ParkedHours is the parked time the drain rate is measured over.
ParkedHours float64
// DrainPctPerDay is nil when ParkedHours is under the window's
// minimum.
DrainPctPerDay *float64
// ObservedHours is the time with a recorded power state.
ObservedHours float64
// IdleAwakeHPerDay is nil when ObservedHours is under the minimum.
IdleAwakeHPerDay *float64
Sleeps int
QuickRewakes int
// MedianSleep is nil when no sleep ended in the window.
MedianSleep *time.Duration
}
Stat is one vehicle's metrics over one window.