meter

package
v0.4.19 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package meter is the module usage-metering surface. A module DECLARES each metric once, up front, with its kind and optional aggregation, unit, and price; runtime code then emits BY NAME with a single Record call — exactly mirroring ms.Emits (declare) / ms.Emit (emit by name). Declaration registers the metric into the module MANIFEST (via the registry, in core) AND into the module's meter registry, so the platform can populate its metric catalog BEFORE any event arrives — and a call site can never mislabel a metric's semantics, because they are read from the catalog, not the wire. There is NO stored handle: Record resolves the metric by name and fails fast if it was never declared (declaration-first).

Security model: the SDK runs inside the module's own (untrusted) process. Wire-format fields suffixed with Hint (AppIDHint, ModuleIDHint, RecordedAtHint) are NOT trusted — the platform re-derives authoritative values from the authenticated invoker. Reported VALUES affect only the developer's own customer billing. A reserved infra.*/platform.* metric may be declared only as a price override and can never be self-reported.

Index

Constants

View Source
const (
	// Counter declares the metric as additive: the platform SUMs it over the
	// billing period. Use it for things you count: orders placed, minutes
	// transcoded, messages sent. Pass it to Meter as the kind option.
	Counter kindOption = kindOption(counterKind)
	// Gauge declares the metric as an absolute current level the platform
	// never SUMs — it takes the MAX or a time-weighted integral. Use it for
	// levels you can safely re-report on a heartbeat: stored bytes, active
	// rows, open connections. Gauge is self-healing: a lost sample only loses
	// resolution. Pass it to Meter as the kind option.
	Gauge kindOption = kindOption(gaugeKind)
)

Counter / Gauge are CONSTANTS, not vars, so a third-party module cannot reassign them (the SDK is a trust boundary). A const of a defined type is immutable from any package; reassigning `ms.Counter = …` is a compile error.

View Source
const BySubject aggregationOption = aggregationOption(aggregationKeySubject)

BySubject declares a Gauge as a subject-keyed peak. Within each billing period the platform computes SUM(MAX(value) per Observation.Subject). The subject is a dedicated authoritative field; Metadata is diagnostic only and is never used as billing identity. BySubject is valid only with Gauge.

Variables

This section is empty.

Functions

func IsReserved

func IsReserved(name string) bool

IsReserved reports whether name falls under a platform-owned namespace (infra.*/platform.*). A reserved metric is platform-measured: a module may declare it with Price ONLY (to override the customer passthrough) but may never set its kind/unit or self-report its value.

func ValidateMetricName

func ValidateMetricName(name string)

ValidateMetricName rejects names that are empty, contain a path separator, whitespace, a dot-segment, or a null byte. It mirrors registry.ValidateName. The mirroring is deliberate, NOT accidental duplication: meter is a public package and must not import internal/registry, so the shared rule is restated here rather than shared via an internal import.

The reserved infra.*/platform.* namespace is NOT rejected here: a module may declare such a name with Price ONLY to override the customer passthrough. The reserved-vs-custom option rules are enforced in Declare; self-reporting a reserved name is rejected in Record.

func WithDispatchURL

func WithDispatchURL(ctx context.Context, dispatchURL string) context.Context

WithDispatchURL installs a broker-attested dispatch base for one managed task attempt. It avoids mutating MS_DISPATCH_URL in warm Lambda processes.

func WithServiceCredentialProvider

func WithServiceCredentialProvider(ctx context.Context, provider ServiceCredentialProvider) context.Context

WithServiceCredentialProvider installs an attempt-scoped credential source.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the module-level meter transport AND metric registry. Created eagerly at ms.Init: it POSTs each usage Event to the platform dispatch usage ingress over HTTP (mirroring ms.Emit / ms.Call), in both dev and prod — there is no separate dev sink. Declared metrics live in its registry, keyed by name, so Record resolves a metric by name (mirroring ms.Emits/ms.Emit).

The transport is dispatch-HTTP: MS_DISPATCH_URL (or the dev fallback) routes to the dispatch usage ingress, which re-derives the authoritative attribution and forwards to billing-engine. The HTTP client is ALWAYS built (never nil) — in dev it harmlessly targets the local dispatch, and a transport failure is non-fatal (returned, then logged by the caller, not propagated).

func New

func New() (*Client, error)

New creates a meter client. The transport is dispatch-HTTP in both dev and prod (the URL is resolved per-Record from MS_DISPATCH_URL with the dev fallback), so there is a single constructor — no ARN/dev split. The HTTP client is always built so Record never dereferences a nil transport.

Fail-fast prod-base validation mirrors ms.Emit: if MS_DISPATCH_URL is set it must be a parseable http(s) URL, so a typo fails at startup rather than at the first (silently lost) Record call. An unset MS_DISPATCH_URL is valid (dev fallback).

func (*Client) Declare

func (c *Client) Declare(moduleID string, decl Decl)

Declare validates a metric declaration and registers it into the client's metric registry under decl.Name, so a later Record(ctx, name, value) resolves it BY NAME (mirroring how ms.Emits records an emit name that ms.Emit later resolves). NO handle is returned — emission is by name.

The caller (core) is responsible for registering decl into the MANIFEST (registry.AddMetric); Declare owns the name/kind/reserved-prefix validation so the public ms.Meter contract is enforced in one place, plus its own registry duplicate guard.

Validation (all panic — declaration is startup code, so a bad declaration is a programmer error that must fail loudly):

  • empty/malformed name;
  • conflicting kinds (both Counter and Gauge passed);
  • a CUSTOM (non-reserved) name without exactly one kind option;
  • BySubject without Gauge;
  • a RESERVED infra.*/platform.* name carrying a kind or unit option (those are platform-owned — a reserved name may carry Price ONLY, to override the customer passthrough);
  • a duplicate metric name (a second declaration would silently disagree on kind/price).

func (*Client) Record

func (c *Client) Record(ctx context.Context, name string, value float64) error

Record emits a usage event for the metric declared under name with the given value. It mirrors ms.Emit: resolve the declared name, build the envelope, hand it to the transport. The platform reads the declared kind from its manifest-fed catalog to decide SUM vs MAX/integral, so a call site can never mislabel a metric.

Declaration-first: if name was never declared via ms.Meter, Record returns an error (fail fast in dev) — it never silently emits an unknown metric.

Emitted by POSTing the Event envelope to the platform dispatch usage ingress ({MS_DISPATCH_URL | dev fallback}/apps/{appID}/usage), exactly mirroring ms.Emit. Dispatch re-derives the authoritative app/module/owner/recorded_at and forwards to billing-engine; the SDK's Hint fields are untrusted. An empty app id (no auth identity in ctx) is an error (no panic), like ms.Emit.

Call sparingly — once per meaningful action, not per row processed. Errors are returned, NOT panicked, and should be logged, not propagated: a billing failure must never fail the handler.

Returns an error (does NOT panic) if value is negative, NaN, or infinite — a single bad value can't crash the handler. The metric name was already validated at declaration.

The EventID is minted ONCE per Record call (and the built Event reused across any transport retry of the same call) so the platform's ON CONFLICT(event_id) dedupe holds and a retried delivery is not double-counted.

func (*Client) RecordObservation

func (c *Client) RecordObservation(ctx context.Context, name string, value float64, observation Observation) error

RecordObservation emits a v2 usage observation with a caller-persisted EventID, opaque end-user Subject, bounded diagnostic Metadata, and original OccurredAt. It is the required runtime path for a metric declared BySubject and is also valid for ordinary custom metrics when occurrence/audit evidence is needed. The SDK validates the observation's structure and normalizes OccurredAt to UTC, but does not compare it with the module process clock. Dispatch/API/Billing own authoritative occurrence-window, replay, and billing-period decisions. Dispatch also re-derives attribution and receipt time; RecordedAtHint is carried only for envelope continuity and is discarded before Billing.

func (*Client) RecordWithID

func (c *Client) RecordWithID(ctx context.Context, eventID, name string, value float64) error

RecordWithID emits a usage event with a caller-owned, persisted UUID as its deduplication key. Persist eventID with the billable state transition and reuse it for every delivery retry.

type Decl

type Decl struct {
	Name    string
	Kind    Kind
	KindSet bool

	// AggregationKey is empty for ordinary semantics or "subject" when
	// BySubject was selected. Construct declarations through DeclFromOptions.
	AggregationKey string
	Unit           string
	UnitSet        bool
	Price          int64
	PriceSet       bool
	// Label is the metric's optional per-locale display label (ms.Text / ms.T),
	// resolved into the manifest metric entry in core.Meter (mirroring the
	// permission-label path). Zero when undeclared.
	Label i18n.Label
	// UnitLabel is the metric's optional per-locale display label for its Unit
	// (ms.Text / ms.T), resolved into the manifest metric entry in core.Meter
	// (mirroring Label). Zero when undeclared; does not affect Unit's billing
	// semantics.
	UnitLabel i18n.Label
	// contains filtered or unexported fields
}

Decl is the declared shape of a metric: name + kind + optional aggregation, unit, and price. It is what flows into the manifest (see registry.MetricDecl), so the platform can populate its metric_definitions catalog at install/publish.

KindSet / UnitSet / PriceSet report whether each option group was supplied, so Declare can enforce that a custom metric carries a kind and a reserved infra.*/platform.* metric carries price only. For a reserved price-override declaration Kind is empty (KindSet false) and the platform catalog supplies the kind/unit.

Construct a Decl via DeclFromOptions, NOT a struct literal: it has an unexported conflict-tracking field (kindDup) that a literal cannot set, so a hand-built Decl would silently skip the conflicting-kind guard.

func DeclFromOptions

func DeclFromOptions(name string, opts ...MetricOption) Decl

DeclFromOptions applies the variadic options to produce a Decl. Used by core (ms.Meter) to translate the public name + options into the declaration that is both validated + registered (Declare) and registered into the manifest (registry.AddMetric). The kind is itself an option (Counter / Gauge); the resulting Decl records which option groups were set so Declare can enforce the custom-vs-reserved rules (custom requires a kind; reserved allows price only).

type Event

type Event struct {
	V              int       `json:"v"`
	EventID        string    `json:"eventId"`
	AppIDHint      string    `json:"appIdHint,omitempty"`
	ModuleIDHint   string    `json:"moduleIdHint"`
	Metric         string    `json:"metric"`
	Value          float64   `json:"value"`
	RecordedAtHint time.Time `json:"recordedAtHint"`
}

Event is the v1 JSON wire format sent to the platform meter ingress by Record and RecordWithID. RecordObservation uses a private additive v2 envelope so this exported struct remains source-compatible.

There is deliberately NO kind field: a metric's kind (counter/gauge) is declared once via ms.Meter and travels in the manifest, so the platform's catalog is the single authoritative source — a call site can never mislabel a metric's semantic on the wire.

Fields ending in Hint are SDK-asserted values the platform uses for debugging and logging but MUST NOT trust for billing attribution. The authoritative values are re-derived platform-side from the authenticated invoker (Axis 2 of the Milestone D design).

ModuleIDHint is required (no omitempty) even though it is labeled a hint — the "Hint" suffix means "SDK-asserted, not authoritative," not "optional." AppIDHint is omitempty because system-triggered events may have no app context (e.g., cron job running without an AppID in ctx).

EventID is the at-least-once retry dedup key: the platform ingest is idempotent on it (ON CONFLICT(event_id) DO NOTHING), so the SAME logical Record call must reuse the SAME EventID across any transport retry. It is minted ONCE per Record call (before the event is handed to the transport) and the built Event is reused across any transport retry within that call, so a retried delivery is deduped rather than double-counted.

type Kind

type Kind string

Kind is the billing semantic of a declared metric. It is fixed at declaration and recorded in the manifest; it does NOT travel on the wire (the platform's manifest-fed catalog is authoritative). A single Record call emits for both kinds — the kind decides how the platform aggregates.

Kind is set via the Counter / Gauge declaration OPTIONS, not as a positional argument; counterKind / gaugeKind are the internal enum values those options carry into the manifest and registry.

func (Kind) IsValid

func (k Kind) IsValid() bool

IsValid reports whether k is one of the two known kinds.

type MetricOption

type MetricOption interface {
	// contains filtered or unexported methods
}

MetricOption configures a metric at declaration time. The KIND is itself an option (Counter / Gauge), alongside BySubject, Unit, and Price. A custom metric MUST pass exactly one kind option; BySubject is valid only with Gauge. A reserved infra.*/platform.* metric may pass ONLY Price (its kind/unit/ aggregation are platform-owned). All other combinations panic at declaration (see Declare).

MetricOption is an INTERFACE, not a func type, on purpose: it lets Counter and Gauge be untyped-style package CONSTANTS rather than reassignable package vars. The SDK is a security boundary — if Counter/Gauge were exported `var`s a hostile module could execute `ms.Counter = nil` and silently break every later Meter call. As const-backed interface values they cannot be reassigned from outside (or inside) the package, while the call site stays parens-free (`ms.Meter(name, ms.Counter, ...)`).

func MetricLabel

func MetricLabel(l i18n.Label) MetricOption

MetricLabel sets the metric's per-locale display label, built from ms.Text (a literal) or ms.T (an i18n catalog key). It is resolved against the module's i18n catalogs at manifest build (mirroring PermissionOpts.Label) and folded into the manifest metric entry per-locale, so the platform's pricing / usage UI can show a localized name instead of the raw metric key. Optional — a zero Label is omitted entirely.

Named MetricLabel (not Label) because ms.Label is already the exported label TYPE; a top-level Label option would collide with it.

func MetricUnitLabel

func MetricUnitLabel(l i18n.Label) MetricOption

MetricUnitLabel sets the metric's per-locale display label for its UNIT, built from ms.Text (a literal) or ms.T (an i18n catalog key). It is resolved against the module's i18n catalogs at manifest build (mirroring MetricLabel) and folded into the manifest metric entry per-locale, so the platform's pricing / usage UI can show a localized unit (e.g. "使用者") instead of the raw Unit identifier. Optional — a zero Label is omitted entirely.

The Unit itself (ms.Unit) stays the untranslated billing unit identifier used for per-unit pricing aggregation; this is a SEPARATE display-only label and does NOT change Unit's semantics.

func Price

func Price(microDollars int64) MetricOption

Price sets the metric's per-unit CUSTOMER price in micro-dollars (1e-6 USD). This is the developer's Plane-2 pricing for THEIR customer; the platform charges quantity × this price with NO blanket markup (the flat 1.2× applies only to platform-infra metrics, never a module's custom metric). Optional — omit it to meter without charging.

func Unit

func Unit(u string) MetricOption

Unit sets the metric's display unit (e.g. "order", "byte"). Informational metadata for the platform UI / invoice line; it does not affect aggregation.

type Observation

type Observation struct {
	EventID    string
	Subject    string
	Metadata   json.RawMessage
	OccurredAt time.Time
}

Observation carries the caller-owned identity and occurrence evidence for a v2 meter event. EventID must be persisted with the billable state change and reused across ambiguous retries. Subject is an opaque end-user identity; Metadata is bounded diagnostic JSON and never an aggregation key. OccurredAt is the original event time, not the delivery/receipt time. The SDK requires it and normalizes it to UTC, but does not compare it with the module process clock. API and Billing own occurrence-window, replay, and billing-period decisions against authoritative server state.

type ServiceCredentialProvider

type ServiceCredentialProvider interface {
	ServiceCredential(context.Context) (string, error)
}

ServiceCredentialProvider supplies renewable outbound module authority. Managed one-shot tasks install one so Record never falls back to an ambient process credential that the task runtime intentionally does not receive.

Jump to

Keyboard shortcuts

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