WZP-P2-T5-S1: Relay Prometheus /metrics - RelayMetrics: active_sessions, active_rooms, packets/bytes_forwarded, auth_attempts (ok/fail), handshake_duration histogram - --metrics-port flag spawns HTTP server - Wired into auth, handshake, session, and packet forwarding paths - 2 tests WZP-P2-T5-S3: Web bridge Prometheus /metrics - WebMetrics: active_connections, frames_bridged (up/down), auth_failures, handshake_latency histogram - Added /metrics route to existing axum app - Wired into WS connect/disconnect, auth, handshake, send/recv loops - 2 tests WZP-P2-T5-S4: Client --metrics-file JSONL - ClientMetricsSnapshot with all telemetry fields - MetricsWriter: writes one JSON line per second to file - snapshot_from_stats() converts JitterStats to snapshot - --metrics-file <path> flag - 3 tests 223 tests passing across all crates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
131 lines
3.8 KiB
Rust
131 lines
3.8 KiB
Rust
//! Prometheus metrics for the WZP web bridge.
|
|
|
|
use prometheus::{
|
|
Encoder, Histogram, HistogramOpts, IntCounter, IntCounterVec, IntGauge, Opts, Registry,
|
|
TextEncoder,
|
|
};
|
|
|
|
/// Holds all Prometheus metrics for the web bridge.
|
|
#[derive(Clone)]
|
|
pub struct WebMetrics {
|
|
pub active_connections: IntGauge,
|
|
pub frames_bridged: IntCounterVec,
|
|
pub auth_failures: IntCounter,
|
|
pub handshake_latency: Histogram,
|
|
registry: Registry,
|
|
}
|
|
|
|
impl WebMetrics {
|
|
/// Create and register all web bridge metrics.
|
|
pub fn new() -> Self {
|
|
let registry = Registry::new();
|
|
|
|
let active_connections = IntGauge::with_opts(
|
|
Opts::new("wzp_web_active_connections", "Current WebSocket connections"),
|
|
)
|
|
.expect("metric");
|
|
registry
|
|
.register(Box::new(active_connections.clone()))
|
|
.expect("register");
|
|
|
|
let frames_bridged = IntCounterVec::new(
|
|
Opts::new("wzp_web_frames_bridged_total", "Audio frames bridged"),
|
|
&["direction"],
|
|
)
|
|
.expect("metric");
|
|
registry
|
|
.register(Box::new(frames_bridged.clone()))
|
|
.expect("register");
|
|
|
|
let auth_failures = IntCounter::with_opts(
|
|
Opts::new("wzp_web_auth_failures_total", "Browser auth failures"),
|
|
)
|
|
.expect("metric");
|
|
registry
|
|
.register(Box::new(auth_failures.clone()))
|
|
.expect("register");
|
|
|
|
let handshake_latency = Histogram::with_opts(
|
|
HistogramOpts::new(
|
|
"wzp_web_handshake_latency_seconds",
|
|
"Relay handshake time",
|
|
)
|
|
.buckets(vec![0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]),
|
|
)
|
|
.expect("metric");
|
|
registry
|
|
.register(Box::new(handshake_latency.clone()))
|
|
.expect("register");
|
|
|
|
Self {
|
|
active_connections,
|
|
frames_bridged,
|
|
auth_failures,
|
|
handshake_latency,
|
|
registry,
|
|
}
|
|
}
|
|
|
|
/// Encode all metrics as Prometheus text exposition format.
|
|
pub fn gather(&self) -> String {
|
|
let encoder = TextEncoder::new();
|
|
let metric_families = self.registry.gather();
|
|
let mut buf = Vec::new();
|
|
encoder.encode(&metric_families, &mut buf).unwrap();
|
|
String::from_utf8(buf).unwrap()
|
|
}
|
|
}
|
|
|
|
/// Axum handler that returns Prometheus text metrics.
|
|
pub async fn metrics_handler(
|
|
axum::extract::State(state): axum::extract::State<super::AppState>,
|
|
) -> String {
|
|
state.metrics.gather()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn web_metrics_register() {
|
|
let m = WebMetrics::new();
|
|
// Touch CounterVec labels so they appear in output
|
|
m.frames_bridged.with_label_values(&["up"]);
|
|
m.frames_bridged.with_label_values(&["down"]);
|
|
let output = m.gather();
|
|
assert!(
|
|
output.contains("wzp_web_active_connections"),
|
|
"missing active_connections"
|
|
);
|
|
assert!(
|
|
output.contains("wzp_web_frames_bridged_total"),
|
|
"missing frames_bridged"
|
|
);
|
|
assert!(
|
|
output.contains("wzp_web_auth_failures_total"),
|
|
"missing auth_failures"
|
|
);
|
|
assert!(
|
|
output.contains("wzp_web_handshake_latency_seconds"),
|
|
"missing handshake_latency"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn web_metrics_track_connections() {
|
|
let m = WebMetrics::new();
|
|
assert_eq!(m.active_connections.get(), 0);
|
|
|
|
m.active_connections.inc();
|
|
m.active_connections.inc();
|
|
assert_eq!(m.active_connections.get(), 2);
|
|
|
|
m.active_connections.dec();
|
|
assert_eq!(m.active_connections.get(), 1);
|
|
|
|
let output = m.gather();
|
|
assert!(output.contains("wzp_web_active_connections 1"));
|
|
}
|
|
}
|