1use std::sync::atomic::{AtomicU64, Ordering};
4
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
7pub struct XdpStats {
8 pub dropped: u64,
10 pub passed: u64,
12 pub redirected: u64,
14 pub invalid: u64,
16 pub bytes_processed: u64,
18}
19
20impl XdpStats {
21 #[must_use]
23 pub const fn total_packets(&self) -> u64 {
24 self.dropped + self.passed + self.redirected + self.invalid
25 }
26
27 #[must_use]
29 #[allow(clippy::cast_precision_loss)]
30 pub fn drop_rate(&self) -> f64 {
31 let total = self.total_packets();
32 if total == 0 {
33 return 0.0;
34 }
35 (self.dropped as f64 / total as f64) * 100.0
36 }
37
38 #[must_use]
40 #[allow(clippy::cast_precision_loss)]
41 pub fn redirect_rate(&self) -> f64 {
42 let total = self.total_packets();
43 if total == 0 {
44 return 0.0;
45 }
46 (self.redirected as f64 / total as f64) * 100.0
47 }
48
49 pub fn merge(&mut self, other: &XdpStats) {
51 self.dropped += other.dropped;
52 self.passed += other.passed;
53 self.redirected += other.redirected;
54 self.invalid += other.invalid;
55 self.bytes_processed += other.bytes_processed;
56 }
57}
58
59impl std::fmt::Display for XdpStats {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(
62 f,
63 "XdpStats {{ dropped: {}, passed: {}, redirected: {}, invalid: {}, bytes: {} }}",
64 self.dropped, self.passed, self.redirected, self.invalid, self.bytes_processed
65 )
66 }
67}
68
69#[derive(Debug, Default)]
71#[allow(dead_code)]
72pub(crate) struct AtomicXdpStats {
73 dropped: AtomicU64,
74 passed: AtomicU64,
75 redirected: AtomicU64,
76 invalid: AtomicU64,
77 bytes_processed: AtomicU64,
78}
79
80#[allow(dead_code)]
81impl AtomicXdpStats {
82 #[must_use]
84 pub const fn new() -> Self {
85 Self {
86 dropped: AtomicU64::new(0),
87 passed: AtomicU64::new(0),
88 redirected: AtomicU64::new(0),
89 invalid: AtomicU64::new(0),
90 bytes_processed: AtomicU64::new(0),
91 }
92 }
93
94 pub fn inc_dropped(&self) {
96 self.dropped.fetch_add(1, Ordering::Relaxed);
97 }
98
99 pub fn inc_passed(&self) {
101 self.passed.fetch_add(1, Ordering::Relaxed);
102 }
103
104 pub fn inc_redirected(&self) {
106 self.redirected.fetch_add(1, Ordering::Relaxed);
107 }
108
109 pub fn inc_invalid(&self) {
111 self.invalid.fetch_add(1, Ordering::Relaxed);
112 }
113
114 pub fn add_bytes(&self, bytes: u64) {
116 self.bytes_processed.fetch_add(bytes, Ordering::Relaxed);
117 }
118
119 #[must_use]
121 pub fn snapshot(&self) -> XdpStats {
122 XdpStats {
123 dropped: self.dropped.load(Ordering::Relaxed),
124 passed: self.passed.load(Ordering::Relaxed),
125 redirected: self.redirected.load(Ordering::Relaxed),
126 invalid: self.invalid.load(Ordering::Relaxed),
127 bytes_processed: self.bytes_processed.load(Ordering::Relaxed),
128 }
129 }
130
131 pub fn reset(&self) {
133 self.dropped.store(0, Ordering::Relaxed);
134 self.passed.store(0, Ordering::Relaxed);
135 self.redirected.store(0, Ordering::Relaxed);
136 self.invalid.store(0, Ordering::Relaxed);
137 self.bytes_processed.store(0, Ordering::Relaxed);
138 }
139}
140
141#[derive(Debug, Clone)]
143#[allow(dead_code)]
144pub(crate) struct PerCpuStats {
145 pub cpu_stats: Vec<XdpStats>,
147}
148
149#[allow(dead_code)]
150impl PerCpuStats {
151 #[must_use]
153 pub fn new(num_cpus: usize) -> Self {
154 Self {
155 cpu_stats: vec![XdpStats::default(); num_cpus],
156 }
157 }
158
159 #[must_use]
161 pub fn aggregate(&self) -> XdpStats {
162 let mut total = XdpStats::default();
163 for stats in &self.cpu_stats {
164 total.merge(stats);
165 }
166 total
167 }
168
169 #[must_use]
171 pub fn get(&self, cpu: usize) -> Option<&XdpStats> {
172 self.cpu_stats.get(cpu)
173 }
174
175 #[must_use]
177 pub fn num_cpus(&self) -> usize {
178 self.cpu_stats.len()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_xdp_stats_default() {
188 let stats = XdpStats::default();
189 assert_eq!(stats.dropped, 0);
190 assert_eq!(stats.total_packets(), 0);
191 }
192
193 #[test]
194 fn test_xdp_stats_total() {
195 let stats = XdpStats {
196 dropped: 10,
197 passed: 20,
198 redirected: 30,
199 invalid: 5,
200 bytes_processed: 1000,
201 };
202 assert_eq!(stats.total_packets(), 65);
203 }
204
205 #[test]
206 fn test_xdp_stats_rates() {
207 let stats = XdpStats {
208 dropped: 25,
209 passed: 50,
210 redirected: 25,
211 invalid: 0,
212 bytes_processed: 0,
213 };
214 assert!((stats.drop_rate() - 25.0).abs() < 0.01);
215 assert!((stats.redirect_rate() - 25.0).abs() < 0.01);
216 }
217
218 #[test]
219 fn test_xdp_stats_rates_zero_total() {
220 let stats = XdpStats::default();
221 assert!(stats.drop_rate().abs() < f64::EPSILON);
222 assert!(stats.redirect_rate().abs() < f64::EPSILON);
223 }
224
225 #[test]
226 fn test_xdp_stats_merge() {
227 let mut stats1 = XdpStats {
228 dropped: 10,
229 passed: 20,
230 redirected: 30,
231 invalid: 5,
232 bytes_processed: 1000,
233 };
234 let stats2 = XdpStats {
235 dropped: 5,
236 passed: 10,
237 redirected: 15,
238 invalid: 2,
239 bytes_processed: 500,
240 };
241 stats1.merge(&stats2);
242
243 assert_eq!(stats1.dropped, 15);
244 assert_eq!(stats1.passed, 30);
245 assert_eq!(stats1.redirected, 45);
246 assert_eq!(stats1.invalid, 7);
247 assert_eq!(stats1.bytes_processed, 1500);
248 }
249
250 #[test]
251 fn test_xdp_stats_display() {
252 let stats = XdpStats {
253 dropped: 1,
254 passed: 2,
255 redirected: 3,
256 invalid: 4,
257 bytes_processed: 5,
258 };
259 let display = format!("{stats}");
260 assert!(display.contains("dropped: 1"));
261 assert!(display.contains("redirected: 3"));
262 }
263
264 #[test]
265 fn test_atomic_stats() {
266 let stats = AtomicXdpStats::new();
267
268 stats.inc_dropped();
269 stats.inc_dropped();
270 stats.inc_passed();
271 stats.inc_redirected();
272 stats.inc_invalid();
273 stats.add_bytes(100);
274
275 let snapshot = stats.snapshot();
276 assert_eq!(snapshot.dropped, 2);
277 assert_eq!(snapshot.passed, 1);
278 assert_eq!(snapshot.redirected, 1);
279 assert_eq!(snapshot.invalid, 1);
280 assert_eq!(snapshot.bytes_processed, 100);
281 }
282
283 #[test]
284 fn test_atomic_stats_reset() {
285 let stats = AtomicXdpStats::new();
286 stats.inc_dropped();
287 stats.reset();
288 assert_eq!(stats.snapshot().dropped, 0);
289 }
290
291 #[test]
292 fn test_per_cpu_stats() {
293 let mut per_cpu = PerCpuStats::new(4);
294
295 per_cpu.cpu_stats[0].dropped = 10;
296 per_cpu.cpu_stats[1].dropped = 20;
297 per_cpu.cpu_stats[2].redirected = 30;
298 per_cpu.cpu_stats[3].passed = 40;
299
300 let aggregate = per_cpu.aggregate();
301 assert_eq!(aggregate.dropped, 30);
302 assert_eq!(aggregate.redirected, 30);
303 assert_eq!(aggregate.passed, 40);
304
305 assert_eq!(per_cpu.get(0).unwrap().dropped, 10);
306 assert!(per_cpu.get(10).is_none());
307 assert_eq!(per_cpu.num_cpus(), 4);
308 }
309}