Skip to main content
Version: 0.1.0-alpha.4 (alpha)

Bounded guest custom metrics

The configured MetricProvider implements latent:telemetry/custom@0.1.0 on the existing shared telemetry pipeline. Every emit-metric requires the exact import, installed provider identity, current publication and policy, and an operation grant with resource kind telemetry and an allowed metric name. Guest strings never select a provider or supply trusted source identity. An uninstalled import fails preparation.

Composition and identity​

An embedding supplies MetricProvider::install(&broker, telemetry_handle, epoch, config, activation_limits) and calls ActivationCapabilityRuntime::install_metrics with the returned Arc. Its reference() supplies the custom-metrics-v1 profile, configuration digest and epoch for durable bindings and exact plan compilation. This provider is available through the Rust embedding. The current standalone provider configuration has no custom-metric configuration field.

CustomMetricsConfig declares exact tenant policies, metric names, kinds, units, label keys and permitted values. It has no wildcard labels. Names start with an ASCII letter and contain only letters, digits, _ and .. Case-insensitive latent, otel and host prefixes are reserved. Units use nonempty ASCII letters, digits or _.-/. Names are at most 64 bytes, units 16, label keys 32, and label values 64. Duplicate keys or values are rejected. Values must be explicitly configured nonempty ASCII graphic strings; choose nonsecret data. Optional labels are canonicalized in descriptor order, independently of guest ordering. Omission is a distinct series.

Every exported name has the host prefix latent.application.. Attributes latent.tenant, latent.service and latent.revision come from the sealed activation plan. Allowed application labels appear under guest.. Public activation IDs, principal claims and arbitrary caller metadata are not labels. Two independently authorized publications containing identical component bytes retain independent tenant series, permissions and activation allowances.

One registry can be installed during a pipeline lifetime. It holds bounded node configuration and a preallocated fixed-capacity series table; there is no registry, exporter, task, timer or connection per dormant service. Retirement stops new observations without deleting accepted queue owners. Reconfiguration requires a new node/pipeline composition. Failure after installation during composition also requires a new pipeline; it cannot silently replace a live registry. Compiled components may retain the shared provider owner, but never an activation's mutable metric state.

Aggregation contract​

All values must be finite. Same-name descriptors across tenant policies must use the same kind, unit and histogram bounds. No implicit type or unit conversion is performed.

KindAccepted value and exported aggregation
CounterNonnegative delta; running sum and observation count.
Up-down counterSigned delta; running sum and observation count.
GaugeLast accepted sample, ordered by the registry's monotonic acceptance sequence, independent of wall-clock ordering.
HistogramObservation plus running count, sum and fixed disjoint bucket counts. Each finite bound is inclusive; the final bucket covers values above the last bound.

Histograms have at most 16 finite, strictly increasing operator-supplied bounds. Guests cannot create bucket maps. An empty bound list has only the final bucket. Count/sequence overflow and nonfinite sum results reject the observation without changing the aggregate. Floating-point sums are approximate. Aggregation covers accepted observations even if their subsequent export fails. It is process-local diagnostic state and is not recovered after restart.

Admission, bounds and ownership​

ScopeDefault and hard bound
ConfigurationAt most 8 tenants, 32 descriptors per tenant, 128 total, 256 KiB charged metadata; actual vector/string capacities are checked.
Label cardinalityAt most 8 keys and 16 exact values per key. Combined supplied key/value bytes default to 1,024; hard maximum 4,096.
Active seriesNode default 512, hard maximum 1,024; tenant default 64, bounded by the node ceiling. No eviction silently resets an active aggregate.
Observation rateNode default 4,096 per second, hard maximum 16,384; tenant default 1,024, bounded by the node ceiling. One shared monotonic one-second window, with no timer. Clock rollback grants no fresh window.
Queued/exporting custom bytesNode default 4 MiB, hard maximum 32 MiB; tenant default 512 KiB, bounded by the node ceiling. Each observation reserves a conservative 32 KiB before allocation.
ActivationDefault 128 observations, 16 distinct series and 1 MiB cumulative record allowance; hard maxima 1,024 observations, 32 series and 4 MiB. All apply together: the default byte allowance permits at most 32 accepted records.
Shared exporterExisting channel capacity and one exporting future; custom metrics share capacity with lifecycle logs, spans and runtime metrics. The local sink has its own bounded retention.

Validation and exact policy checks precede record copying. Pending activation reservations cover observations, distinct series and record bytes. Required audit admission completes before capture. Registry admission uses a nonblocking lock and reserves both channel capacity and node/tenant bytes before allocating the record or updating aggregation. Input strings are replaced with a compact validated selection before asynchronous audit waits. That selection is bound to the exact registry and trusted source; it grants no execution permission.

Dropping unstarted work or rejecting capture refunds pending activation quota. Accepted observations remain spent even if export fails. The original queue charge remains owned through actual export completion, timeout or abort cleanup; returning to the guest does not refund it. The local sink independently charges its retained records. Broker result ownership survives guest lowering until Store destruction. Session metadata includes its bounded activation series table.

Every call prepays CPU fuel 100 + bounded retained input bytes through the original activation ledger, plus normal broker call/input metadata reservations. Fuel is accounting work, not a latency guarantee. No outbound-request, log, blob, child-call, state or effect allowance is consumed. Cancellation and deadlines are checked before dispatch and before returning control to the guest.

Acceptance, failure and export​

ok(true) means admission to the bounded capture queue. This profile never uses ok(false); it returns invalid-name for invalid observations or configuration mismatches, budget-exhausted for quotas, busy/full capacity or arithmetic overflow, and unavailable for closed/retired owners or authorization failure. The frozen WIT error names do not imply that only the name field is validated.

Capture acceptance is not exporter acknowledgement, persistence or durable audit. MetricProvider::snapshot() counts guest attempts, captures and typed failures. CustomMetricRegistry::snapshot() reports active series, outstanding bytes, capture outcomes and retirement using fixed numeric fields. Validation done by a broker before registry submission appears in the provider's counters. Pipeline snapshots separately report actual exports, sink failures, timeouts and panics. Required capability audit records an admitted operation and HostCompleted or Rejected; those outcomes describe capture, not monitoring delivery.

The shared TelemetrySink::emit_custom_metric receives an immutable CustomMetricPoint with the point, acceptance sequence and aggregation. StructuredLocalSink supports it. External sinks must implement that method; the default explicitly returns unavailable, which increments sink failures. There is no selected OTLP adapter in this repository. Integrations use this existing shared exporter port and must bound their own buffers and cooperate with asynchronous deadlines. A blocked synchronous exporter cannot be forcibly interrupted by a timer. flush() waits for preceding attempts, including failed attempts; inspect counters rather than inferring delivery from its success.

Verification​

cargo test -p latent-telemetry --lib --locked
cargo test -p latent-wasmtime --test metrics --locked
cargo test -p latent-capabilities --lib --locked

Real canonical ABI guests cover all four kinds, nonfinite values, incompatible kind/unit reuse, duplicate/reserved labels, cardinality and activation limits, closed sinks, missing authority, retirement and required audit. Identical Wasm publications execute for two tenants on the same backend/cell with independent aggregates. Ownership cases cover pending refunds, queue failures, fuel, cancellation and held results after session closure. Shared exporter cases cover overflow, slow/panicking/unsupported sinks, timeout and abort reclamation. These bounded tests require no large load benchmark.