Skip to main content

rns_net/common/
interface_stats.rs

1/// Maximum number of announce timestamps to keep per direction.
2pub const ANNOUNCE_SAMPLE_MAX: usize = 12;
3
4/// Minimum number of incoming announce samples before ingress-control frequency is meaningful.
5pub const INCOMING_ANNOUNCE_MIN_SAMPLE: usize = 8;
6
7/// Traffic statistics for an interface.
8#[derive(Debug, Clone, Default)]
9pub struct InterfaceStats {
10    pub rxb: u64,
11    pub txb: u64,
12    pub rx_packets: u64,
13    pub tx_packets: u64,
14    pub started: f64,
15    /// Recent incoming announce timestamps (bounded).
16    pub ia_timestamps: Vec<f64>,
17    /// Recent outgoing announce timestamps (bounded).
18    pub oa_timestamps: Vec<f64>,
19}
20
21impl InterfaceStats {
22    /// Record an incoming announce timestamp.
23    pub fn record_incoming_announce(&mut self, now: f64) {
24        self.ia_timestamps.push(now);
25        if self.ia_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
26            self.ia_timestamps.remove(0);
27        }
28    }
29
30    /// Record an outgoing announce timestamp.
31    pub fn record_outgoing_announce(&mut self, now: f64) {
32        self.oa_timestamps.push(now);
33        if self.oa_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
34            self.oa_timestamps.remove(0);
35        }
36    }
37
38    /// Compute announce frequency (per second) from timestamps.
39    fn compute_frequency(timestamps: &[f64], min_sample: usize) -> f64 {
40        let sample_count = timestamps.len();
41        if sample_count <= min_sample {
42            return 0.0;
43        }
44        let span = timestamps[sample_count - 1] - timestamps[0];
45        if span <= 0.0 {
46            return 0.0;
47        }
48        sample_count as f64 / span
49    }
50
51    /// Incoming announce frequency (per second).
52    pub fn incoming_announce_freq(&self) -> f64 {
53        Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
54    }
55
56    /// Outgoing announce frequency (per second).
57    pub fn outgoing_announce_freq(&self) -> f64 {
58        Self::compute_frequency(&self.oa_timestamps, 1)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn incoming_frequency_waits_for_minimum_sample_count() {
68        let mut stats = InterfaceStats::default();
69
70        for i in 0..8 {
71            stats.record_incoming_announce(i as f64);
72        }
73
74        assert_eq!(
75            stats.incoming_announce_freq(),
76            0.0,
77            "incoming announce frequency must stay zero until more than 8 samples exist"
78        );
79    }
80
81    #[test]
82    fn announce_frequency_keeps_twelve_samples() {
83        let mut stats = InterfaceStats::default();
84
85        for i in 0..12 {
86            stats.record_incoming_announce(i as f64);
87            stats.record_outgoing_announce(i as f64);
88        }
89
90        assert_eq!(stats.ia_timestamps.len(), 12);
91        assert_eq!(stats.oa_timestamps.len(), 12);
92
93        stats.record_incoming_announce(12.0);
94        stats.record_outgoing_announce(12.0);
95
96        assert_eq!(stats.ia_timestamps.len(), 12);
97        assert_eq!(stats.oa_timestamps.len(), 12);
98        assert_eq!(stats.ia_timestamps[0], 1.0);
99        assert_eq!(stats.oa_timestamps[0], 1.0);
100    }
101
102    #[test]
103    fn incoming_frequency_uses_sample_count_over_oldest_span() {
104        let mut stats = InterfaceStats::default();
105
106        for i in 0..12 {
107            stats.record_incoming_announce(i as f64);
108        }
109
110        let expected = 12.0 / 11.0;
111        assert!(
112            (stats.incoming_announce_freq() - expected).abs() < f64::EPSILON,
113            "incoming frequency should be samples / span, got {} expected {}",
114            stats.incoming_announce_freq(),
115            expected
116        );
117    }
118}