use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Stats {
pub rx_packets: u64,
pub rx_bytes: u64,
pub tx_packets: u64,
pub tx_bytes: u64,
pub rx_dropped: u64,
pub tx_dropped: u64,
pub errors: u64,
}
#[derive(Debug, Default)]
pub struct DeviceStats {
rx_packets: AtomicU64,
rx_bytes: AtomicU64,
tx_packets: AtomicU64,
tx_bytes: AtomicU64,
rx_dropped: AtomicU64,
tx_dropped: AtomicU64,
errors: AtomicU64,
}
impl DeviceStats {
pub fn new() -> DeviceStats {
DeviceStats::default()
}
#[inline]
pub fn record_rx(&self, bytes: usize) {
bump(&self.rx_packets, 1);
bump(&self.rx_bytes, bytes as u64);
}
#[inline]
pub fn record_tx(&self, bytes: usize) {
bump(&self.tx_packets, 1);
bump(&self.tx_bytes, bytes as u64);
}
#[inline]
pub fn record_rx_drop(&self) {
bump(&self.rx_dropped, 1);
}
#[inline]
pub fn record_tx_drop(&self) {
bump(&self.tx_dropped, 1);
}
#[inline]
pub fn record_error(&self) {
bump(&self.errors, 1);
}
pub fn snapshot(&self) -> Stats {
Stats {
rx_packets: self.rx_packets.load(Ordering::Relaxed),
rx_bytes: self.rx_bytes.load(Ordering::Relaxed),
tx_packets: self.tx_packets.load(Ordering::Relaxed),
tx_bytes: self.tx_bytes.load(Ordering::Relaxed),
rx_dropped: self.rx_dropped.load(Ordering::Relaxed),
tx_dropped: self.tx_dropped.load(Ordering::Relaxed),
errors: self.errors.load(Ordering::Relaxed),
}
}
pub fn reset(&self) {
for c in [
&self.rx_packets,
&self.rx_bytes,
&self.tx_packets,
&self.tx_bytes,
&self.rx_dropped,
&self.tx_dropped,
&self.errors,
] {
c.store(0, Ordering::Relaxed);
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct HubCounters {
pub received: u64,
pub forwarded: u64,
pub flooded: u64,
pub dropped: u64,
}
#[derive(Debug, Default)]
pub struct HubStats {
received: AtomicU64,
forwarded: AtomicU64,
flooded: AtomicU64,
dropped: AtomicU64,
}
impl HubStats {
pub fn new() -> HubStats {
HubStats::default()
}
#[inline]
pub fn record_received(&self) {
bump(&self.received, 1);
}
#[inline]
pub fn record_forwarded(&self, n: u64) {
bump(&self.forwarded, n);
}
#[inline]
pub fn record_flooded(&self) {
bump(&self.flooded, 1);
}
#[inline]
pub fn record_dropped(&self) {
bump(&self.dropped, 1);
}
pub fn snapshot(&self) -> HubCounters {
HubCounters {
received: self.received.load(Ordering::Relaxed),
forwarded: self.forwarded.load(Ordering::Relaxed),
flooded: self.flooded.load(Ordering::Relaxed),
dropped: self.dropped.load(Ordering::Relaxed),
}
}
pub fn reset(&self) {
for c in [
&self.received,
&self.forwarded,
&self.flooded,
&self.dropped,
] {
c.store(0, Ordering::Relaxed);
}
}
}
#[inline]
fn bump(counter: &AtomicU64, by: u64) {
let mut cur = counter.load(Ordering::Relaxed);
loop {
let next = cur.saturating_add(by);
if next == cur {
return;
}
match counter.compare_exchange_weak(cur, next, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => return,
Err(actual) => cur = actual,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counters_accumulate() {
let s = DeviceStats::new();
s.record_rx(100);
s.record_rx(50);
s.record_tx(10);
s.record_rx_drop();
s.record_tx_drop();
s.record_error();
let snap = s.snapshot();
assert_eq!(snap.rx_packets, 2);
assert_eq!(snap.rx_bytes, 150);
assert_eq!(snap.tx_packets, 1);
assert_eq!(snap.tx_bytes, 10);
assert_eq!(snap.rx_dropped, 1);
assert_eq!(snap.tx_dropped, 1);
assert_eq!(snap.errors, 1);
}
#[test]
fn reset_zeroes_everything() {
let s = DeviceStats::new();
s.record_rx(1);
s.record_tx(1);
s.reset();
assert_eq!(s.snapshot(), Stats::default());
}
#[test]
fn counters_saturate() {
let s = DeviceStats::new();
s.rx_bytes.store(u64::MAX - 1, Ordering::Relaxed);
s.record_rx(1000);
assert_eq!(s.snapshot().rx_bytes, u64::MAX, "must not wrap to zero");
}
#[test]
fn shared_across_threads() {
use std::sync::Arc;
let s = Arc::new(DeviceStats::new());
let mut handles = Vec::new();
for _ in 0..4 {
let s = s.clone();
handles.push(std::thread::spawn(move || {
for _ in 0..1000 {
s.record_rx(10);
}
}));
}
for h in handles {
h.join().unwrap();
}
let snap = s.snapshot();
assert_eq!(snap.rx_packets, 4000);
assert_eq!(snap.rx_bytes, 40000);
}
}