| Feature | Default | Description |
|---|---|---|
macros |
✓ | ExportMetrics and LabelEnum derive macros |
runtime |
Shared telemetry runtime for metrics and spans | |
otlp |
OTLP protobuf export support | |
clickhouse |
First-party ClickHouse row export support | |
eviction |
Enable stale-series eviction for dynamic metrics |
The eviction feature enables evict_stale(), advance_cycle(), and current_cycle() on
dynamic metric types. Without it, these APIs are not available.
[dependencies]
fast-telemetry = "0.10"use fast_telemetry::{Counter, ExportMetrics, Gauge, Histogram, MaxGauge, SampledTimer};
#[derive(ExportMetrics)]
#[metric_prefix = "myapp"]
pub struct AppMetrics {
#[help = "Total requests processed"]
pub requests: Counter,
#[help = "Request latency in microseconds"]
pub latency: Histogram,
#[help = "Hot path latency"]
pub hot_path_latency: SampledTimer,
#[help = "Current queue depth"]
pub queue_depth: Gauge,
#[help = "Peak queue depth seen during the current export window"]
pub queue_depth_peak: MaxGauge,
}
impl AppMetrics {
pub fn new() -> Self {
Self {
requests: Counter::new(4), // use available_parallelism() in production
latency: Histogram::with_latency_buckets(4),
hot_path_latency: SampledTimer::with_latency_buckets(4, 64),
queue_depth: Gauge::new(),
queue_depth_peak: MaxGauge::new(4),
}
}
}metrics.requests.inc();
metrics.latency.record(elapsed_us);
metrics.hot_path_latency.record_elapsed(std::time::Duration::from_nanos(125_000));
metrics.queue_depth.set(queue.len() as i64);
metrics.queue_depth_peak.observe(queue.len() as i64);SampledTimer is for measuring elapsed time around an operation. You can start
timing with an RAII guard and let it record on drop, or record a pre-measured
Duration directly.
It is designed for hot paths where you want timing instrumentation to stay cheap. Every use increments a total call counter, while elapsed-time measurements are sampled at the configured stride before being recorded into the latency histogram.
use std::time::Duration;
use fast_telemetry::{DeriveLabel, LabeledSampledTimer, SampledTimer};
let timer = SampledTimer::with_latency_buckets(4, 64);
// Record a duration you already measured elsewhere.
timer.record_elapsed(Duration::from_nanos(180_000));
{
// Time this scope with an RAII guard.
let _guard = timer.start();
// timed work
}
#[derive(Copy, Clone, Debug, DeriveLabel)]
#[label_name = "phase"]
enum Phase {
Parse,
Execute,
}
let labeled = LabeledSampledTimer::<Phase>::with_latency_buckets(4, 32);
{
// Time a labeled scope with the same guard pattern.
let _guard = labeled.start(Phase::Parse);
// timed parse work
}
// Or record a pre-measured labeled duration directly.
labeled.record_elapsed(Phase::Parse, Duration::from_nanos(90_000));Sampled timers record durations in nanoseconds. They export as a composite metric:
- Prometheus:
name_callsandname_samples - DogStatsD / OTLP:
name.callsandname.samples
name_calls / name.calls is the total operation count. name_samples is the
sampled latency histogram. The timer/guard API is the instrumentation surface;
sampling is how it keeps that instrumentation cheap enough for hot code.
// Prometheus text format
let mut output = String::new();
metrics.export_prometheus(&mut output);
// DogStatsD
let mut output = String::new();
metrics.export_dogstatsd(&mut output, &[("env", "prod")]);
// Structured in-process custom exporter
let mut visitor = MyMetricVisitor::default();
metrics.visit_metrics(&mut visitor);visit_metrics is generated by #[derive(ExportMetrics)] and calls a
MetricVisitor with typed cumulative observations. It uses Prometheus-style
metric names and borrowed MetricLabels, including borrowed canonical dynamic
label pairs, so custom exporters can collect report frames or UI models without
parsing Prometheus, DogStatsD, OTLP, or ClickHouse output.