Help us shape the future of Go! Take the 2026 Go Developer Survey

drain

package
v0.40.22 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 9 Imported by: 0

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

View Source
const (
	Window24h = "24h"
	Window7d  = "7d"
)

Windows the metrics are computed over.

View Source
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

func ParseInterval(v string) (d time.Duration, ok bool, err error)

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.

One replica (the holder of a Postgres advisory lock) computes and exports the fleet-wide series; the others export nothing, so a pod that lost the lock can't keep serving stale numbers. The 7-day fleet scan is the cost, and it would otherwise double with every replica. Failover is within one interval after the leader pod goes away. Query the series aggregated, max by (vehicle, window), as the parked-drain Grafana dashboard 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, while this replica holds the leader lock. Without it the snapshot is cleared (the replica exports no drain series) and the next tick tries again. A failed recompute keeps the previous snapshot (its last-success timestamp ages, which is what to alert on), gives the lock up with the connection, and retries on the next tick.

func (*Collector) Set

func (c *Collector) Set(stats []Stat, at time.Time, took time.Duration)

Set replaces the snapshot. Run calls it; tests use it directly.

type Querier added in v0.38.0

type Querier interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

Querier is what Compute reads through: a *sql.DB, or the *sql.Conn the collector holds its leader lock on.

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.

func Compute

func Compute(ctx context.Context, db Querier, now time.Time) ([]Stat, error)

Compute returns every vehicle's stats for the 24 h and 7 d windows ending at now, two per vehicle with any rows in the last 7 days. It reads across all users (the app role owns the tables).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL