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 = 48;
3
4/// Maximum number of path request timestamps to keep per direction.
5pub const PATH_REQUEST_SAMPLE_MAX: usize = 48;
6
7/// Minimum number of incoming announce samples before ingress-control frequency is meaningful.
8pub const INCOMING_ANNOUNCE_MIN_SAMPLE: usize = 2;
9
10/// Minimum number of incoming path request samples before frequency is meaningful.
11pub const INCOMING_PATH_REQUEST_MIN_SAMPLE: usize = 2;
12
13/// Traffic statistics for an interface.
14#[derive(Debug, Clone, Default)]
15pub struct InterfaceStats {
16    pub rxb: u64,
17    pub txb: u64,
18    pub rx_packets: u64,
19    pub tx_packets: u64,
20    pub started: f64,
21    /// Recent incoming announce timestamps (bounded).
22    pub ia_timestamps: Vec<f64>,
23    /// Recent outgoing announce timestamps (bounded).
24    pub oa_timestamps: Vec<f64>,
25    /// Recent incoming path request timestamps (bounded).
26    pub ip_timestamps: Vec<f64>,
27    /// Recent outgoing path request timestamps (bounded).
28    pub op_timestamps: Vec<f64>,
29}
30
31impl InterfaceStats {
32    /// Record an incoming announce timestamp.
33    pub fn record_incoming_announce(&mut self, now: f64) {
34        self.ia_timestamps.push(now);
35        if self.ia_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
36            self.ia_timestamps.remove(0);
37        }
38    }
39
40    /// Record an outgoing announce timestamp.
41    pub fn record_outgoing_announce(&mut self, now: f64) {
42        self.oa_timestamps.push(now);
43        if self.oa_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
44            self.oa_timestamps.remove(0);
45        }
46    }
47
48    /// Record an incoming path request timestamp.
49    pub fn record_incoming_path_request(&mut self, now: f64) {
50        self.ip_timestamps.push(now);
51        if self.ip_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
52            self.ip_timestamps.remove(0);
53        }
54    }
55
56    /// Record an outgoing path request timestamp.
57    pub fn record_outgoing_path_request(&mut self, now: f64) {
58        self.op_timestamps.push(now);
59        if self.op_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
60            self.op_timestamps.remove(0);
61        }
62    }
63
64    /// Compute announce frequency (per second) from timestamps.
65    fn compute_frequency(timestamps: &[f64], min_sample: usize) -> f64 {
66        let sample_count = timestamps.len();
67        if sample_count <= min_sample {
68            return 0.0;
69        }
70        let span = timestamps[sample_count - 1] - timestamps[0];
71        if span <= 0.0 {
72            return 0.0;
73        }
74        sample_count as f64 / span
75    }
76
77    /// Incoming announce frequency (per second).
78    pub fn incoming_announce_freq(&self) -> f64 {
79        Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
80    }
81
82    /// Outgoing announce frequency (per second).
83    pub fn outgoing_announce_freq(&self) -> f64 {
84        Self::compute_frequency(&self.oa_timestamps, 1)
85    }
86
87    /// Incoming path request frequency (per second).
88    pub fn incoming_path_request_freq(&self) -> f64 {
89        Self::compute_frequency(&self.ip_timestamps, INCOMING_PATH_REQUEST_MIN_SAMPLE)
90    }
91
92    /// Outgoing path request frequency (per second).
93    pub fn outgoing_path_request_freq(&self) -> f64 {
94        Self::compute_frequency(&self.op_timestamps, 1)
95    }
96
97    /// Number of outgoing path request samples currently held.
98    pub fn outgoing_path_request_samples(&self) -> usize {
99        self.op_timestamps.len()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn incoming_frequency_waits_for_minimum_sample_count() {
109        let mut stats = InterfaceStats::default();
110
111        for i in 0..INCOMING_ANNOUNCE_MIN_SAMPLE {
112            stats.record_incoming_announce(i as f64);
113        }
114
115        assert_eq!(
116            stats.incoming_announce_freq(),
117            0.0,
118            "incoming announce frequency must stay zero until more than the minimum samples exist"
119        );
120    }
121
122    #[test]
123    fn announce_frequency_keeps_bounded_samples() {
124        let mut stats = InterfaceStats::default();
125
126        for i in 0..ANNOUNCE_SAMPLE_MAX {
127            stats.record_incoming_announce(i as f64);
128            stats.record_outgoing_announce(i as f64);
129        }
130
131        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
132        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
133
134        stats.record_incoming_announce(ANNOUNCE_SAMPLE_MAX as f64);
135        stats.record_outgoing_announce(ANNOUNCE_SAMPLE_MAX as f64);
136
137        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
138        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
139        assert_eq!(stats.ia_timestamps[0], 1.0);
140        assert_eq!(stats.oa_timestamps[0], 1.0);
141    }
142
143    #[test]
144    fn incoming_frequency_uses_sample_count_over_oldest_span() {
145        let mut stats = InterfaceStats::default();
146
147        for i in 0..12 {
148            stats.record_incoming_announce(i as f64);
149        }
150
151        let expected = 12.0 / 11.0;
152        assert!(
153            (stats.incoming_announce_freq() - expected).abs() < f64::EPSILON,
154            "incoming frequency should be samples / span, got {} expected {}",
155            stats.incoming_announce_freq(),
156            expected
157        );
158    }
159
160    #[test]
161    fn path_request_frequency_tracks_incoming_and_outgoing_samples() {
162        let mut stats = InterfaceStats::default();
163
164        for i in 0..=INCOMING_PATH_REQUEST_MIN_SAMPLE {
165            stats.record_incoming_path_request(i as f64);
166        }
167        stats.record_outgoing_path_request(10.0);
168        stats.record_outgoing_path_request(12.0);
169
170        assert_eq!(stats.incoming_path_request_freq(), 3.0 / 2.0);
171        assert_eq!(stats.outgoing_path_request_freq(), 2.0 / 2.0);
172        assert_eq!(stats.outgoing_path_request_samples(), 2);
173    }
174
175    #[test]
176    fn path_request_frequency_keeps_bounded_samples() {
177        let mut stats = InterfaceStats::default();
178
179        for i in 0..PATH_REQUEST_SAMPLE_MAX {
180            stats.record_incoming_path_request(i as f64);
181            stats.record_outgoing_path_request(i as f64);
182        }
183
184        stats.record_incoming_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
185        stats.record_outgoing_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
186
187        assert_eq!(stats.ip_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
188        assert_eq!(stats.op_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
189        assert_eq!(stats.ip_timestamps[0], 1.0);
190        assert_eq!(stats.op_timestamps[0], 1.0);
191    }
192}