/gauge
// import { integrate, window } from "pricemeter/gauge";const sept = window({ from: Date.parse("2026-09-01T00:00:00Z"), to: Date.parse("2026-10-01T00:00:00Z") });const seats = [ { at: Date.parse("2026-08-20T00:00:00Z"), value: 2 }, // before the window: the starting level { at: Date.parse("2026-09-16T00:00:00Z"), value: 3 }, // one seat added mid-month];integrate({ samples: seats, window: sept, mode: "last" }); // 3integrate({ samples: seats, window: sept, mode: "max" }); // 3integrate({ samples: seats, window: sept, mode: "time_weighted", unit: "s" }); // 6_480_000 seat-seconds// 2 seats × 30 days + 1 seat × 15 days = 75 seat-days = 6,480,000 seat-secondsSeat counts, stored GB, running instances: these are levels, not events. pricemeter/gauge reduces the level samples your application keeps to one integer quantity at period end; that quantity is priced by an ordinary observe. Its seam in the module test is “producing an observation quantity”.
| API | What it does |
|---|---|
window({ from, to }) |
A half-open [from, to) window; throws when empty or not finite |
integrate({ samples, window, mode, unit? }) |
Integrates the step function over the window and returns an integer, rounded up |
mode |
Result | Typical use |
|---|---|---|
last |
The level at the end of the window | “how many seats at month end” |
max |
The highest level in the window | the month’s peak GB (B10) |
time_weighted |
Level × time in unit (ms, s, h; default s) |
seat-seconds, prorated with per (B9) |
- A sample is
{ at, value }:valueholds fromatuntil the next sample. Samples can come in any order; the level before the first one is 0. - Samples before the window set the starting level. Negative or non-finite values throw.
- The result is rounded up with float noise removed.
Proration: time_weighted yields seat-seconds; the tariff is unitPriceMicroUsd: monthly price, per: seconds in the month. A seat added halfway costs half. The period-end observation uses at: window.to − 1 (A98). Full flow: SaaS seats example.
How you’d do it without this
Section titled “How you’d do it without this”It is a loop of a few lines:
// Without /gauge: walk the step function yourself.let level = 0;let t = sept.from;let area = 0;for (const s of [...seats].sort((a, b) => a.at - b.at)) { if (s.at <= sept.from) level = s.value; else if (s.at < sept.to) { area += level * (s.at - t); [t, level] = [s.at, s.value]; }}area += level * (sept.to - t);Math.ceil(area / 1000); // 6_480_000 — then remember float noise, unsorted input and negative valuesThe traps: unsorted input, the starting level from before the window, the half-open window, float noise pushing an integer up by one (ceil(2.0000000001)), negative values. If your database already sums levels over time (SUM(value × duration)), you don’t need the module; pass that number to observe.