Skip to main content

netwatch_rs/
stats.rs

1use crate::device::NetworkStats;
2use std::collections::VecDeque;
3use std::time::Duration;
4#[cfg(test)]
5use std::time::SystemTime;
6
7pub struct StatsCalculator {
8    // Data storage
9    history: VecDeque<NetworkStats>,
10    window_size: Duration,
11
12    // Calculated values
13    current_speed_in: u64,
14    current_speed_out: u64,
15    avg_speed_in: u64,
16    avg_speed_out: u64,
17    min_speed_in: u64,
18    min_speed_out: u64,
19    max_speed_in: u64,
20    max_speed_out: u64,
21
22    // Graph data for display
23    graph_data_in: VecDeque<(f64, f64)>, // (time, value) pairs
24    graph_data_out: VecDeque<(f64, f64)>,
25
26    // Totals (from last sample)
27    total_bytes_in: u64,
28    total_bytes_out: u64,
29    total_packets_in: u64,
30    total_packets_out: u64,
31
32    // First sample flag for initialization
33    first_sample: bool,
34}
35
36impl StatsCalculator {
37    pub fn new(window_size: Duration) -> Self {
38        Self {
39            history: VecDeque::new(),
40            window_size,
41            current_speed_in: 0,
42            current_speed_out: 0,
43            avg_speed_in: 0,
44            avg_speed_out: 0,
45            min_speed_in: 0,
46            min_speed_out: 0,
47            max_speed_in: 0,
48            max_speed_out: 0,
49            graph_data_in: VecDeque::new(),
50            graph_data_out: VecDeque::new(),
51            total_bytes_in: 0,
52            total_bytes_out: 0,
53            total_packets_in: 0,
54            total_packets_out: 0,
55            first_sample: true,
56        }
57    }
58
59    pub fn add_sample(&mut self, stats: NetworkStats) {
60        // Update totals
61        self.total_bytes_in = stats.bytes_in;
62        self.total_bytes_out = stats.bytes_out;
63        self.total_packets_in = stats.packets_in;
64        self.total_packets_out = stats.packets_out;
65
66        // Calculate current speed if we have previous data
67        if let Some(previous) = self.history.back() {
68            let time_diff = stats
69                .timestamp
70                .duration_since(previous.timestamp)
71                .unwrap_or_default()
72                .as_secs_f64();
73
74            if time_diff > 0.0 {
75                // Handle counter overflow (32-bit counters can wrap)
76                let bytes_in_diff = self.calculate_diff(stats.bytes_in, previous.bytes_in);
77                let bytes_out_diff = self.calculate_diff(stats.bytes_out, previous.bytes_out);
78
79                self.current_speed_in = (bytes_in_diff as f64 / time_diff) as u64;
80                self.current_speed_out = (bytes_out_diff as f64 / time_diff) as u64;
81
82                // Update min/max (skip first few samples for stability)
83                if !self.first_sample {
84                    self.update_min_max();
85                }
86
87                // Add to graph data
88                self.add_graph_data(&stats);
89            }
90        }
91
92        self.history.push_back(stats);
93        self.trim_old_samples();
94        self.calculate_averages();
95
96        if self.first_sample {
97            self.first_sample = false;
98        }
99    }
100
101    fn calculate_diff(&self, current: u64, previous: u64) -> u64 {
102        if current >= previous {
103            current - previous
104        } else {
105            // Counter wrapped, assume 32-bit or 64-bit counter
106            // Try 32-bit first, then 64-bit
107            let diff_32 = (u32::MAX as u64) - previous + current + 1;
108            let diff_64 = (u64::MAX) - previous + current + 1;
109
110            // Choose the smaller, more reasonable difference
111            if diff_32 < diff_64 / 1000 {
112                diff_32
113            } else {
114                diff_64
115            }
116        }
117    }
118
119    fn update_min_max(&mut self) {
120        if self.current_speed_in < self.min_speed_in || self.min_speed_in == 0 {
121            self.min_speed_in = self.current_speed_in;
122        }
123        if self.current_speed_in > self.max_speed_in {
124            self.max_speed_in = self.current_speed_in;
125        }
126        if self.current_speed_out < self.min_speed_out || self.min_speed_out == 0 {
127            self.min_speed_out = self.current_speed_out;
128        }
129        if self.current_speed_out > self.max_speed_out {
130            self.max_speed_out = self.current_speed_out;
131        }
132    }
133
134    fn add_graph_data(&mut self, _stats: &NetworkStats) {
135        // First, shift all existing points forward in time (age them)
136        for (time, _) in self.graph_data_in.iter_mut() {
137            *time += 0.5; // Assuming ~500ms refresh rate
138        }
139        for (time, _) in self.graph_data_out.iter_mut() {
140            *time += 0.5; // Assuming ~500ms refresh rate
141        }
142
143        // Remove data older than 60 seconds
144        self.graph_data_in.retain(|(time, _)| *time <= 60.0);
145        self.graph_data_out.retain(|(time, _)| *time <= 60.0);
146
147        // Now add new data point at time 0 (now)
148        self.graph_data_in
149            .push_back((0.0, self.current_speed_in as f64));
150        self.graph_data_out
151            .push_back((0.0, self.current_speed_out as f64));
152
153        // Limit to reasonable number of points
154        while self.graph_data_in.len() > 120 {
155            self.graph_data_in.pop_front();
156        }
157        while self.graph_data_out.len() > 120 {
158            self.graph_data_out.pop_front();
159        }
160    }
161
162    fn trim_old_samples(&mut self) {
163        if let Some(latest) = self.history.back() {
164            let cutoff = latest.timestamp - self.window_size;
165            while let Some(oldest) = self.history.front() {
166                if oldest.timestamp < cutoff {
167                    self.history.pop_front();
168                } else {
169                    break;
170                }
171            }
172        }
173    }
174
175    fn calculate_averages(&mut self) {
176        if self.history.len() < 2 {
177            return;
178        }
179
180        let first = &self.history[0];
181        let last = &self.history[self.history.len() - 1];
182
183        let time_span = last
184            .timestamp
185            .duration_since(first.timestamp)
186            .unwrap_or_default()
187            .as_secs_f64();
188
189        if time_span > 0.0 {
190            let bytes_in_diff = self.calculate_diff(last.bytes_in, first.bytes_in);
191            let bytes_out_diff = self.calculate_diff(last.bytes_out, first.bytes_out);
192
193            self.avg_speed_in = (bytes_in_diff as f64 / time_span) as u64;
194            self.avg_speed_out = (bytes_out_diff as f64 / time_span) as u64;
195        }
196    }
197
198    // Public getters for UI
199    pub fn current_speed(&self) -> (u64, u64) {
200        (self.current_speed_in, self.current_speed_out)
201    }
202
203    pub fn average_speed(&self) -> (u64, u64) {
204        (self.avg_speed_in, self.avg_speed_out)
205    }
206
207    pub fn min_speed(&self) -> (u64, u64) {
208        (self.min_speed_in, self.min_speed_out)
209    }
210
211    pub fn max_speed(&self) -> (u64, u64) {
212        (self.max_speed_in, self.max_speed_out)
213    }
214
215    pub fn total_bytes(&self) -> (u64, u64) {
216        (self.total_bytes_in, self.total_bytes_out)
217    }
218
219    pub fn total_packets(&self) -> (u64, u64) {
220        (self.total_packets_in, self.total_packets_out)
221    }
222
223    pub fn graph_data_in(&self) -> &VecDeque<(f64, f64)> {
224        &self.graph_data_in
225    }
226
227    pub fn graph_data_out(&self) -> &VecDeque<(f64, f64)> {
228        &self.graph_data_out
229    }
230
231    pub fn sample_count(&self) -> usize {
232        self.history.len()
233    }
234
235    pub fn reset(&mut self) {
236        self.history.clear();
237        self.graph_data_in.clear();
238        self.graph_data_out.clear();
239        self.current_speed_in = 0;
240        self.current_speed_out = 0;
241        self.avg_speed_in = 0;
242        self.avg_speed_out = 0;
243        self.min_speed_in = 0;
244        self.min_speed_out = 0;
245        self.max_speed_in = 0;
246        self.max_speed_out = 0;
247        self.first_sample = true;
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_stats_calculation() {
257        let mut calc = StatsCalculator::new(Duration::from_secs(60));
258
259        let stats1 = NetworkStats {
260            timestamp: SystemTime::now(),
261            bytes_in: 1000,
262            bytes_out: 500,
263            packets_in: 10,
264            packets_out: 5,
265            errors_in: 0,
266            errors_out: 0,
267            drops_in: 0,
268            drops_out: 0,
269        };
270
271        calc.add_sample(stats1);
272
273        // First sample should not calculate speed
274        assert_eq!(calc.current_speed(), (0, 0));
275
276        // Add second sample after 1 second
277        let stats2 = NetworkStats {
278            timestamp: SystemTime::now() + Duration::from_secs(1),
279            bytes_in: 2000,
280            bytes_out: 1000,
281            packets_in: 20,
282            packets_out: 10,
283            errors_in: 0,
284            errors_out: 0,
285            drops_in: 0,
286            drops_out: 0,
287        };
288
289        calc.add_sample(stats2);
290
291        // Should calculate 1000 bytes/sec for both directions
292        let (in_speed, out_speed) = calc.current_speed();
293        assert!(in_speed > 0);
294        assert!(out_speed > 0);
295    }
296
297    #[test]
298    fn test_counter_overflow() {
299        let calc = StatsCalculator::new(Duration::from_secs(60));
300
301        // Test 32-bit counter overflow
302        let diff = calc.calculate_diff(100, u32::MAX as u64 - 50);
303        assert_eq!(diff, 151); // (u32::MAX - (u32::MAX - 50)) + 100 + 1
304    }
305}