phantom_protocol/observability/
snapshot.rs1use crate::observability::atomics::{HotPathAtomics, DIR_RECV, DIR_SEND};
14use crate::transport::types::LegType;
15
16#[derive(Debug, Clone)]
18pub struct MetricsSnapshot {
19 pub packets_sent: u64,
20 pub packets_recv: u64,
21 pub bytes_sent: u64,
22 pub bytes_recv: u64,
23
24 pub per_leg_packets: [(LegType, u64, u64); 3],
26 pub per_leg_bytes: [(LegType, u64, u64); 3],
28
29 pub avg_encrypt_ns: u64,
30 pub avg_decrypt_ns: u64,
31 pub encrypt_count: u64,
32 pub decrypt_count: u64,
33
34 pub rtt_us_path_0: u64,
35
36 pub active_sessions: i64,
37 pub active_streams: i64,
38
39 pub handshakes_success: u64,
40 pub handshakes_failure: u64,
41 pub handshake_latency_ns_sum: u64,
42 pub handshake_latency_count: u64,
43
44 pub uptime_secs: u64,
45}
46
47impl Default for MetricsSnapshot {
48 fn default() -> Self {
49 Self {
50 packets_sent: 0,
51 packets_recv: 0,
52 bytes_sent: 0,
53 bytes_recv: 0,
54 per_leg_packets: [
55 (LegType::Kcp, 0, 0),
56 (LegType::Tcp, 0, 0),
57 (LegType::FakeTls, 0, 0),
58 ],
59 per_leg_bytes: [
60 (LegType::Kcp, 0, 0),
61 (LegType::Tcp, 0, 0),
62 (LegType::FakeTls, 0, 0),
63 ],
64 avg_encrypt_ns: 0,
65 avg_decrypt_ns: 0,
66 encrypt_count: 0,
67 decrypt_count: 0,
68 rtt_us_path_0: 0,
69 active_sessions: 0,
70 active_streams: 0,
71 handshakes_success: 0,
72 handshakes_failure: 0,
73 handshake_latency_ns_sum: 0,
74 handshake_latency_count: 0,
75 uptime_secs: 0,
76 }
77 }
78}
79
80impl MetricsSnapshot {
81 pub(crate) fn capture(h: &HotPathAtomics) -> Self {
82 let avg_encrypt_ns = avg(h.encrypt_sum_ns(), h.encrypt_count());
83 let avg_decrypt_ns = avg(h.decrypt_sum_ns(), h.decrypt_count());
84
85 let per_leg_packets = [
86 (
87 LegType::Kcp,
88 h.packets_per_leg(DIR_SEND, LegType::Kcp),
89 h.packets_per_leg(DIR_RECV, LegType::Kcp),
90 ),
91 (
92 LegType::Tcp,
93 h.packets_per_leg(DIR_SEND, LegType::Tcp),
94 h.packets_per_leg(DIR_RECV, LegType::Tcp),
95 ),
96 (
97 LegType::FakeTls,
98 h.packets_per_leg(DIR_SEND, LegType::FakeTls),
99 h.packets_per_leg(DIR_RECV, LegType::FakeTls),
100 ),
101 ];
102 let per_leg_bytes = [
103 (
104 LegType::Kcp,
105 h.bytes_per_leg(DIR_SEND, LegType::Kcp),
106 h.bytes_per_leg(DIR_RECV, LegType::Kcp),
107 ),
108 (
109 LegType::Tcp,
110 h.bytes_per_leg(DIR_SEND, LegType::Tcp),
111 h.bytes_per_leg(DIR_RECV, LegType::Tcp),
112 ),
113 (
114 LegType::FakeTls,
115 h.bytes_per_leg(DIR_SEND, LegType::FakeTls),
116 h.bytes_per_leg(DIR_RECV, LegType::FakeTls),
117 ),
118 ];
119
120 Self {
121 packets_sent: h.packets_total(DIR_SEND),
122 packets_recv: h.packets_total(DIR_RECV),
123 bytes_sent: h.bytes_total(DIR_SEND),
124 bytes_recv: h.bytes_total(DIR_RECV),
125 per_leg_packets,
126 per_leg_bytes,
127 avg_encrypt_ns,
128 avg_decrypt_ns,
129 encrypt_count: h.encrypt_count(),
130 decrypt_count: h.decrypt_count(),
131 rtt_us_path_0: h.rtt_us(0),
132 active_sessions: h.active_sessions(),
133 active_streams: h.active_streams(),
134 handshakes_success: h.handshake_success_count(),
135 handshakes_failure: h.handshake_failure_count(),
136 handshake_latency_ns_sum: h.handshake_latency_ns_sum(),
137 handshake_latency_count: h.handshake_latency_count(),
138 uptime_secs: h.uptime_secs(),
139 }
140 }
141}
142
143fn avg(sum: u64, count: u64) -> u64 {
144 sum.checked_div(count).unwrap_or(0)
145}
146
147impl std::fmt::Display for MetricsSnapshot {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(
150 f,
151 "tx={} rx={} bytes_tx={} bytes_rx={} encrypt={}ns decrypt={}ns sessions={} streams={} up={}s",
152 self.packets_sent,
153 self.packets_recv,
154 self.bytes_sent,
155 self.bytes_recv,
156 self.avg_encrypt_ns,
157 self.avg_decrypt_ns,
158 self.active_sessions,
159 self.active_streams,
160 self.uptime_secs,
161 )
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::observability::atomics::HotPathAtomics;
169
170 #[test]
171 fn snapshot_zero_state() {
172 let h = HotPathAtomics::new();
173 let s = MetricsSnapshot::capture(&h);
174 assert_eq!(s.packets_sent, 0);
175 assert_eq!(s.avg_encrypt_ns, 0);
176 assert_eq!(s.active_sessions, 0);
177 }
178
179 #[test]
180 fn snapshot_after_recording() {
181 let h = HotPathAtomics::new();
182 h.record_send(1024, LegType::Tcp);
183 h.record_send(2048, LegType::Kcp);
184 h.record_recv(512, LegType::Tcp);
185 h.record_encrypt_ns(100);
186 h.record_encrypt_ns(200);
187 h.session_opened();
188 h.stream_opened();
189
190 let s = MetricsSnapshot::capture(&h);
191 assert_eq!(s.packets_sent, 2);
192 assert_eq!(s.packets_recv, 1);
193 assert_eq!(s.bytes_sent, 3072);
194 assert_eq!(s.avg_encrypt_ns, 150);
195 assert_eq!(s.encrypt_count, 2);
196 assert_eq!(s.active_sessions, 1);
197 assert_eq!(s.active_streams, 1);
198 }
199
200 #[test]
201 fn display_is_one_line() {
202 let h = HotPathAtomics::new();
203 let s = MetricsSnapshot::capture(&h);
204 let text = format!("{}", s);
205 assert!(!text.contains('\n'));
206 assert!(text.contains("tx=0"));
207 }
208}