#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct XdpStats {
pub rx_dropped: u64,
pub rx_invalid_descs: u64,
pub tx_invalid_descs: u64,
pub rx_ring_full: u64,
pub rx_fill_ring_empty_descs: u64,
pub tx_ring_empty_descs: u64,
}
impl From<libc::xdp_statistics> for XdpStats {
fn from(s: libc::xdp_statistics) -> Self {
Self {
rx_dropped: s.rx_dropped,
rx_invalid_descs: s.rx_invalid_descs,
tx_invalid_descs: s.tx_invalid_descs,
rx_ring_full: s.rx_ring_full,
rx_fill_ring_empty_descs: s.rx_fill_ring_empty_descs,
tx_ring_empty_descs: s.tx_ring_empty_descs,
}
}
}
#[allow(dead_code)]
impl XdpStats {
pub(crate) fn saturating_add(self, other: Self) -> Self {
Self {
rx_dropped: self.rx_dropped.saturating_add(other.rx_dropped),
rx_invalid_descs: self.rx_invalid_descs.saturating_add(other.rx_invalid_descs),
tx_invalid_descs: self.tx_invalid_descs.saturating_add(other.tx_invalid_descs),
rx_ring_full: self.rx_ring_full.saturating_add(other.rx_ring_full),
rx_fill_ring_empty_descs: self
.rx_fill_ring_empty_descs
.saturating_add(other.rx_fill_ring_empty_descs),
tx_ring_empty_descs: self
.tx_ring_empty_descs
.saturating_add(other.tx_ring_empty_descs),
}
}
pub(crate) fn queue_pressure_drops(&self) -> u64 {
self.rx_dropped
.saturating_add(self.rx_ring_full)
.saturating_add(self.rx_fill_ring_empty_descs)
}
pub(crate) fn to_capture_stats(self) -> crate::stats::CaptureStats {
crate::stats::CaptureStats {
packets: 0,
drops: self.queue_pressure_drops().min(u32::MAX as u64) as u32,
freeze_count: 0,
}
}
}
impl From<XdpStats> for crate::stats::DropBreakdown {
fn from(s: XdpStats) -> Self {
crate::stats::DropBreakdown::Xdp {
rx_dropped: s.rx_dropped,
rx_invalid_descs: s.rx_invalid_descs,
rx_ring_full: s.rx_ring_full,
rx_fill_ring_empty_descs: s.rx_fill_ring_empty_descs,
tx_invalid_descs: s.tx_invalid_descs,
tx_ring_empty_descs: s.tx_ring_empty_descs,
}
}
}
impl std::fmt::Display for XdpStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"rx_dropped={} rx_invalid={} tx_invalid={} rx_ring_full={} rx_fill_empty={} tx_ring_empty={}",
self.rx_dropped,
self.rx_invalid_descs,
self.tx_invalid_descs,
self.rx_ring_full,
self.rx_fill_ring_empty_descs,
self.tx_ring_empty_descs,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_libc_round_trip() {
let raw = libc::xdp_statistics {
rx_dropped: 1,
rx_invalid_descs: 2,
tx_invalid_descs: 3,
rx_ring_full: 4,
rx_fill_ring_empty_descs: 5,
tx_ring_empty_descs: 6,
};
let s = XdpStats::from(raw);
assert_eq!(s.rx_dropped, 1);
assert_eq!(s.rx_invalid_descs, 2);
assert_eq!(s.tx_invalid_descs, 3);
assert_eq!(s.rx_ring_full, 4);
assert_eq!(s.rx_fill_ring_empty_descs, 5);
assert_eq!(s.tx_ring_empty_descs, 6);
}
#[test]
fn display_format() {
let s = XdpStats {
rx_dropped: 7,
..XdpStats::default()
};
let out = s.to_string();
assert!(out.contains("rx_dropped=7"));
assert!(out.contains("tx_ring_empty=0"));
}
#[test]
fn to_capture_stats_drops_are_queue_pressure_only() {
let s = XdpStats {
rx_dropped: 1,
rx_invalid_descs: 1000,
rx_ring_full: 2,
rx_fill_ring_empty_descs: 4,
tx_invalid_descs: 9,
tx_ring_empty_descs: 9,
};
let cs = s.to_capture_stats();
assert_eq!(cs.packets, 0, "XDP_STATISTICS has no RX packet count");
assert_eq!(cs.drops, 7, "1 + 2 + 4 — invalid_descs excluded");
assert_eq!(cs.freeze_count, 0);
let b = crate::stats::DropBreakdown::from(s);
assert_eq!(b.total_rx_drops(), 1007);
}
#[test]
fn saturating_add_sums_each_field_independently() {
let a = XdpStats {
rx_dropped: 1,
rx_invalid_descs: 2,
tx_invalid_descs: 3,
rx_ring_full: 4,
rx_fill_ring_empty_descs: 5,
tx_ring_empty_descs: 6,
};
let sum = a.saturating_add(a);
assert_eq!(sum.rx_dropped, 2);
assert_eq!(sum.rx_invalid_descs, 4);
assert_eq!(sum.tx_invalid_descs, 6);
assert_eq!(sum.rx_ring_full, 8);
assert_eq!(sum.rx_fill_ring_empty_descs, 10);
assert_eq!(sum.tx_ring_empty_descs, 12);
let big = XdpStats {
rx_dropped: u64::MAX,
..XdpStats::default()
};
assert_eq!(big.saturating_add(big).rx_dropped, u64::MAX);
}
}