use std::sync::LazyLock;
use rivet_metrics::prometheus::{
Histogram, HistogramOpts, HistogramVec, IntCounterVec, IntGauge, Opts, Registry,
};
const SQLITE_REQUEST_EXPIRED_LABELS: &[&str] = &["kind", "was_sent"];
const ENVOY_LOOP_ITER_LABELS: &[&str] = &["branch"];
const WS_TX_LOCK_LABELS: &[&str] = &["message_kind"];
const SQLITE_SEND_LABELS: &[&str] = &["kind"];
const WS_RECONNECT_LABELS: &[&str] = &["reason"];
pub struct EnvoyClientMetrics {
pub sqlite_request_expired_total: IntCounterVec,
pub sqlite_requests_inflight: IntGauge,
pub remote_sqlite_requests_inflight: IntGauge,
pub kv_requests_inflight: IntGauge,
pub envoy_loop_iteration_duration_seconds: HistogramVec,
pub ws_tx_lock_wait_duration_seconds: HistogramVec,
pub ws_tx_lock_hold_duration_seconds: HistogramVec,
pub sqlite_request_total_duration_seconds: HistogramVec,
pub sqlite_request_submit_duration_seconds: HistogramVec,
pub sqlite_request_wait_duration_seconds: HistogramVec,
pub ws_reconnect_total: IntCounterVec,
pub ws_session_duration_seconds: Histogram,
pub envoy_tx_depth: IntGauge,
}
impl EnvoyClientMetrics {
fn new() -> Self {
let sqlite_request_expired_total = IntCounterVec::new(
Opts::new(
"rivetkit_envoy_client_sqlite_request_expired_total",
"total VFS sqlite requests expired by cleanup (smoking-gun signal: was_sent=true means a request was abandoned without resolution)",
),
SQLITE_REQUEST_EXPIRED_LABELS,
)
.expect("create envoy_client_sqlite_request_expired_total counter");
let sqlite_requests_inflight = IntGauge::new(
"rivetkit_envoy_client_sqlite_requests_inflight",
"current in-flight VFS sqlite requests tracked in envoy ctx",
)
.expect("create envoy_client_sqlite_requests_inflight gauge");
let remote_sqlite_requests_inflight = IntGauge::new(
"rivetkit_envoy_client_remote_sqlite_requests_inflight",
"current in-flight remote sqlite (exec/execute) requests tracked in envoy ctx",
)
.expect("create envoy_client_remote_sqlite_requests_inflight gauge");
let kv_requests_inflight = IntGauge::new(
"rivetkit_envoy_client_kv_requests_inflight",
"current in-flight KV requests tracked in envoy ctx",
)
.expect("create envoy_client_kv_requests_inflight gauge");
let envoy_loop_iteration_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_envoy_loop_iteration_duration_seconds",
"duration of one envoy_loop select branch in seconds; long tails indicate the single fan-in loop stalled",
)
.buckets(rivet_metrics::MICRO_BUCKETS.to_vec()),
ENVOY_LOOP_ITER_LABELS,
)
.expect("create envoy_client_envoy_loop_iteration_duration_seconds histogram");
let ws_tx_lock_wait_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_ws_tx_lock_wait_duration_seconds",
"time spent waiting to acquire the ws_tx mutex in seconds",
)
.buckets(rivet_metrics::MICRO_BUCKETS.to_vec()),
WS_TX_LOCK_LABELS,
)
.expect("create envoy_client_ws_tx_lock_wait_duration_seconds histogram");
let ws_tx_lock_hold_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_ws_tx_lock_hold_duration_seconds",
"time the ws_tx mutex guard was held in seconds (covers encode + send)",
)
.buckets(rivet_metrics::MICRO_BUCKETS.to_vec()),
WS_TX_LOCK_LABELS,
)
.expect("create envoy_client_ws_tx_lock_hold_duration_seconds histogram");
let sqlite_request_total_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_sqlite_request_total_duration_seconds",
"end-to-end duration from send_sqlite_request call to oneshot resolution in seconds",
)
.buckets(rivet_metrics::BUCKETS.to_vec()),
SQLITE_SEND_LABELS,
)
.expect("create envoy_client_sqlite_request_total_duration_seconds histogram");
let sqlite_request_submit_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_sqlite_request_submit_duration_seconds",
"time from send_sqlite_request entry to envoy_tx.send return in seconds",
)
.buckets(rivet_metrics::MICRO_BUCKETS.to_vec()),
SQLITE_SEND_LABELS,
)
.expect("create envoy_client_sqlite_request_submit_duration_seconds histogram");
let sqlite_request_wait_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_envoy_client_sqlite_request_wait_duration_seconds",
"time from envoy_tx.send return to oneshot resolution in seconds",
)
.buckets(rivet_metrics::BUCKETS.to_vec()),
SQLITE_SEND_LABELS,
)
.expect("create envoy_client_sqlite_request_wait_duration_seconds histogram");
let ws_reconnect_total = IntCounterVec::new(
Opts::new(
"rivetkit_envoy_client_ws_reconnect_total",
"total websocket reconnect events by reason",
),
WS_RECONNECT_LABELS,
)
.expect("create envoy_client_ws_reconnect_total counter");
let ws_session_duration_seconds = Histogram::with_opts(
HistogramOpts::new(
"rivetkit_envoy_client_ws_session_duration_seconds",
"duration of a single websocket session from connect to disconnect in seconds",
)
.buckets(rivet_metrics::BUCKETS.to_vec()),
)
.expect("create envoy_client_ws_session_duration_seconds histogram");
let envoy_tx_depth = IntGauge::new(
"rivetkit_envoy_client_envoy_tx_depth",
"current depth of the unbounded envoy_tx mpsc between WS read task and envoy_loop",
)
.expect("create envoy_client_envoy_tx_depth gauge");
register(
&rivet_metrics::REGISTRY,
sqlite_request_expired_total.clone(),
);
register(&rivet_metrics::REGISTRY, sqlite_requests_inflight.clone());
register(
&rivet_metrics::REGISTRY,
remote_sqlite_requests_inflight.clone(),
);
register(&rivet_metrics::REGISTRY, kv_requests_inflight.clone());
register(
&rivet_metrics::REGISTRY,
envoy_loop_iteration_duration_seconds.clone(),
);
register(
&rivet_metrics::REGISTRY,
ws_tx_lock_wait_duration_seconds.clone(),
);
register(
&rivet_metrics::REGISTRY,
ws_tx_lock_hold_duration_seconds.clone(),
);
register(
&rivet_metrics::REGISTRY,
sqlite_request_total_duration_seconds.clone(),
);
register(
&rivet_metrics::REGISTRY,
sqlite_request_submit_duration_seconds.clone(),
);
register(
&rivet_metrics::REGISTRY,
sqlite_request_wait_duration_seconds.clone(),
);
register(&rivet_metrics::REGISTRY, ws_reconnect_total.clone());
register(
&rivet_metrics::REGISTRY,
ws_session_duration_seconds.clone(),
);
register(&rivet_metrics::REGISTRY, envoy_tx_depth.clone());
Self {
sqlite_request_expired_total,
sqlite_requests_inflight,
remote_sqlite_requests_inflight,
kv_requests_inflight,
envoy_loop_iteration_duration_seconds,
ws_tx_lock_wait_duration_seconds,
ws_tx_lock_hold_duration_seconds,
sqlite_request_total_duration_seconds,
sqlite_request_submit_duration_seconds,
sqlite_request_wait_duration_seconds,
ws_reconnect_total,
ws_session_duration_seconds,
envoy_tx_depth,
}
}
}
pub static METRICS: LazyLock<EnvoyClientMetrics> = LazyLock::new(EnvoyClientMetrics::new);
fn register<M>(registry: &Registry, metric: M)
where
M: rivet_metrics::prometheus::core::Collector + Clone + Send + Sync + 'static,
{
if let Err(error) = registry.register(Box::new(metric)) {
tracing::warn!(
?error,
"envoy-client metric registration failed, using existing collector"
);
}
}