Skip to main content

core.metrics

Layer 4.96 — Prometheus-compatible metrics

Concurrent-safe Counter / UpDownCounter / Gauge / Histogram primitives with bounded label cardinality, a pluggable exporter surface, and a Prometheus text-format (0.0.4) encoder.

Module layout

SubmodulePurpose
metrics.labelLabelSet — ordered value list
metrics.valueAtomicF64 — IEEE 754 atomic via AtomicU64 bit-pattern
metrics.instrumentCounter / UpDownCounter / Gauge
metrics.histogramHistogramBuckets + Histogram + HistogramSnapshot
metrics.registryRegistry, *Config, MetricFamily, handles
metrics.exporterMetricsExporter protocol
metrics.prometheusText-format (0.0.4) encoder

Instrument semantics

InstrumentSemanticsPrometheus TYPE
CounterMonotonic; add(v < 0) panicscounter
UpDownCounterBi-directional (OTel-style)gauge
GaugeArbitrary settablegauge
HistogramFixed bucket counts + sum + counthistogram

All scalar instruments are backed by AtomicF64 (lock-free CAS-loop add). Histogram bucket counts are per-bucket AtomicU64, sum is AtomicF64, total count is AtomicU64.

Registration + recording

mount core.metrics.*;

let registry = Registry.new();

let requests = registry.counter(CounterConfig {
name: "http_requests_total".into(),
help: "Total HTTP requests".into(),
labels: ["method".into(), "status".into()].to_list(),
});

// Bind label values and record
requests.with(&[&"GET".into(), &"200".into()]).inc();
requests.with(&[&"POST".into(), &"201".into()]).add(3.0);

*.with(values) returns a Shared<Counter> / Shared<Gauge> / … that can be cached by the caller if needed — repeated .with(values) calls with the same values hit the same underlying atomic (linear scan in the family's entry table).

Histogram

let h = registry.histogram(HistogramConfig {
name: "request_duration_seconds".into(),
help: "Request latency".into(),
labels: ["route".into()].to_list(),
buckets: HistogramBuckets.exponential(0.001, 2.0, 12),
});

h.with(&[&"/v1/orders".into()]).observe(0.023);

Default buckets cover ~5 ms to 10 s in Prometheus-default steps. Custom buckets available via HistogramBuckets.custom(&[...]), .linear(start, width, count), or .exponential(start, factor, count).

Prometheus text format

// Snapshot the registry and serialise
let body = core.metrics.prometheus.encode(&registry);

// Or stream into a caller-provided buffer (HTTP response body)
let mut buf = Text.with_capacity(4096);
core.metrics.prometheus.encode_into(&registry, &mut buf);

The encoder produces Prometheus exposition format 0.0.4 with # HELP and # TYPE comments, properly-escaped label values (\\\\\\, "\\", \n\\n), and cumulative histogram buckets with a synthetic le="+Inf" bucket for the total.

Performance

  • Counter.inc() and Gauge.set() are single-atomic operations (~5 ns uncontended, ~40 ns under contention).
  • Histogram.observe(v) walks buckets — O(bucket count) atomic ops. Default 11 buckets ⇒ ~60 ns uncontended.
  • Cardinality lookup is O(n_entries) per family; designers should keep label cardinality ≤ a few dozen per family (Prometheus best practice).

Label-cardinality control

There is no built-in hard limit on entries per family; callers are expected to enforce bounded cardinality at instrumentation time (e.g. avoid raw user-IDs / request-IDs as label values). A future CardinalityBudget helper may enforce this at the registry boundary.