Skip to content

/gauge

docs/src/snippets/modules.test.ts
// 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" }); // 3
integrate({ samples: seats, window: sept, mode: "max" }); // 3
integrate({ 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-seconds

Seat 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 }: value holds from at until 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.

It is a loop of a few lines:

docs/src/snippets/modules.test.ts
// 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 values

The 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.