This guide helps you port codebases using the metrics ecosystem (metrics-rs) to metrics-lib with minimal friction. The focus is on in-process hot-path metrics with ultra-low overhead.
- API surface
- metrics-rs: macro-first (
counter!,gauge!,histogram!) routed through a global recorder/recorder backend. - metrics-lib: direct typed handles (
Counter,Gauge,Timer,RateMeter) via a thin globalMetricsCoreor explicit instances.
- metrics-rs: macro-first (
- Overhead model
- metrics-rs: depends on the installed recorder, label handling, and chosen exporter.
- metrics-lib: zero-allocation hot paths, lock-free atomics, cache-line alignment; exporting is intentionally kept out of the hot path.
metrics-rs:
metrics::counter!("requests_total", 1);metrics-lib:
use metrics_lib::{init, metrics};
init();
metrics().counter("requests_total").inc();Batching:
// metrics-rs often relies on exporter batching.
// metrics-lib exposes explicit batch ops or manual aggregation.
metrics().counter("requests_total").add(10);metrics-rs:
metrics::gauge!("cpu_pct", 87.3);metrics-lib:
metrics().gauge("cpu_pct").set(87.3);metrics-rs:
let start = std::time::Instant::now();
// ... work ...
metrics::histogram!("latency", start.elapsed());metrics-lib:
let t = metrics().timer("latency").start();
// ... work ...
t.stop(); // recorded on dropOr record directly:
metrics().timer("latency").record(std::time::Duration::from_micros(123));metrics-rs: derived from counters via observer queries (PromQL, etc.).
metrics-lib: in-process sliding window via RateMeter with tick(), rate(), and burst admission helpers:
let r = metrics().rate("api_calls");
r.tick();
if r.tick_if_under_limit(1000.0) { /* allowed */ }- metrics-rs encourages labels; high cardinality can be expensive.
- metrics-lib keeps the hot path label-free. Prefer out-of-band export formatting or background enrichment.
Migration tip: keep a small, stable set of metric names; push optional labels to your exporter/collector stage.
- metrics-rs: exporter-integrated.
- metrics-lib: export via background snapshots (JSON, Prometheus/OpenMetrics bridge, etc.). See
docs/API.md#observability-stack-integration.
- Prefer
try_variants (try_add,try_set,try_record) when inputs can be invalid. - Result-bearing methods use
#[must_use]so you don’t accidentally drop critical decisions (e.g., rate-limiter admissions).
- Initialize once at startup:
init()/init_with_config(). - Pre-register handles to avoid lookup overhead in hot paths.
- Replace macros with explicit typed handles and method calls.
- Move label logic to exporters or logging layers.
- Counters, Gauges, Timers produce the same values as before under a test harness.
- Rate-limiting behavior matches expectations with
RateMeter. - Benchmarks on representative hardware show expected improvements.
Before:
metrics::counter!("jobs", 1);
metrics::gauge!("queue_depth", 42.0);
metrics::histogram!("db_ms", 12.0);After:
let m = metrics_lib::metrics();
m.counter("jobs").inc();
m.gauge("queue_depth").set(42.0);
m.timer("db_ms").record(std::time::Duration::from_millis(12));