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); 4],
26 pub per_leg_bytes: [(LegType, u64, u64); 4],
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 (LegType::Udp, 0, 0),
59 ],
60 per_leg_bytes: [
61 (LegType::Kcp, 0, 0),
62 (LegType::Tcp, 0, 0),
63 (LegType::FakeTls, 0, 0),
64 (LegType::Udp, 0, 0),
65 ],
66 avg_encrypt_ns: 0,
67 avg_decrypt_ns: 0,
68 encrypt_count: 0,
69 decrypt_count: 0,
70 rtt_us_path_0: 0,
71 active_sessions: 0,
72 active_streams: 0,
73 handshakes_success: 0,
74 handshakes_failure: 0,
75 handshake_latency_ns_sum: 0,
76 handshake_latency_count: 0,
77 uptime_secs: 0,
78 }
79 }
80}
81
82impl MetricsSnapshot {
83 pub(crate) fn capture(h: &HotPathAtomics) -> Self {
84 let avg_encrypt_ns = avg(h.encrypt_sum_ns(), h.encrypt_count());
85 let avg_decrypt_ns = avg(h.decrypt_sum_ns(), h.decrypt_count());
86
87 let per_leg_packets = [
88 (
89 LegType::Kcp,
90 h.packets_per_leg(DIR_SEND, LegType::Kcp),
91 h.packets_per_leg(DIR_RECV, LegType::Kcp),
92 ),
93 (
94 LegType::Tcp,
95 h.packets_per_leg(DIR_SEND, LegType::Tcp),
96 h.packets_per_leg(DIR_RECV, LegType::Tcp),
97 ),
98 (
99 LegType::FakeTls,
100 h.packets_per_leg(DIR_SEND, LegType::FakeTls),
101 h.packets_per_leg(DIR_RECV, LegType::FakeTls),
102 ),
103 (
104 LegType::Udp,
105 h.packets_per_leg(DIR_SEND, LegType::Udp),
106 h.packets_per_leg(DIR_RECV, LegType::Udp),
107 ),
108 ];
109 let per_leg_bytes = [
110 (
111 LegType::Kcp,
112 h.bytes_per_leg(DIR_SEND, LegType::Kcp),
113 h.bytes_per_leg(DIR_RECV, LegType::Kcp),
114 ),
115 (
116 LegType::Tcp,
117 h.bytes_per_leg(DIR_SEND, LegType::Tcp),
118 h.bytes_per_leg(DIR_RECV, LegType::Tcp),
119 ),
120 (
121 LegType::FakeTls,
122 h.bytes_per_leg(DIR_SEND, LegType::FakeTls),
123 h.bytes_per_leg(DIR_RECV, LegType::FakeTls),
124 ),
125 (
126 LegType::Udp,
127 h.bytes_per_leg(DIR_SEND, LegType::Udp),
128 h.bytes_per_leg(DIR_RECV, LegType::Udp),
129 ),
130 ];
131
132 Self {
133 packets_sent: h.packets_total(DIR_SEND),
134 packets_recv: h.packets_total(DIR_RECV),
135 bytes_sent: h.bytes_total(DIR_SEND),
136 bytes_recv: h.bytes_total(DIR_RECV),
137 per_leg_packets,
138 per_leg_bytes,
139 avg_encrypt_ns,
140 avg_decrypt_ns,
141 encrypt_count: h.encrypt_count(),
142 decrypt_count: h.decrypt_count(),
143 rtt_us_path_0: h.rtt_us(0),
144 active_sessions: h.active_sessions(),
145 active_streams: h.active_streams(),
146 handshakes_success: h.handshake_success_count(),
147 handshakes_failure: h.handshake_failure_count(),
148 handshake_latency_ns_sum: h.handshake_latency_ns_sum(),
149 handshake_latency_count: h.handshake_latency_count(),
150 uptime_secs: h.uptime_secs(),
151 }
152 }
153}
154
155fn avg(sum: u64, count: u64) -> u64 {
156 sum.checked_div(count).unwrap_or(0)
157}
158
159impl std::fmt::Display for MetricsSnapshot {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 write!(
162 f,
163 "tx={} rx={} bytes_tx={} bytes_rx={} encrypt={}ns decrypt={}ns sessions={} streams={} up={}s",
164 self.packets_sent,
165 self.packets_recv,
166 self.bytes_sent,
167 self.bytes_recv,
168 self.avg_encrypt_ns,
169 self.avg_decrypt_ns,
170 self.active_sessions,
171 self.active_streams,
172 self.uptime_secs,
173 )
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::observability::atomics::HotPathAtomics;
181
182 #[test]
183 fn snapshot_zero_state() {
184 let h = HotPathAtomics::new();
185 let s = MetricsSnapshot::capture(&h);
186 assert_eq!(s.packets_sent, 0);
187 assert_eq!(s.avg_encrypt_ns, 0);
188 assert_eq!(s.active_sessions, 0);
189 }
190
191 #[test]
192 fn snapshot_after_recording() {
193 let h = HotPathAtomics::new();
194 h.record_send(1024, LegType::Tcp);
195 h.record_send(2048, LegType::Kcp);
196 h.record_recv(512, LegType::Tcp);
197 h.record_encrypt_ns(100);
198 h.record_encrypt_ns(200);
199 h.session_opened();
200 h.stream_opened();
201
202 let s = MetricsSnapshot::capture(&h);
203 assert_eq!(s.packets_sent, 2);
204 assert_eq!(s.packets_recv, 1);
205 assert_eq!(s.bytes_sent, 3072);
206 assert_eq!(s.avg_encrypt_ns, 150);
207 assert_eq!(s.encrypt_count, 2);
208 assert_eq!(s.active_sessions, 1);
209 assert_eq!(s.active_streams, 1);
210 }
211
212 #[test]
213 fn display_is_one_line() {
214 let h = HotPathAtomics::new();
215 let s = MetricsSnapshot::capture(&h);
216 let text = format!("{}", s);
217 assert!(!text.contains('\n'));
218 assert!(text.contains("tx=0"));
219 }
220}