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 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 pub ia_timestamps: Vec<f64>,
56 pub oa_timestamps: Vec<f64>,
58 pub ip_timestamps: Vec<f64>,
60 pub op_timestamps: Vec<f64>,
62}
63
64impl InterfaceStats {
65 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 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 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 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 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 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 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 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 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 pub fn incoming_announce_freq(&self) -> f64 {
140 Self::compute_frequency(&self.ia_timestamps, INCOMING_ANNOUNCE_MIN_SAMPLE)
141 }
142
143 pub fn outgoing_announce_freq(&self) -> f64 {
145 Self::compute_frequency(&self.oa_timestamps, 1)
146 }
147
148 pub fn incoming_path_request_freq(&self) -> f64 {
150 Self::compute_frequency(&self.ip_timestamps, INCOMING_PATH_REQUEST_MIN_SAMPLE)
151 }
152
153 pub fn outgoing_path_request_freq(&self) -> f64 {
155 Self::compute_frequency(&self.op_timestamps, 1)
156 }
157
158 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}