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, Copy, Default)]
15pub struct TrafficRates {
16    pub rxs: f64,
17    pub txs: f64,
18    /// Received packets per second in the current sampling window.
19    pub rxpps: f64,
20    /// Transmitted packets per second in the current sampling window.
21    pub txpps: f64,
22    pub arxs: f64,
23    pub atxs: f64,
24    pub prxs: f64,
25    pub ptxs: f64,
26}
27
28/// Traffic statistics for an interface.
29#[derive(Debug, Clone, Default)]
30pub struct InterfaceStats {
31    pub rxb: u64,
32    pub txb: u64,
33    pub arxb: u64,
34    pub atxb: u64,
35    pub arxc: u64,
36    pub atxc: u64,
37    pub prxb: u64,
38    pub ptxb: u64,
39    pub prxc: u64,
40    pub ptxc: u64,
41    pub protocol_violations: u64,
42    pub ifac_violations: u64,
43    pub packet_filter_hits: u64,
44    pub traffic_rates: TrafficRates,
45    pub rx_packets: u64,
46    pub tx_packets: u64,
47    pub cpu_load: Option<f64>,
48    pub mem_load: Option<f64>,
49    pub switch_id: Option<[u8; 4]>,
50    pub endpoint_id: Option<[u8; 8]>,
51    pub via_switch_id: Option<[u8; 4]>,
52    pub peers: Option<usize>,
53    pub started: f64,
54    /// Recent incoming announce timestamps (bounded).
55    pub ia_timestamps: Vec<f64>,
56    /// Recent outgoing announce timestamps (bounded).
57    pub oa_timestamps: Vec<f64>,
58    /// Recent incoming path request timestamps (bounded).
59    pub ip_timestamps: Vec<f64>,
60    /// Recent outgoing path request timestamps (bounded).
61    pub op_timestamps: Vec<f64>,
62}
63
64impl InterfaceStats {
65    /// Record incoming announce traffic after the packet has been accepted.
66    pub fn record_incoming_announce_bytes(&mut self, now: f64, size: usize) {
67        self.arxc = self.arxc.saturating_add(1);
68        self.arxb = self.arxb.saturating_add(size as u64);
69        self.record_incoming_announce(now);
70    }
71
72    /// Record an incoming announce timestamp.
73    pub fn record_incoming_announce(&mut self, now: f64) {
74        self.ia_timestamps.push(now);
75        if self.ia_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
76            self.ia_timestamps.remove(0);
77        }
78    }
79
80    /// Record an outgoing announce timestamp.
81    pub fn record_outgoing_announce(&mut self, now: f64) {
82        self.oa_timestamps.push(now);
83        if self.oa_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
84            self.oa_timestamps.remove(0);
85        }
86    }
87
88    /// Record outgoing announce traffic.
89    pub fn record_outgoing_announce_bytes(&mut self, now: f64, size: usize) {
90        self.atxc = self.atxc.saturating_add(1);
91        self.atxb = self.atxb.saturating_add(size as u64);
92        self.record_outgoing_announce(now);
93    }
94
95    /// Record an incoming path request timestamp.
96    pub fn record_incoming_path_request(&mut self, now: f64) {
97        self.ip_timestamps.push(now);
98        if self.ip_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
99            self.ip_timestamps.remove(0);
100        }
101    }
102
103    /// Record accepted incoming path-request traffic.
104    pub fn record_incoming_path_request_bytes(&mut self, now: f64, size: usize) {
105        self.prxc = self.prxc.saturating_add(1);
106        self.prxb = self.prxb.saturating_add(size as u64);
107        self.record_incoming_path_request(now);
108    }
109
110    /// Record an outgoing path request timestamp.
111    pub fn record_outgoing_path_request(&mut self, now: f64) {
112        self.op_timestamps.push(now);
113        if self.op_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
114            self.op_timestamps.remove(0);
115        }
116    }
117
118    /// Record outgoing path-request traffic.
119    pub fn record_outgoing_path_request_bytes(&mut self, now: f64, size: usize) {
120        self.ptxc = self.ptxc.saturating_add(1);
121        self.ptxb = self.ptxb.saturating_add(size as u64);
122        self.record_outgoing_path_request(now);
123    }
124
125    /// Compute announce frequency (per second) from timestamps.
126    fn compute_frequency(timestamps: &[f64], min_sample: usize) -> f64 {
127        let sample_count = timestamps.len();
128        if sample_count <= min_sample {
129            return 0.0;
130        }
131        let span = timestamps[sample_count - 1] - timestamps[0];
132        if span <= 0.0 {
133            return 0.0;
134        }
135        sample_count as f64 / span
136    }
137
138    /// Incoming announce frequency (per second).
139    pub fn incoming_announce_freq(&self) -> f64 {
140        Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
141    }
142
143    /// Outgoing announce frequency (per second).
144    pub fn outgoing_announce_freq(&self) -> f64 {
145        Self::compute_frequency(&self.oa_timestamps, 1)
146    }
147
148    /// Incoming path request frequency (per second).
149    pub fn incoming_path_request_freq(&self) -> f64 {
150        Self::compute_frequency(&self.ip_timestamps, INCOMING_PATH_REQUEST_MIN_SAMPLE)
151    }
152
153    /// Outgoing path request frequency (per second).
154    pub fn outgoing_path_request_freq(&self) -> f64 {
155        Self::compute_frequency(&self.op_timestamps, 1)
156    }
157
158    /// Number of outgoing path request samples currently held.
159    pub fn outgoing_path_request_samples(&self) -> usize {
160        self.op_timestamps.len()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn incoming_frequency_waits_for_minimum_sample_count() {
170        let mut stats = InterfaceStats::default();
171
172        for i in 0..INCOMING_ANNOUNCE_MIN_SAMPLE {
173            stats.record_incoming_announce(i as f64);
174        }
175
176        assert_eq!(
177            stats.incoming_announce_freq(),
178            0.0,
179            "incoming announce frequency must stay zero until more than the minimum samples exist"
180        );
181    }
182
183    #[test]
184    fn announce_frequency_keeps_bounded_samples() {
185        let mut stats = InterfaceStats::default();
186
187        for i in 0..ANNOUNCE_SAMPLE_MAX {
188            stats.record_incoming_announce(i as f64);
189            stats.record_outgoing_announce(i as f64);
190        }
191
192        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
193        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
194
195        stats.record_incoming_announce(ANNOUNCE_SAMPLE_MAX as f64);
196        stats.record_outgoing_announce(ANNOUNCE_SAMPLE_MAX as f64);
197
198        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
199        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
200        assert_eq!(stats.ia_timestamps[0], 1.0);
201        assert_eq!(stats.oa_timestamps[0], 1.0);
202    }
203
204    #[test]
205    fn incoming_frequency_uses_sample_count_over_oldest_span() {
206        let mut stats = InterfaceStats::default();
207
208        for i in 0..12 {
209            stats.record_incoming_announce(i as f64);
210        }
211
212        let expected = 12.0 / 11.0;
213        assert!(
214            (stats.incoming_announce_freq() - expected).abs() < f64::EPSILON,
215            "incoming frequency should be samples / span, got {} expected {}",
216            stats.incoming_announce_freq(),
217            expected
218        );
219    }
220
221    #[test]
222    fn path_request_frequency_tracks_incoming_and_outgoing_samples() {
223        let mut stats = InterfaceStats::default();
224
225        for i in 0..=INCOMING_PATH_REQUEST_MIN_SAMPLE {
226            stats.record_incoming_path_request(i as f64);
227        }
228        stats.record_outgoing_path_request(10.0);
229        stats.record_outgoing_path_request(12.0);
230
231        assert_eq!(stats.incoming_path_request_freq(), 3.0 / 2.0);
232        assert_eq!(stats.outgoing_path_request_freq(), 2.0 / 2.0);
233        assert_eq!(stats.outgoing_path_request_samples(), 2);
234    }
235
236    #[test]
237    fn detailed_traffic_counts_bytes_without_changing_timestamp_bounds() {
238        let mut stats = InterfaceStats::default();
239
240        stats.record_incoming_announce_bytes(1.0, 101);
241        stats.record_outgoing_announce_bytes(2.0, 202);
242        stats.record_incoming_path_request_bytes(3.0, 303);
243        stats.record_outgoing_path_request_bytes(4.0, 404);
244
245        assert_eq!((stats.arxb, stats.atxb), (101, 202));
246        assert_eq!((stats.arxc, stats.atxc), (1, 1));
247        assert_eq!((stats.prxb, stats.ptxb), (303, 404));
248        assert_eq!((stats.prxc, stats.ptxc), (1, 1));
249        assert_eq!(stats.ia_timestamps, vec![1.0]);
250        assert_eq!(stats.oa_timestamps, vec![2.0]);
251        assert_eq!(stats.ip_timestamps, vec![3.0]);
252        assert_eq!(stats.op_timestamps, vec![4.0]);
253    }
254
255    #[test]
256    fn path_request_frequency_keeps_bounded_samples() {
257        let mut stats = InterfaceStats::default();
258
259        for i in 0..PATH_REQUEST_SAMPLE_MAX {
260            stats.record_incoming_path_request(i as f64);
261            stats.record_outgoing_path_request(i as f64);
262        }
263
264        stats.record_incoming_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
265        stats.record_outgoing_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
266
267        assert_eq!(stats.ip_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
268        assert_eq!(stats.op_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
269        assert_eq!(stats.ip_timestamps[0], 1.0);
270        assert_eq!(stats.op_timestamps[0], 1.0);
271    }
272}