1pub const ANNOUNCE_SAMPLE_MAX: usize = 48;
3
4pub const PATH_REQUEST_SAMPLE_MAX: usize = 48;
6
7pub const INCOMING_ANNOUNCE_MIN_SAMPLE: usize = 2;
9
10pub const INCOMING_PATH_REQUEST_MIN_SAMPLE: usize = 2;
12
13#[derive(Debug, Clone, Copy, Default)]
15pub struct TrafficRates {
16 pub rxs: f64,
17 pub txs: f64,
18 pub rxpps: f64,
20 pub txpps: f64,
22 pub arxs: f64,
23 pub atxs: f64,
24 pub prxs: f64,
25 pub ptxs: f64,
26}
27
28#[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 hop_limiter_hits: u64,
46 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 pub rssi: Option<i16>,
59 pub snr: Option<f32>,
61 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 pub ia_timestamps: Vec<f64>,
73 pub oa_timestamps: Vec<f64>,
75 pub ip_timestamps: Vec<f64>,
77 pub op_timestamps: Vec<f64>,
79}
80
81impl InterfaceStats {
82 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 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 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 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 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 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 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 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 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 pub fn incoming_announce_freq(&self) -> f64 {
157 Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
158 }
159
160 pub fn outgoing_announce_freq(&self) -> f64 {
162 Self::compute_frequency(&self.oa_timestamps, 1)
163 }
164
165 pub fn incoming_path_request_freq(&self) -> f64 {
167 Self::compute_frequency(&self.ip_timestamps, INCOMING_PATH_REQUEST_MIN_SAMPLE)
168 }
169
170 pub fn outgoing_path_request_freq(&self) -> f64 {
172 Self::compute_frequency(&self.op_timestamps, 1)
173 }
174
175 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}