feat(metrics): add frontend and backend protocol distribution metrics

This commit is contained in:
2026-04-04 16:52:25 +00:00
parent a55ff20391
commit b04eb0ab17
7 changed files with 295 additions and 2 deletions

View File

@@ -33,6 +33,9 @@ pub struct Metrics {
pub total_datagrams_out: u64,
// Protocol detection cache snapshot (populated by RustProxy from HttpProxyService)
pub detected_protocols: Vec<ProtocolCacheEntryMetric>,
// Protocol distribution for frontend (client→proxy) and backend (proxy→upstream)
pub frontend_protocols: ProtocolMetrics,
pub backend_protocols: ProtocolMetrics,
}
/// Per-route metrics.
@@ -99,6 +102,23 @@ pub struct ProtocolCacheEntryMetric {
pub h3_consecutive_failures: Option<u32>,
}
/// Protocol distribution metrics for frontend (client→proxy) and backend (proxy→upstream).
/// Tracks active and total counts for each protocol category.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ProtocolMetrics {
pub h1_active: u64,
pub h1_total: u64,
pub h2_active: u64,
pub h2_total: u64,
pub h3_active: u64,
pub h3_total: u64,
pub ws_active: u64,
pub ws_total: u64,
pub other_active: u64,
pub other_total: u64,
}
/// Statistics snapshot.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -170,6 +190,30 @@ pub struct MetricsCollector {
total_datagrams_in: AtomicU64,
total_datagrams_out: AtomicU64,
// ── Frontend protocol tracking (h1/h2/h3/ws/other) ──
frontend_h1_active: AtomicU64,
frontend_h1_total: AtomicU64,
frontend_h2_active: AtomicU64,
frontend_h2_total: AtomicU64,
frontend_h3_active: AtomicU64,
frontend_h3_total: AtomicU64,
frontend_ws_active: AtomicU64,
frontend_ws_total: AtomicU64,
frontend_other_active: AtomicU64,
frontend_other_total: AtomicU64,
// ── Backend protocol tracking (h1/h2/h3/ws/other) ──
backend_h1_active: AtomicU64,
backend_h1_total: AtomicU64,
backend_h2_active: AtomicU64,
backend_h2_total: AtomicU64,
backend_h3_active: AtomicU64,
backend_h3_total: AtomicU64,
backend_ws_active: AtomicU64,
backend_ws_total: AtomicU64,
backend_other_active: AtomicU64,
backend_other_total: AtomicU64,
// ── Lock-free pending throughput counters (hot path) ──
global_pending_tp_in: AtomicU64,
global_pending_tp_out: AtomicU64,
@@ -221,6 +265,26 @@ impl MetricsCollector {
total_http_requests: AtomicU64::new(0),
pending_http_requests: AtomicU64::new(0),
http_request_throughput: Mutex::new(ThroughputTracker::new(retention_seconds)),
frontend_h1_active: AtomicU64::new(0),
frontend_h1_total: AtomicU64::new(0),
frontend_h2_active: AtomicU64::new(0),
frontend_h2_total: AtomicU64::new(0),
frontend_h3_active: AtomicU64::new(0),
frontend_h3_total: AtomicU64::new(0),
frontend_ws_active: AtomicU64::new(0),
frontend_ws_total: AtomicU64::new(0),
frontend_other_active: AtomicU64::new(0),
frontend_other_total: AtomicU64::new(0),
backend_h1_active: AtomicU64::new(0),
backend_h1_total: AtomicU64::new(0),
backend_h2_active: AtomicU64::new(0),
backend_h2_total: AtomicU64::new(0),
backend_h3_active: AtomicU64::new(0),
backend_h3_total: AtomicU64::new(0),
backend_ws_active: AtomicU64::new(0),
backend_ws_total: AtomicU64::new(0),
backend_other_active: AtomicU64::new(0),
backend_other_total: AtomicU64::new(0),
global_pending_tp_in: AtomicU64::new(0),
global_pending_tp_out: AtomicU64::new(0),
route_pending_tp: DashMap::new(),
@@ -411,6 +475,62 @@ impl MetricsCollector {
self.total_datagrams_out.fetch_add(1, Ordering::Relaxed);
}
// ── Frontend/backend protocol distribution tracking ──
/// Get the (active, total) counter pair for a frontend protocol.
fn frontend_proto_counters(&self, proto: &str) -> (&AtomicU64, &AtomicU64) {
match proto {
"h2" => (&self.frontend_h2_active, &self.frontend_h2_total),
"h3" => (&self.frontend_h3_active, &self.frontend_h3_total),
"ws" => (&self.frontend_ws_active, &self.frontend_ws_total),
"other" => (&self.frontend_other_active, &self.frontend_other_total),
_ => (&self.frontend_h1_active, &self.frontend_h1_total), // h1 + default
}
}
/// Get the (active, total) counter pair for a backend protocol.
fn backend_proto_counters(&self, proto: &str) -> (&AtomicU64, &AtomicU64) {
match proto {
"h2" => (&self.backend_h2_active, &self.backend_h2_total),
"h3" => (&self.backend_h3_active, &self.backend_h3_total),
"ws" => (&self.backend_ws_active, &self.backend_ws_total),
"other" => (&self.backend_other_active, &self.backend_other_total),
_ => (&self.backend_h1_active, &self.backend_h1_total), // h1 + default
}
}
/// Record a frontend request/connection opened with a given protocol.
pub fn frontend_protocol_opened(&self, proto: &str) {
let (active, total) = self.frontend_proto_counters(proto);
active.fetch_add(1, Ordering::Relaxed);
total.fetch_add(1, Ordering::Relaxed);
}
/// Record a frontend request/connection closed with a given protocol.
pub fn frontend_protocol_closed(&self, proto: &str) {
let (active, _) = self.frontend_proto_counters(proto);
let val = active.load(Ordering::Relaxed);
if val > 0 {
active.fetch_sub(1, Ordering::Relaxed);
}
}
/// Record a backend connection opened with a given protocol.
pub fn backend_protocol_opened(&self, proto: &str) {
let (active, total) = self.backend_proto_counters(proto);
active.fetch_add(1, Ordering::Relaxed);
total.fetch_add(1, Ordering::Relaxed);
}
/// Record a backend connection closed with a given protocol.
pub fn backend_protocol_closed(&self, proto: &str) {
let (active, _) = self.backend_proto_counters(proto);
let val = active.load(Ordering::Relaxed);
if val > 0 {
active.fetch_sub(1, Ordering::Relaxed);
}
}
// ── Per-backend recording methods ──
/// Record a successful backend connection with its connect duration.
@@ -866,6 +986,30 @@ impl MetricsCollector {
total_datagrams_in: self.total_datagrams_in.load(Ordering::Relaxed),
total_datagrams_out: self.total_datagrams_out.load(Ordering::Relaxed),
detected_protocols: vec![],
frontend_protocols: ProtocolMetrics {
h1_active: self.frontend_h1_active.load(Ordering::Relaxed),
h1_total: self.frontend_h1_total.load(Ordering::Relaxed),
h2_active: self.frontend_h2_active.load(Ordering::Relaxed),
h2_total: self.frontend_h2_total.load(Ordering::Relaxed),
h3_active: self.frontend_h3_active.load(Ordering::Relaxed),
h3_total: self.frontend_h3_total.load(Ordering::Relaxed),
ws_active: self.frontend_ws_active.load(Ordering::Relaxed),
ws_total: self.frontend_ws_total.load(Ordering::Relaxed),
other_active: self.frontend_other_active.load(Ordering::Relaxed),
other_total: self.frontend_other_total.load(Ordering::Relaxed),
},
backend_protocols: ProtocolMetrics {
h1_active: self.backend_h1_active.load(Ordering::Relaxed),
h1_total: self.backend_h1_total.load(Ordering::Relaxed),
h2_active: self.backend_h2_active.load(Ordering::Relaxed),
h2_total: self.backend_h2_total.load(Ordering::Relaxed),
h3_active: self.backend_h3_active.load(Ordering::Relaxed),
h3_total: self.backend_h3_total.load(Ordering::Relaxed),
ws_active: self.backend_ws_active.load(Ordering::Relaxed),
ws_total: self.backend_ws_total.load(Ordering::Relaxed),
other_active: self.backend_other_active.load(Ordering::Relaxed),
other_total: self.backend_other_total.load(Ordering::Relaxed),
},
}
}
}