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