phantom_protocol/observability/
mod.rs1pub(crate) mod atomics;
24pub mod attrs;
25pub(crate) mod bridge;
26pub mod config;
27pub(crate) mod instruments;
28pub mod snapshot;
29
30pub use attrs::{
31 leg_str, AeadAlgorithm, CookieOutcome, Direction, EarlyDataOutcome, FallbackReason,
32 HandshakeOutcome, PathValidationOutcome, PowOutcome, ProtocolVersion, ReplayReason,
33 ResumptionMode,
34};
35pub use config::{HistogramConfig, ObservabilityConfig, ObservabilityConfigBuilder};
36pub use snapshot::MetricsSnapshot;
37
38use crate::transport::types::LegType;
39use atomics::HotPathAtomics;
40use instruments::PhantomInstruments;
41use std::sync::Arc;
42
43#[derive(Debug)]
52pub struct Observability {
53 config: ObservabilityConfig,
54 atomics: Arc<HotPathAtomics>,
55 instruments: PhantomInstruments,
56}
57
58impl Observability {
59 pub fn new(config: ObservabilityConfig) -> Arc<Self> {
73 let instruments = PhantomInstruments::new(&config);
74 let atomics = Arc::new(HotPathAtomics::new());
75 bridge::register_observables(&atomics, &config);
80 Arc::new(Self {
81 config,
82 atomics,
83 instruments,
84 })
85 }
86
87 pub fn config(&self) -> &ObservabilityConfig {
89 &self.config
90 }
91
92 pub fn snapshot(&self) -> MetricsSnapshot {
94 MetricsSnapshot::capture(&self.atomics)
95 }
96
97 #[inline]
100 pub fn record_send(&self, bytes: usize, leg: LegType) {
101 self.atomics.record_send(bytes, leg);
102 }
103
104 #[inline]
105 pub fn record_recv(&self, bytes: usize, leg: LegType) {
106 self.atomics.record_recv(bytes, leg);
107 }
108
109 #[inline]
110 pub fn record_encrypt_ns(&self, duration_ns: u64) {
111 self.atomics.record_encrypt_ns(duration_ns);
112 }
113
114 #[inline]
115 pub fn record_decrypt_ns(&self, duration_ns: u64) {
116 self.atomics.record_decrypt_ns(duration_ns);
117 }
118
119 #[inline]
120 pub fn record_rtt_us(&self, rtt_us: u64, path_id: u8) {
121 self.atomics.record_rtt_us(rtt_us, path_id);
122 }
123
124 #[inline]
129 pub fn session_opened(&self, leg: LegType) {
130 self.atomics.session_opened();
131 self.instruments.session_opened(leg);
132 }
133
134 #[inline]
135 pub fn session_closed(&self, leg: LegType) {
136 self.atomics.session_closed();
137 self.instruments.session_closed(leg);
138 }
139
140 #[inline]
141 pub fn stream_opened(&self) {
142 self.atomics.stream_opened();
143 self.instruments.stream_opened();
144 }
145
146 #[inline]
147 pub fn stream_closed(&self) {
148 self.atomics.stream_closed();
149 self.instruments.stream_closed();
150 }
151
152 pub fn record_handshake_success(&self, duration_ns: u64) {
160 self.atomics.record_handshake_success(duration_ns);
161 }
162
163 #[inline]
168 pub fn record_handshake_failure(&self) {
169 self.atomics.record_handshake_failure();
170 }
171
172 pub fn record_handshake(
180 &self,
181 duration: std::time::Duration,
182 outcome: HandshakeOutcome,
183 leg: LegType,
184 cipher: AeadAlgorithm,
185 version: ProtocolVersion,
186 ) {
187 let duration_ns = u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX);
188 match outcome {
189 HandshakeOutcome::Success => self.atomics.record_handshake_success(duration_ns),
190 HandshakeOutcome::Failure => self.atomics.record_handshake_failure(),
191 }
192 self.instruments.record_handshake_duration(
193 duration.as_secs_f64(),
194 outcome,
195 leg,
196 cipher,
197 version,
198 );
199 }
200
201 pub fn record_path_validation(
203 &self,
204 duration: std::time::Duration,
205 path_id: u8,
206 outcome: PathValidationOutcome,
207 ) {
208 self.instruments
209 .record_path_validation_duration(duration.as_secs_f64(), path_id, outcome);
210 }
211
212 pub fn record_resumption(&self, mode: ResumptionMode, accepted: bool) {
213 self.instruments.record_resumption(mode, accepted);
214 }
215
216 #[inline]
217 pub fn record_replay_rejected(&self, reason: ReplayReason) {
218 self.instruments.record_replay_rejected(reason);
219 }
220
221 #[inline]
222 pub fn record_aead_failure(&self, leg: LegType, algorithm: AeadAlgorithm) {
223 self.instruments.record_aead_failure(leg, algorithm);
224 }
225
226 #[inline]
227 pub fn record_unencrypted_dropped(&self, leg: LegType) {
228 self.instruments.record_unencrypted_dropped(leg);
229 }
230
231 pub fn record_path_migration(&self, from: u8, to: u8) {
232 self.instruments.record_path_migration(from, to);
233 }
234
235 pub fn record_cookie(&self, outcome: CookieOutcome) {
236 self.instruments.record_cookie(outcome);
237 }
238
239 pub fn record_pow(&self, outcome: PowOutcome, difficulty: u8) {
240 self.instruments.record_pow(outcome, difficulty);
241 }
242
243 pub fn record_early_data(&self, outcome: EarlyDataOutcome) {
244 self.instruments.record_early_data(outcome);
245 }
246
247 pub fn record_rekey(&self, direction: Direction) {
248 self.instruments.record_rekey(direction);
249 }
250
251 pub fn record_fallback(&self, from_leg: LegType, to_leg: LegType, reason: FallbackReason) {
252 self.instruments.record_fallback(from_leg, to_leg, reason);
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn default_observability_has_default_config() {
262 let obs = Observability::new(ObservabilityConfig::default());
263 assert_eq!(obs.config().namespace.as_ref(), "phantom");
264 }
265
266 #[test]
267 fn new_returns_arc_with_provided_config() {
268 let cfg = ObservabilityConfig::builder().namespace("myapp").build();
269 let obs = Observability::new(cfg);
270 assert_eq!(obs.config().namespace.as_ref(), "myapp");
271 let obs2 = obs.clone();
273 assert_eq!(obs2.config().namespace.as_ref(), "myapp");
274 }
275
276 #[test]
277 fn record_send_round_trips_through_snapshot() {
278 let obs = Observability::new(ObservabilityConfig::default());
279 obs.record_send(1024, LegType::Tcp);
280 obs.record_send(2048, LegType::Tcp);
281 obs.record_recv(512, LegType::Kcp);
282 obs.record_encrypt_ns(100);
283 obs.record_encrypt_ns(300);
284 obs.session_opened(LegType::Tcp);
285 obs.stream_opened();
286 obs.stream_opened();
287 obs.record_rtt_us(5_000, 0);
288
289 let s = obs.snapshot();
290 assert_eq!(s.packets_sent, 2);
291 assert_eq!(s.packets_recv, 1);
292 assert_eq!(s.bytes_sent, 3072);
293 assert_eq!(s.bytes_recv, 512);
294 assert_eq!(s.avg_encrypt_ns, 200);
295 assert_eq!(s.encrypt_count, 2);
296 assert_eq!(s.active_sessions, 1);
297 assert_eq!(s.active_streams, 2);
298 assert_eq!(s.rtt_us_path_0, 5_000);
299 }
300}