use crate::afpacket::ffi;
use std::fmt;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CaptureStats {
pub packets: u32,
pub drops: u32,
pub freeze_count: u32,
}
impl From<ffi::tpacket_stats_v3> for CaptureStats {
fn from(s: ffi::tpacket_stats_v3) -> Self {
Self {
packets: s.tp_packets,
drops: s.tp_drops,
freeze_count: s.tp_freeze_q_cnt,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum DropBreakdown {
AfPacket {
freezes: u64,
},
Xdp {
rx_dropped: u64,
rx_invalid_descs: u64,
rx_ring_full: u64,
rx_fill_ring_empty_descs: u64,
tx_invalid_descs: u64,
tx_ring_empty_descs: u64,
},
}
impl DropBreakdown {
#[inline]
pub fn total_rx_drops(&self) -> u64 {
match *self {
DropBreakdown::AfPacket { .. } => 0,
DropBreakdown::Xdp {
rx_dropped,
rx_invalid_descs,
rx_ring_full,
rx_fill_ring_empty_descs,
..
} => rx_dropped
.saturating_add(rx_invalid_descs)
.saturating_add(rx_ring_full)
.saturating_add(rx_fill_ring_empty_descs),
}
}
}
impl fmt::Display for CaptureStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"packets: {}, drops: {}, freezes: {}",
self.packets, self.drops, self.freeze_count
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_zero() {
let s = CaptureStats::default();
assert_eq!(s.packets, 0);
assert_eq!(s.drops, 0);
assert_eq!(s.freeze_count, 0);
}
#[test]
fn from_tpacket_stats_v3() {
let raw = ffi::tpacket_stats_v3 {
tp_packets: 100,
tp_drops: 5,
tp_freeze_q_cnt: 1,
};
let s = CaptureStats::from(raw);
assert_eq!(s.packets, 100);
assert_eq!(s.drops, 5);
assert_eq!(s.freeze_count, 1);
}
#[test]
fn display_format() {
let s = CaptureStats {
packets: 42,
drops: 3,
freeze_count: 0,
};
assert_eq!(s.to_string(), "packets: 42, drops: 3, freezes: 0");
}
#[test]
fn af_packet_breakdown_has_no_rx_drops_only_freezes() {
let b = DropBreakdown::AfPacket { freezes: 4 };
assert_eq!(b.total_rx_drops(), 0);
}
#[test]
fn xdp_breakdown_total_rx_drops_includes_invalid_descs() {
let b = DropBreakdown::Xdp {
rx_dropped: 1,
rx_invalid_descs: 2,
rx_ring_full: 4,
rx_fill_ring_empty_descs: 8,
tx_invalid_descs: 16,
tx_ring_empty_descs: 32,
};
assert_eq!(b.total_rx_drops(), 15);
}
#[cfg(feature = "serde")]
#[test]
fn xdp_breakdown_serializes_each_source() {
let b = DropBreakdown::Xdp {
rx_dropped: 1,
rx_invalid_descs: 2,
rx_ring_full: 3,
rx_fill_ring_empty_descs: 4,
tx_invalid_descs: 5,
tx_ring_empty_descs: 6,
};
let json = serde_json::to_string(&b).expect("serialize");
assert!(json.contains("\"Xdp\""));
assert!(json.contains("\"rx_invalid_descs\":2"));
assert!(json.contains("\"rx_ring_full\":3"));
assert!(json.contains("\"tx_ring_empty_descs\":6"));
}
}