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    /// Inbound packets dropped because their hop count reached `HOP_LIMITER`.
45    pub hop_limiter_hits: u64,
46    /// Inbound path requests whose stamp met `PATH_REQUEST_STAMP_COST`.
47    pub stamped_path_requests: u64,
48    pub traffic_rates: TrafficRates,
49    pub rx_packets: u64,
50    pub tx_packets: u64,
51    pub cpu_load: Option<f64>,
52    pub mem_load: Option<f64>,
53    pub switch_id: Option<[u8; 4]>,
54    pub endpoint_id: Option<[u8; 8]>,
55    pub via_switch_id: Option<[u8; 4]>,
56    pub peers: Option<usize>,
57    /// Last reported receive signal strength in dBm.
58    pub rssi: Option<i16>,
59    /// Last reported signal-to-noise ratio in dB.
60    pub snr: Option<f32>,
61    /// Signal quality percentage derived from SNR and spreading factor.
62    pub signal_quality: Option<f64>,
63    pub frequency: Option<u32>,
64    pub bandwidth: Option<u32>,
65    pub txpower: Option<i8>,
66    pub spreading_factor: Option<u8>,
67    pub coding_rate: Option<u8>,
68    pub channel: Option<u32>,
69    pub modulation: Option<String>,
70    pub started: f64,
71    /// Recent incoming announce timestamps (bounded).
72    pub ia_timestamps: Vec<f64>,
73    /// Recent outgoing announce timestamps (bounded).
74    pub oa_timestamps: Vec<f64>,
75    /// Recent incoming path request timestamps (bounded).
76    pub ip_timestamps: Vec<f64>,
77    /// Recent outgoing path request timestamps (bounded).
78    pub op_timestamps: Vec<f64>,
79}
80
81impl InterfaceStats {
82    /// Record incoming announce traffic after the packet has been accepted.
83    pub fn record_incoming_announce_bytes(&mut self, now: f64, size: usize) {
84        self.arxc = self.arxc.saturating_add(1);
85        self.arxb = self.arxb.saturating_add(size as u64);
86        self.record_incoming_announce(now);
87    }
88
89    /// Record an incoming announce timestamp.
90    pub fn record_incoming_announce(&mut self, now: f64) {
91        self.ia_timestamps.push(now);
92        if self.ia_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
93            self.ia_timestamps.remove(0);
94        }
95    }
96
97    /// Record an outgoing announce timestamp.
98    pub fn record_outgoing_announce(&mut self, now: f64) {
99        self.oa_timestamps.push(now);
100        if self.oa_timestamps.len() > ANNOUNCE_SAMPLE_MAX {
101            self.oa_timestamps.remove(0);
102        }
103    }
104
105    /// Record outgoing announce traffic.
106    pub fn record_outgoing_announce_bytes(&mut self, now: f64, size: usize) {
107        self.atxc = self.atxc.saturating_add(1);
108        self.atxb = self.atxb.saturating_add(size as u64);
109        self.record_outgoing_announce(now);
110    }
111
112    /// Record an incoming path request timestamp.
113    pub fn record_incoming_path_request(&mut self, now: f64) {
114        self.ip_timestamps.push(now);
115        if self.ip_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
116            self.ip_timestamps.remove(0);
117        }
118    }
119
120    /// Record accepted incoming path-request traffic.
121    pub fn record_incoming_path_request_bytes(&mut self, now: f64, size: usize) {
122        self.prxc = self.prxc.saturating_add(1);
123        self.prxb = self.prxb.saturating_add(size as u64);
124        self.record_incoming_path_request(now);
125    }
126
127    /// Record an outgoing path request timestamp.
128    pub fn record_outgoing_path_request(&mut self, now: f64) {
129        self.op_timestamps.push(now);
130        if self.op_timestamps.len() > PATH_REQUEST_SAMPLE_MAX {
131            self.op_timestamps.remove(0);
132        }
133    }
134
135    /// Record outgoing path-request traffic.
136    pub fn record_outgoing_path_request_bytes(&mut self, now: f64, size: usize) {
137        self.ptxc = self.ptxc.saturating_add(1);
138        self.ptxb = self.ptxb.saturating_add(size as u64);
139        self.record_outgoing_path_request(now);
140    }
141
142    /// Compute announce frequency (per second) from timestamps.
143    fn compute_frequency(timestamps: &[f64], min_sample: usize) -> f64 {
144        let sample_count = timestamps.len();
145        if sample_count <= min_sample {
146            return 0.0;
147        }
148        let span = timestamps[sample_count - 1] - timestamps[0];
149        if span <= 0.0 {
150            return 0.0;
151        }
152        sample_count as f64 / span
153    }
154
155    /// Incoming announce frequency (per second).
156    pub fn incoming_announce_freq(&self) -> f64 {
157        Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
158    }
159
160    /// Outgoing announce frequency (per second).
161    pub fn outgoing_announce_freq(&self) -> f64 {
162        Self::compute_frequency(&self.oa_timestamps, 1)
163    }
164
165    /// Incoming path request frequency (per second).
166    pub fn incoming_path_request_freq(&self) -> f64 {
167        Self::compute_frequency(&self.ip_timestamps, INCOMING_PATH_REQUEST_MIN_SAMPLE)
168    }
169
170    /// Outgoing path request frequency (per second).
171    pub fn outgoing_path_request_freq(&self) -> f64 {
172        Self::compute_frequency(&self.op_timestamps, 1)
173    }
174
175    /// Number of outgoing path request samples currently held.
176    pub fn outgoing_path_request_samples(&self) -> usize {
177        self.op_timestamps.len()
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn incoming_frequency_waits_for_minimum_sample_count() {
187        let mut stats = InterfaceStats::default();
188
189        for i in 0..INCOMING_ANNOUNCE_MIN_SAMPLE {
190            stats.record_incoming_announce(i as f64);
191        }
192
193        assert_eq!(
194            stats.incoming_announce_freq(),
195            0.0,
196            "incoming announce frequency must stay zero until more than the minimum samples exist"
197        );
198    }
199
200    #[test]
201    fn announce_frequency_keeps_bounded_samples() {
202        let mut stats = InterfaceStats::default();
203
204        for i in 0..ANNOUNCE_SAMPLE_MAX {
205            stats.record_incoming_announce(i as f64);
206            stats.record_outgoing_announce(i as f64);
207        }
208
209        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
210        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
211
212        stats.record_incoming_announce(ANNOUNCE_SAMPLE_MAX as f64);
213        stats.record_outgoing_announce(ANNOUNCE_SAMPLE_MAX as f64);
214
215        assert_eq!(stats.ia_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
216        assert_eq!(stats.oa_timestamps.len(), ANNOUNCE_SAMPLE_MAX);
217        assert_eq!(stats.ia_timestamps[0], 1.0);
218        assert_eq!(stats.oa_timestamps[0], 1.0);
219    }
220
221    #[test]
222    fn incoming_frequency_uses_sample_count_over_oldest_span() {
223        let mut stats = InterfaceStats::default();
224
225        for i in 0..12 {
226            stats.record_incoming_announce(i as f64);
227        }
228
229        let expected = 12.0 / 11.0;
230        assert!(
231            (stats.incoming_announce_freq() - expected).abs() < f64::EPSILON,
232            "incoming frequency should be samples / span, got {} expected {}",
233            stats.incoming_announce_freq(),
234            expected
235        );
236    }
237
238    #[test]
239    fn path_request_frequency_tracks_incoming_and_outgoing_samples() {
240        let mut stats = InterfaceStats::default();
241
242        for i in 0..=INCOMING_PATH_REQUEST_MIN_SAMPLE {
243            stats.record_incoming_path_request(i as f64);
244        }
245        stats.record_outgoing_path_request(10.0);
246        stats.record_outgoing_path_request(12.0);
247
248        assert_eq!(stats.incoming_path_request_freq(), 3.0 / 2.0);
249        assert_eq!(stats.outgoing_path_request_freq(), 2.0 / 2.0);
250        assert_eq!(stats.outgoing_path_request_samples(), 2);
251    }
252
253    #[test]
254    fn detailed_traffic_counts_bytes_without_changing_timestamp_bounds() {
255        let mut stats = InterfaceStats::default();
256
257        stats.record_incoming_announce_bytes(1.0, 101);
258        stats.record_outgoing_announce_bytes(2.0, 202);
259        stats.record_incoming_path_request_bytes(3.0, 303);
260        stats.record_outgoing_path_request_bytes(4.0, 404);
261
262        assert_eq!((stats.arxb, stats.atxb), (101, 202));
263        assert_eq!((stats.arxc, stats.atxc), (1, 1));
264        assert_eq!((stats.prxb, stats.ptxb), (303, 404));
265        assert_eq!((stats.prxc, stats.ptxc), (1, 1));
266        assert_eq!(stats.ia_timestamps, vec![1.0]);
267        assert_eq!(stats.oa_timestamps, vec![2.0]);
268        assert_eq!(stats.ip_timestamps, vec![3.0]);
269        assert_eq!(stats.op_timestamps, vec![4.0]);
270    }
271
272    #[test]
273    fn path_request_frequency_keeps_bounded_samples() {
274        let mut stats = InterfaceStats::default();
275
276        for i in 0..PATH_REQUEST_SAMPLE_MAX {
277            stats.record_incoming_path_request(i as f64);
278            stats.record_outgoing_path_request(i as f64);
279        }
280
281        stats.record_incoming_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
282        stats.record_outgoing_path_request(PATH_REQUEST_SAMPLE_MAX as f64);
283
284        assert_eq!(stats.ip_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
285        assert_eq!(stats.op_timestamps.len(), PATH_REQUEST_SAMPLE_MAX);
286        assert_eq!(stats.ip_timestamps[0], 1.0);
287        assert_eq!(stats.op_timestamps[0], 1.0);
288    }
289}