1use super::algorithms::{DualEwma, SrttEstimator, compute_etx};
9use super::report::ReceiverReport;
10use std::time::Instant;
11use tracing::trace;
12
13pub struct MmpMetrics {
19 pub srtt: SrttEstimator,
21
22 pub rtt_trend: DualEwma,
24 pub loss_trend: DualEwma,
25 pub goodput_trend: DualEwma,
26 pub jitter_trend: DualEwma,
27 pub etx_trend: DualEwma,
28
29 pub delivery_ratio_forward: f64,
31 pub delivery_ratio_reverse: f64,
33 pub etx: f64,
35
36 pub goodput_bps: f64,
38
39 prev_rr_cum_packets: u64,
42 prev_rr_cum_bytes: u64,
43 prev_rr_highest_counter: u64,
44 prev_rr_ecn_ce: u32,
45 prev_rr_reorder: u32,
46 prev_rr_time: Option<Instant>,
48 last_srtt_update: Option<Instant>,
50 has_prev_rr: bool,
52 last_forward_counter_span: u64,
54 last_forward_loss_rate: Option<f64>,
56 forward_loss_window_span: u64,
59 forward_loss_window_lost: u64,
60
61 prev_reverse_packets: u64,
64 prev_reverse_highest: u64,
66 has_prev_reverse: bool,
68}
69
70impl MmpMetrics {
71 pub fn reset_for_rekey(&mut self) {
77 self.prev_rr_cum_packets = 0;
78 self.prev_rr_cum_bytes = 0;
79 self.prev_rr_highest_counter = 0;
80 self.prev_rr_ecn_ce = 0;
81 self.prev_rr_reorder = 0;
82 self.prev_rr_time = None;
83 self.has_prev_rr = false;
84 self.last_forward_counter_span = 0;
85 self.last_forward_loss_rate = None;
86 self.forward_loss_window_span = 0;
87 self.forward_loss_window_lost = 0;
88 self.delivery_ratio_forward = 1.0;
89 self.prev_reverse_packets = 0;
90 self.prev_reverse_highest = 0;
91 self.has_prev_reverse = false;
92 }
94
95 pub fn new() -> Self {
96 Self {
97 srtt: SrttEstimator::new(),
98 rtt_trend: DualEwma::new(),
99 loss_trend: DualEwma::new(),
100 goodput_trend: DualEwma::new(),
101 jitter_trend: DualEwma::new(),
102 etx_trend: DualEwma::new(),
103 delivery_ratio_forward: 1.0,
104 delivery_ratio_reverse: 1.0,
105 etx: 1.0,
106 goodput_bps: 0.0,
107 prev_rr_cum_packets: 0,
108 prev_rr_cum_bytes: 0,
109 prev_rr_highest_counter: 0,
110 prev_rr_ecn_ce: 0,
111 prev_rr_reorder: 0,
112 prev_rr_time: None,
113 last_srtt_update: None,
114 has_prev_rr: false,
115 last_forward_counter_span: 0,
116 last_forward_loss_rate: None,
117 forward_loss_window_span: 0,
118 forward_loss_window_lost: 0,
119 prev_reverse_packets: 0,
120 prev_reverse_highest: 0,
121 has_prev_reverse: false,
122 }
123 }
124
125 pub fn process_receiver_report(
133 &mut self,
134 rr: &ReceiverReport,
135 our_timestamp_ms: u32,
136 now: Instant,
137 ) -> bool {
138 let had_srtt = self.srtt.initialized();
139
140 if self.has_prev_rr {
141 let counters_regressed = rr.highest_counter < self.prev_rr_highest_counter
142 || rr.cumulative_packets_recv < self.prev_rr_cum_packets
143 || rr.cumulative_bytes_recv < self.prev_rr_cum_bytes
144 || rr.ecn_ce_count < self.prev_rr_ecn_ce
145 || rr.cumulative_reorder_count < self.prev_rr_reorder;
146 let duplicate_counters = rr.highest_counter == self.prev_rr_highest_counter
147 && rr.cumulative_packets_recv == self.prev_rr_cum_packets
148 && rr.cumulative_bytes_recv == self.prev_rr_cum_bytes
149 && rr.ecn_ce_count == self.prev_rr_ecn_ce
150 && rr.cumulative_reorder_count == self.prev_rr_reorder;
151 if counters_regressed || duplicate_counters {
152 trace!(
153 highest_counter = rr.highest_counter,
154 prev_highest_counter = self.prev_rr_highest_counter,
155 cumulative_packets_recv = rr.cumulative_packets_recv,
156 prev_cumulative_packets_recv = self.prev_rr_cum_packets,
157 cumulative_bytes_recv = rr.cumulative_bytes_recv,
158 prev_cumulative_bytes_recv = self.prev_rr_cum_bytes,
159 "Ignoring stale MMP ReceiverReport"
160 );
161 return false;
162 }
163 }
164
165 if rr.timestamp_echo > 0 {
168 let echo_ms = rr.timestamp_echo;
169 let dwell_ms = u32::from(rr.dwell_time);
170 let rtt_sample_ms = echo_ms
171 .checked_add(dwell_ms)
172 .and_then(|send_done_ms| our_timestamp_ms.checked_sub(send_done_ms));
173
174 match rtt_sample_ms {
175 Some(rtt_ms) if rtt_ms > 0 => {
176 let rtt_us = (rtt_ms as i64) * 1000;
177 trace!(
178 our_ts = our_timestamp_ms,
179 echo = echo_ms,
180 dwell = dwell_ms,
181 rtt_ms = rtt_ms,
182 srtt_ms = self.srtt.srtt_us() as f64 / 1000.0,
183 "RTT sample from timestamp echo"
184 );
185 self.srtt.update(rtt_us);
186 self.last_srtt_update = Some(now);
187 self.rtt_trend.update(rtt_us as f64);
188 }
189 _ => {
190 trace!(
191 our_ts = our_timestamp_ms,
192 echo = echo_ms,
193 dwell = dwell_ms,
194 "Ignoring invalid MMP RTT sample"
195 );
196 }
197 }
198 }
199
200 self.last_forward_counter_span = 0;
203 self.last_forward_loss_rate = None;
204 if self.has_prev_rr {
205 let counter_span = rr
206 .highest_counter
207 .saturating_sub(self.prev_rr_highest_counter);
208 let packets_delta = rr
209 .cumulative_packets_recv
210 .saturating_sub(self.prev_rr_cum_packets);
211
212 if counter_span > 0 {
213 let delivery = (packets_delta as f64) / (counter_span as f64);
214 self.delivery_ratio_forward = delivery.clamp(0.0, 1.0);
215 let loss_rate = 1.0 - self.delivery_ratio_forward;
216 self.last_forward_counter_span = counter_span;
217 self.last_forward_loss_rate = Some(loss_rate);
218 self.forward_loss_window_span =
219 self.forward_loss_window_span.saturating_add(counter_span);
220 self.forward_loss_window_lost = self
221 .forward_loss_window_lost
222 .saturating_add(counter_span.saturating_sub(packets_delta));
223 self.loss_trend.update(loss_rate);
224 self.etx = compute_etx(self.delivery_ratio_forward, self.delivery_ratio_reverse);
225 self.etx_trend.update(self.etx);
226 }
227 }
228
229 if self.has_prev_rr {
231 let bytes_delta = rr
232 .cumulative_bytes_recv
233 .saturating_sub(self.prev_rr_cum_bytes);
234 self.goodput_trend.update(bytes_delta as f64);
235
236 if let Some(prev_time) = self.prev_rr_time {
238 let elapsed = now.duration_since(prev_time);
239 let secs = elapsed.as_secs_f64();
240 if secs > 0.0 {
241 let bps = bytes_delta as f64 / secs;
242 if self.goodput_bps == 0.0 {
244 self.goodput_bps = bps;
245 } else {
246 self.goodput_bps += (bps - self.goodput_bps) * 0.25;
247 }
248 }
249 }
250 }
251
252 self.jitter_trend.update(rr.jitter as f64);
254
255 self.prev_rr_cum_packets = rr.cumulative_packets_recv;
257 self.prev_rr_cum_bytes = rr.cumulative_bytes_recv;
258 self.prev_rr_highest_counter = rr.highest_counter;
259 self.prev_rr_ecn_ce = rr.ecn_ce_count;
260 self.prev_rr_reorder = rr.cumulative_reorder_count;
261 self.prev_rr_time = Some(now);
262 self.has_prev_rr = true;
263
264 !had_srtt && self.srtt.initialized()
265 }
266
267 pub fn update_reverse_delivery(&mut self, our_recv_packets: u64, peer_highest: u64) {
272 if self.has_prev_reverse {
273 let counter_span = peer_highest.saturating_sub(self.prev_reverse_highest);
274 let packets_delta = our_recv_packets.saturating_sub(self.prev_reverse_packets);
275
276 if counter_span > 0 {
277 let delivery = (packets_delta as f64) / (counter_span as f64);
278 self.delivery_ratio_reverse = delivery.clamp(0.0, 1.0);
279 self.etx = compute_etx(self.delivery_ratio_forward, self.delivery_ratio_reverse);
280 self.etx_trend.update(self.etx);
281 }
282 }
283
284 self.prev_reverse_packets = our_recv_packets;
285 self.prev_reverse_highest = peer_highest;
286 self.has_prev_reverse = true;
287 }
288
289 pub fn srtt_ms(&self) -> Option<f64> {
291 if self.srtt.initialized() {
292 Some(self.srtt.srtt_us() as f64 / 1000.0)
293 } else {
294 None
295 }
296 }
297
298 pub fn srtt_age_ms(&self, now: Instant) -> Option<u64> {
300 self.last_srtt_update.map(|updated_at| {
301 now.saturating_duration_since(updated_at)
302 .as_millis()
303 .min(u128::from(u64::MAX)) as u64
304 })
305 }
306
307 pub fn loss_rate(&self) -> f64 {
309 1.0 - self.delivery_ratio_forward
310 }
311
312 pub fn smoothed_loss(&self) -> Option<f64> {
314 if self.loss_trend.initialized() {
315 Some(self.loss_trend.long())
316 } else {
317 None
318 }
319 }
320
321 pub fn last_forward_loss_sample(&self) -> Option<(u64, f64)> {
323 self.last_forward_loss_rate
324 .map(|loss| (self.last_forward_counter_span, loss))
325 }
326
327 pub fn take_forward_loss_evidence(&mut self, min_span: u64) -> Option<(u64, f64)> {
331 if min_span == 0 {
332 return self.last_forward_loss_sample();
333 }
334
335 if self.last_forward_counter_span >= min_span
336 && let Some(loss) = self.last_forward_loss_rate
337 {
338 self.forward_loss_window_span = 0;
339 self.forward_loss_window_lost = 0;
340 return Some((self.last_forward_counter_span, loss));
341 }
342
343 if self.forward_loss_window_span >= min_span {
344 let span = self.forward_loss_window_span;
345 let loss = (self.forward_loss_window_lost as f64 / span as f64).clamp(0.0, 1.0);
346 self.forward_loss_window_span = 0;
347 self.forward_loss_window_lost = 0;
348 return Some((span, loss));
349 }
350
351 None
352 }
353
354 pub fn smoothed_etx(&self) -> Option<f64> {
356 if self.etx_trend.initialized() {
357 Some(self.etx_trend.long())
358 } else {
359 None
360 }
361 }
362
363 pub fn goodput_bps(&self) -> f64 {
365 self.goodput_bps
366 }
367
368 pub fn last_ecn_ce_count(&self) -> u32 {
370 self.prev_rr_ecn_ce
371 }
372}
373
374impl Default for MmpMetrics {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380#[cfg(test)]
385mod tests {
386 use super::*;
387 use std::time::Duration;
388
389 fn make_rr(
390 highest_counter: u64,
391 cum_packets: u64,
392 cum_bytes: u64,
393 timestamp_echo: u32,
394 dwell: u16,
395 jitter: u32,
396 ) -> ReceiverReport {
397 ReceiverReport {
398 highest_counter,
399 cumulative_packets_recv: cum_packets,
400 cumulative_bytes_recv: cum_bytes,
401 timestamp_echo,
402 dwell_time: dwell,
403 max_burst_loss: 0,
404 mean_burst_loss: 0,
405 jitter,
406 ecn_ce_count: 0,
407 owd_trend: 0,
408 burst_loss_count: 0,
409 cumulative_reorder_count: 0,
410 interval_packets_recv: 0,
411 interval_bytes_recv: 0,
412 }
413 }
414
415 #[test]
416 fn test_rtt_from_echo() {
417 let mut m = MmpMetrics::new();
418 let now = Instant::now();
419 let rr = make_rr(10, 10, 5000, 1000, 5, 0);
421 m.process_receiver_report(&rr, 1050, now);
422
423 assert!(m.srtt.initialized());
424 let srtt_ms = m.srtt_ms().unwrap();
426 assert!((srtt_ms - 45.0).abs() < 1.0, "srtt={srtt_ms}, expected ~45");
427 }
428
429 #[test]
430 fn test_ignores_duplicate_receiver_report_after_valid_sample() {
431 let mut m = MmpMetrics::new();
432 let now = Instant::now();
433
434 let valid_rr = make_rr(10, 10, 5000, 1000, 5, 0);
435 m.process_receiver_report(&valid_rr, 1050, now);
436 let baseline_srtt_ms = m.srtt_ms().unwrap();
437 assert_eq!(m.srtt_age_ms(now), Some(0));
438
439 m.process_receiver_report(&valid_rr, 6000, now + Duration::from_secs(5));
442
443 let srtt_ms = m.srtt_ms().unwrap();
444 assert_eq!(srtt_ms, baseline_srtt_ms);
445 assert_eq!(m.srtt_age_ms(now + Duration::from_secs(5)), Some(5000));
446 }
447
448 #[test]
449 fn test_ignores_out_of_order_receiver_report_after_valid_sample() {
450 let mut m = MmpMetrics::new();
451 let now = Instant::now();
452
453 let valid_rr = make_rr(20, 20, 10000, 1000, 5, 0);
454 m.process_receiver_report(&valid_rr, 1050, now);
455 let baseline_srtt_ms = m.srtt_ms().unwrap();
456
457 let old_rr = make_rr(10, 10, 5000, 1000, 0, 0);
458 m.process_receiver_report(&old_rr, 6000, now + Duration::from_secs(5));
459
460 let srtt_ms = m.srtt_ms().unwrap();
461 assert_eq!(srtt_ms, baseline_srtt_ms);
462 }
463
464 #[test]
465 fn test_ignores_wrapped_rtt_sample() {
466 let mut m = MmpMetrics::new();
467 let now = Instant::now();
468
469 let wrapped_rr = make_rr(10, 10, 5000, u32::MAX - 10, 20, 0);
470 m.process_receiver_report(&wrapped_rr, 15, now);
471
472 assert!(m.srtt_ms().is_none());
473 }
474
475 #[test]
476 fn test_loss_rate_computation() {
477 let mut m = MmpMetrics::new();
478 let t0 = Instant::now();
479
480 let rr1 = make_rr(100, 100, 50000, 0, 0, 0);
482 m.process_receiver_report(&rr1, 0, t0);
483
484 let rr2 = make_rr(300, 290, 145000, 0, 0, 0);
486 m.process_receiver_report(&rr2, 0, t0 + Duration::from_secs(1));
487
488 let loss = m.loss_rate();
489 assert!((loss - 0.05).abs() < 0.01, "loss={loss}, expected ~0.05");
490 assert_eq!(m.last_forward_loss_sample(), Some((200, loss)));
491 }
492
493 #[test]
494 fn test_etx_updates() {
495 let mut m = MmpMetrics::new();
496 assert_eq!(m.etx, 1.0); m.delivery_ratio_forward = 0.9;
500
501 m.update_reverse_delivery(100, 100);
503 assert_eq!(m.etx, 1.0); m.update_reverse_delivery(290, 300);
507 assert!(m.etx > 1.0);
508 assert!(m.etx < 2.0);
509 }
510
511 #[test]
512 fn test_no_rtt_without_echo() {
513 let mut m = MmpMetrics::new();
514 let now = Instant::now();
515 let rr = make_rr(10, 10, 5000, 0, 0, 0);
516 m.process_receiver_report(&rr, 1000, now);
517 assert!(m.srtt_ms().is_none());
518 }
519
520 #[test]
521 fn test_jitter_trend() {
522 let mut m = MmpMetrics::new();
523 let t0 = Instant::now();
524 let rr1 = make_rr(10, 10, 5000, 0, 0, 100);
525 m.process_receiver_report(&rr1, 0, t0);
526
527 let rr2 = make_rr(20, 20, 10000, 0, 0, 500);
528 m.process_receiver_report(&rr2, 0, t0 + Duration::from_secs(1));
529
530 assert!(m.jitter_trend.initialized());
531 assert!(m.jitter_trend.short() > m.jitter_trend.long());
533 }
534
535 #[test]
536 fn test_goodput_bps() {
537 let mut m = MmpMetrics::new();
538 let t0 = Instant::now();
539
540 let rr1 = make_rr(100, 100, 50_000, 0, 0, 0);
542 m.process_receiver_report(&rr1, 0, t0);
543 assert_eq!(m.goodput_bps(), 0.0); let rr2 = make_rr(300, 290, 150_000, 0, 0, 0);
547 m.process_receiver_report(&rr2, 0, t0 + Duration::from_secs(1));
548 assert!(
549 m.goodput_bps() > 90_000.0,
550 "goodput={}, expected ~100000",
551 m.goodput_bps()
552 );
553 assert!(
554 m.goodput_bps() < 110_000.0,
555 "goodput={}, expected ~100000",
556 m.goodput_bps()
557 );
558 }
559
560 #[test]
561 fn test_reverse_delivery_delta() {
562 let mut m = MmpMetrics::new();
563
564 m.update_reverse_delivery(100, 100);
566 assert_eq!(m.delivery_ratio_reverse, 1.0); m.update_reverse_delivery(300, 300);
570 assert!((m.delivery_ratio_reverse - 1.0).abs() < 0.001);
571
572 m.update_reverse_delivery(350, 400);
574 assert!(
575 (m.delivery_ratio_reverse - 0.5).abs() < 0.001,
576 "reverse={}, expected 0.5",
577 m.delivery_ratio_reverse
578 );
579 }
580
581 #[test]
582 fn test_reverse_delivery_rekey_reset() {
583 let mut m = MmpMetrics::new();
584
585 m.update_reverse_delivery(100, 100);
587 m.update_reverse_delivery(300, 300);
588 assert!((m.delivery_ratio_reverse - 1.0).abs() < 0.001);
589
590 m.reset_for_rekey();
592
593 m.update_reverse_delivery(50, 50);
595 assert_eq!(m.delivery_ratio_reverse, 1.0);
599
600 m.update_reverse_delivery(90, 100);
602 assert!(
603 (m.delivery_ratio_reverse - 0.8).abs() < 0.001,
604 "reverse={}, expected 0.8",
605 m.delivery_ratio_reverse
606 );
607 }
608}