# `Hyper.Metering.Usage`
[🔗](https://github.com/harmont-dev/hyper/blob/main/lib/hyper/metering/usage.ex#L1)

One VM's metered compute over one wall-clock window: `cpu_usec` is CPU time
the VM *actually executed* between `window_start` and `window_end`, measured
from its cgroup's `cpu.stat` by `Hyper.Node.FireVMM.Meter`.

Append-only: one row per VM per flush window (plus a final row at teardown),
never updated. Windows with zero consumption are not recorded — absence of
rows over a span means no compute happened. Billing reads `total/1` (a VM's
lifetime compute) or `total/3` (compute over a half-open `[from, to)` range,
bucketed by `window_start` so consecutive ranges never double-count).

Stored in the cluster's shared Postgres via `Hyper.Img.Db.Repo` — usage
rows have no foreign keys so a billing record outlives the VM, its image,
and its lease.

# `attrs`

```elixir
@type attrs() :: %{
  vm_id: Hyper.Vm.Id.t(),
  node_id: String.t(),
  window_start: DateTime.t(),
  window_end: DateTime.t(),
  cpu_time: Unit.Time.t()
}
```

# `record`

```elixir
@spec record(attrs()) :: :ok | {:error, Ecto.Changeset.t() | Exception.t()}
```

Record one usage window. Zero-consumption windows are refused, not stored.

Idempotent on `(vm_id, window_start)`: a retried flush whose earlier attempt
actually committed (the insert succeeded server-side but the client saw an
error) is dropped by the unique index rather than double-billed. DB failures
(connection loss, timeouts) are returned as `{:error, exception}`, never
raised — the meter's keep-and-retry loop depends on that.

# `total`

```elixir
@spec total(Hyper.Vm.Id.t()) :: Unit.Time.t() | nil
```

A VM's lifetime metered compute; `nil` when the VM was never metered.

# `total`

```elixir
@spec total(Hyper.Vm.Id.t(), DateTime.t(), DateTime.t()) :: Unit.Time.t() | nil
```

A VM's metered compute across windows starting in `[from, to)`; `nil` when
none do. A window straddling `to` counts whole — usage is bucketed by
`window_start`, so consecutive ranges never double-count.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
