Skip to content

fix: Usage events are emitted in units the meters do not declare #272

Closed

Description

@ecv

Compute usage events are emitted in units the meters do not declare, so any consumer that trusts the published unit reads a number a thousand times too large.

The billing contract asks services to emit an integer in the metric's base unit and let the meter's pricing unit carry the scale. The emitter instead scales the value itself, which the catalog has no way to know about.

Production prices none of these metrics today, so no invoice is affected. Staging does price them, and that is where the overstatement lands.

What success looks like

  • A consumer reading a compute usage event can multiply by the published rate and get the right cost
  • Fractional usage survives integer encoding without a scale factor the meter does not declare
  • Memory usage is emitted in the unit its meter declares
  • The mismatch is caught before the rates go live in production

https://claude.ai/code/session_01N7JuqnR9kgvHLuQnNcc5qQ

Activity

  1. ecv commented on Sep 3, 2026

    @ecv
    ContributorAuthor

    cc @JoseSzycho, who has been working in this area.

    Evidence, measured 2026-09-03.

    The emitter is the billing-usage-collector-unikraft component in datum-cloud/infra, changed by 0e317bcc:

    milli_vcpu_seconds = to_int(round(vcpu_seconds * 1000.0))
    milli_gib_seconds = to_int(round(memory_gib_seconds * 1000.0))
    vcpu_event.data.value = to_string(milli_vcpu_seconds)
    mem_event.data.value = to_string(milli_gib_seconds)
    

    The meters it feeds, from config/components/service-catalog/service-configuration.yaml:

    Metric Declared unit Emitted
    compute.datumapis.com/instance/cpu-seconds s vcpu-seconds x 1000
    compute.datumapis.com/instance/memory-seconds By.s GiB-seconds x 1000

    The memory mismatch is older than this change. memory_gib_seconds = (memory_bytes / 1073741824.0) * duration_s was already GiB-seconds against a byte-seconds meter, and the milli scaling multiplies an existing disagreement rather than creating it.

    The contract in milo-os/billing docs/emitting-usage.md is direct about both:

    Do not use floating point. Quantity is int64. Measure cpu-seconds, not cpu-hours; bytes, not gibibytes. The meter's consumedUnit/pricingUnit handle scale.

    The problem behind the change is real. data.value is int64 on the wire, so a sub-second window stringified as "0.5" decodes to 0 and the usage disappears. Emitting byte-seconds rather than GiB-seconds solves that for memory without a scale factor, since byte-seconds are large integers by construction. CPU needs its own answer, either a longer aggregation window or a meter that declares the scale.

    Rates, from the staging charges component: vcpu-second at 0.000014 and gib-second at 0.0000045 USD. Rate times a milli-unit value overstates by 1000.

    Blast radius today is staging only. That charges component is included by staging overlays alone, so production emits these metrics with no rates attached and no invoice moves. Worth settling before production gets rates.

    One immutability constraint to plan around: spec.metrics[].name, kind and unit are immutable once the ServiceConfiguration is Published, so a unit correction ships as a new metric name rather than an edit.

    https://claude.ai/code/session_01N7JuqnR9kgvHLuQnNcc5qQ

  2. JoseSzycho commented on Sep 3, 2026

    @JoseSzycho
    Contributor

    I'm currently working into this

  3. self-assigned this
    on Sep 17, 2026
  4. added a commit that references this issue on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions