use crate::observability::atomics::{HotPathAtomics, DIR_RECV, DIR_SEND};
use crate::transport::types::LegType;
#[derive(Debug, Clone)]
pub struct MetricsSnapshot {
pub packets_sent: u64,
pub packets_recv: u64,
pub bytes_sent: u64,
pub bytes_recv: u64,
pub per_leg_packets: [(LegType, u64, u64); 3],
pub per_leg_bytes: [(LegType, u64, u64); 3],
pub avg_encrypt_ns: u64,
pub avg_decrypt_ns: u64,
pub encrypt_count: u64,
pub decrypt_count: u64,
pub rtt_us_path_0: u64,
pub active_sessions: i64,
pub active_streams: i64,
pub handshakes_success: u64,
pub handshakes_failure: u64,
pub handshake_latency_ns_sum: u64,
pub handshake_latency_count: u64,
pub uptime_secs: u64,
}
impl Default for MetricsSnapshot {
fn default() -> Self {
Self {
packets_sent: 0,
packets_recv: 0,
bytes_sent: 0,
bytes_recv: 0,
per_leg_packets: [
(LegType::Kcp, 0, 0),
(LegType::Tcp, 0, 0),
(LegType::FakeTls, 0, 0),
],
per_leg_bytes: [
(LegType::Kcp, 0, 0),
(LegType::Tcp, 0, 0),
(LegType::FakeTls, 0, 0),
],
avg_encrypt_ns: 0,
avg_decrypt_ns: 0,
encrypt_count: 0,
decrypt_count: 0,
rtt_us_path_0: 0,
active_sessions: 0,
active_streams: 0,
handshakes_success: 0,
handshakes_failure: 0,
handshake_latency_ns_sum: 0,
handshake_latency_count: 0,
uptime_secs: 0,
}
}
}
impl MetricsSnapshot {
pub(crate) fn capture(h: &HotPathAtomics) -> Self {
let avg_encrypt_ns = avg(h.encrypt_sum_ns(), h.encrypt_count());
let avg_decrypt_ns = avg(h.decrypt_sum_ns(), h.decrypt_count());
let per_leg_packets = [
(
LegType::Kcp,
h.packets_per_leg(DIR_SEND, LegType::Kcp),
h.packets_per_leg(DIR_RECV, LegType::Kcp),
),
(
LegType::Tcp,
h.packets_per_leg(DIR_SEND, LegType::Tcp),
h.packets_per_leg(DIR_RECV, LegType::Tcp),
),
(
LegType::FakeTls,
h.packets_per_leg(DIR_SEND, LegType::FakeTls),
h.packets_per_leg(DIR_RECV, LegType::FakeTls),
),
];
let per_leg_bytes = [
(
LegType::Kcp,
h.bytes_per_leg(DIR_SEND, LegType::Kcp),
h.bytes_per_leg(DIR_RECV, LegType::Kcp),
),
(
LegType::Tcp,
h.bytes_per_leg(DIR_SEND, LegType::Tcp),
h.bytes_per_leg(DIR_RECV, LegType::Tcp),
),
(
LegType::FakeTls,
h.bytes_per_leg(DIR_SEND, LegType::FakeTls),
h.bytes_per_leg(DIR_RECV, LegType::FakeTls),
),
];
Self {
packets_sent: h.packets_total(DIR_SEND),
packets_recv: h.packets_total(DIR_RECV),
bytes_sent: h.bytes_total(DIR_SEND),
bytes_recv: h.bytes_total(DIR_RECV),
per_leg_packets,
per_leg_bytes,
avg_encrypt_ns,
avg_decrypt_ns,
encrypt_count: h.encrypt_count(),
decrypt_count: h.decrypt_count(),
rtt_us_path_0: h.rtt_us(0),
active_sessions: h.active_sessions(),
active_streams: h.active_streams(),
handshakes_success: h.handshake_success_count(),
handshakes_failure: h.handshake_failure_count(),
handshake_latency_ns_sum: h.handshake_latency_ns_sum(),
handshake_latency_count: h.handshake_latency_count(),
uptime_secs: h.uptime_secs(),
}
}
}
fn avg(sum: u64, count: u64) -> u64 {
sum.checked_div(count).unwrap_or(0)
}
impl std::fmt::Display for MetricsSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"tx={} rx={} bytes_tx={} bytes_rx={} encrypt={}ns decrypt={}ns sessions={} streams={} up={}s",
self.packets_sent,
self.packets_recv,
self.bytes_sent,
self.bytes_recv,
self.avg_encrypt_ns,
self.avg_decrypt_ns,
self.active_sessions,
self.active_streams,
self.uptime_secs,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::observability::atomics::HotPathAtomics;
#[test]
fn snapshot_zero_state() {
let h = HotPathAtomics::new();
let s = MetricsSnapshot::capture(&h);
assert_eq!(s.packets_sent, 0);
assert_eq!(s.avg_encrypt_ns, 0);
assert_eq!(s.active_sessions, 0);
}
#[test]
fn snapshot_after_recording() {
let h = HotPathAtomics::new();
h.record_send(1024, LegType::Tcp);
h.record_send(2048, LegType::Kcp);
h.record_recv(512, LegType::Tcp);
h.record_encrypt_ns(100);
h.record_encrypt_ns(200);
h.session_opened();
h.stream_opened();
let s = MetricsSnapshot::capture(&h);
assert_eq!(s.packets_sent, 2);
assert_eq!(s.packets_recv, 1);
assert_eq!(s.bytes_sent, 3072);
assert_eq!(s.avg_encrypt_ns, 150);
assert_eq!(s.encrypt_count, 2);
assert_eq!(s.active_sessions, 1);
assert_eq!(s.active_streams, 1);
}
#[test]
fn display_is_one_line() {
let h = HotPathAtomics::new();
let s = MetricsSnapshot::capture(&h);
let text = format!("{}", s);
assert!(!text.contains('\n'));
assert!(text.contains("tx=0"));
}
}