//! Node-level statistics for routing, forwarding, and discovery operations. //! //! Unlike `EthernetStats` (which uses `AtomicU64` + `Arc` for cross-task //! sharing), these counters use plain `u64` because `Node` handlers run //! on a single `&mut self` context. A `snapshot()` method produces a //! copyable struct for control socket queries. use serde::Serialize; /// Forwarding statistics — packets and bytes for each outcome. #[derive(Default)] pub struct ForwardingStats { pub received_packets: u64, pub received_bytes: u64, pub decode_error_packets: u64, pub decode_error_bytes: u64, pub ttl_exhausted_packets: u64, pub ttl_exhausted_bytes: u64, pub delivered_packets: u64, pub delivered_bytes: u64, pub forwarded_packets: u64, pub forwarded_bytes: u64, pub drop_no_route_packets: u64, pub drop_no_route_bytes: u64, pub drop_mtu_exceeded_packets: u64, pub drop_mtu_exceeded_bytes: u64, pub drop_send_error_packets: u64, pub drop_send_error_bytes: u64, pub originated_packets: u64, pub originated_bytes: u64, } impl ForwardingStats { pub fn record_received(&mut self, bytes: usize) { self.received_packets += 1; self.received_bytes += bytes as u64; } pub fn record_decode_error(&mut self, bytes: usize) { self.decode_error_packets += 1; self.decode_error_bytes += bytes as u64; } pub fn record_ttl_exhausted(&mut self, bytes: usize) { self.ttl_exhausted_packets += 1; self.ttl_exhausted_bytes += bytes as u64; } pub fn record_delivered(&mut self, bytes: usize) { self.delivered_packets += 1; self.delivered_bytes += bytes as u64; } pub fn record_forwarded(&mut self, bytes: usize) { self.forwarded_packets += 1; self.forwarded_bytes += bytes as u64; } pub fn record_drop_no_route(&mut self, bytes: usize) { self.drop_no_route_packets += 1; self.drop_no_route_bytes += bytes as u64; } pub fn record_drop_mtu_exceeded(&mut self, bytes: usize) { self.drop_mtu_exceeded_packets += 1; self.drop_mtu_exceeded_bytes += bytes as u64; } pub fn record_drop_send_error(&mut self, bytes: usize) { self.drop_send_error_packets += 1; self.drop_send_error_bytes += bytes as u64; } pub fn record_originated(&mut self, bytes: usize) { self.originated_packets += 1; self.originated_bytes += bytes as u64; } pub fn snapshot(&self) -> ForwardingStatsSnapshot { ForwardingStatsSnapshot { received_packets: self.received_packets, received_bytes: self.received_bytes, decode_error_packets: self.decode_error_packets, decode_error_bytes: self.decode_error_bytes, ttl_exhausted_packets: self.ttl_exhausted_packets, ttl_exhausted_bytes: self.ttl_exhausted_bytes, delivered_packets: self.delivered_packets, delivered_bytes: self.delivered_bytes, forwarded_packets: self.forwarded_packets, forwarded_bytes: self.forwarded_bytes, drop_no_route_packets: self.drop_no_route_packets, drop_no_route_bytes: self.drop_no_route_bytes, drop_mtu_exceeded_packets: self.drop_mtu_exceeded_packets, drop_mtu_exceeded_bytes: self.drop_mtu_exceeded_bytes, drop_send_error_packets: self.drop_send_error_packets, drop_send_error_bytes: self.drop_send_error_bytes, originated_packets: self.originated_packets, originated_bytes: self.originated_bytes, } } } /// Discovery statistics — packet counts for request and response handling. #[derive(Default)] pub struct DiscoveryStats { // Request counters pub req_received: u64, pub req_decode_error: u64, pub req_duplicate: u64, pub req_target_is_us: u64, pub req_forwarded: u64, pub req_ttl_exhausted: u64, pub req_initiated: u64, pub req_deduplicated: u64, pub req_backoff_suppressed: u64, pub req_forward_rate_limited: u64, pub req_bloom_miss: u64, pub req_no_tree_peer: u64, pub req_fallback_forwarded: u64, // Response counters pub resp_received: u64, pub resp_decode_error: u64, pub resp_forwarded: u64, pub resp_identity_miss: u64, pub resp_proof_failed: u64, pub resp_accepted: u64, pub resp_timed_out: u64, } impl DiscoveryStats { pub fn snapshot(&self) -> DiscoveryStatsSnapshot { DiscoveryStatsSnapshot { req_received: self.req_received, req_decode_error: self.req_decode_error, req_duplicate: self.req_duplicate, req_target_is_us: self.req_target_is_us, req_forwarded: self.req_forwarded, req_ttl_exhausted: self.req_ttl_exhausted, req_initiated: self.req_initiated, req_deduplicated: self.req_deduplicated, req_backoff_suppressed: self.req_backoff_suppressed, req_forward_rate_limited: self.req_forward_rate_limited, req_bloom_miss: self.req_bloom_miss, req_no_tree_peer: self.req_no_tree_peer, req_fallback_forwarded: self.req_fallback_forwarded, resp_received: self.resp_received, resp_decode_error: self.resp_decode_error, resp_forwarded: self.resp_forwarded, resp_identity_miss: self.resp_identity_miss, resp_proof_failed: self.resp_proof_failed, resp_accepted: self.resp_accepted, resp_timed_out: self.resp_timed_out, } } } /// Spanning tree statistics — announce handling and parent tracking. #[derive(Default)] pub struct TreeStats { // Inbound announce handling pub received: u64, pub decode_error: u64, pub unknown_peer: u64, pub addr_mismatch: u64, pub sig_failed: u64, pub stale: u64, pub accepted: u64, pub parent_switched: u64, pub loop_detected: u64, pub ancestry_changed: u64, // Outbound announce sending pub sent: u64, pub rate_limited: u64, pub send_failed: u64, // Cumulative events pub parent_switches: u64, pub parent_losses: u64, pub flap_dampened: u64, } impl TreeStats { pub fn snapshot(&self) -> TreeStatsSnapshot { TreeStatsSnapshot { received: self.received, decode_error: self.decode_error, unknown_peer: self.unknown_peer, addr_mismatch: self.addr_mismatch, sig_failed: self.sig_failed, stale: self.stale, accepted: self.accepted, parent_switched: self.parent_switched, loop_detected: self.loop_detected, ancestry_changed: self.ancestry_changed, sent: self.sent, rate_limited: self.rate_limited, send_failed: self.send_failed, parent_switches: self.parent_switches, parent_losses: self.parent_losses, flap_dampened: self.flap_dampened, } } } /// Bloom filter statistics — filter announce handling. #[derive(Default)] pub struct BloomStats { // Inbound announce handling pub received: u64, pub decode_error: u64, pub invalid: u64, pub unknown_peer: u64, pub stale: u64, pub fill_exceeded: u64, pub accepted: u64, // Outbound announce sending pub sent: u64, pub debounce_suppressed: u64, pub send_failed: u64, // Delta compression pub deltas_sent: u64, pub full_sends: u64, pub nacks_sent: u64, pub nacks_received: u64, // Adaptive sizing pub size_changes: u64, // Compression tracking pub total_compressed_bytes: u64, pub total_raw_bytes: u64, } impl BloomStats { pub fn snapshot(&self) -> BloomStatsSnapshot { BloomStatsSnapshot { received: self.received, decode_error: self.decode_error, invalid: self.invalid, unknown_peer: self.unknown_peer, stale: self.stale, fill_exceeded: self.fill_exceeded, accepted: self.accepted, sent: self.sent, debounce_suppressed: self.debounce_suppressed, send_failed: self.send_failed, deltas_sent: self.deltas_sent, full_sends: self.full_sends, nacks_sent: self.nacks_sent, nacks_received: self.nacks_received, size_changes: self.size_changes, total_compressed_bytes: self.total_compressed_bytes, total_raw_bytes: self.total_raw_bytes, } } } /// Error signal statistics — counts of each error signal type received. #[derive(Default)] pub struct ErrorSignalStats { pub coords_required: u64, pub path_broken: u64, pub mtu_exceeded: u64, } impl ErrorSignalStats { pub fn snapshot(&self) -> ErrorSignalStatsSnapshot { ErrorSignalStatsSnapshot { coords_required: self.coords_required, path_broken: self.path_broken, mtu_exceeded: self.mtu_exceeded, } } } /// Congestion event statistics — ECN CE tracking and detection triggers. #[derive(Default)] pub struct CongestionStats { /// Packets forwarded with the CE flag set (incoming CE or locally detected). pub ce_forwarded: u64, /// CE-flagged packets received at this node as final destination. pub ce_received: u64, /// Number of times detect_congestion() returned true. pub congestion_detected: u64, /// Rising-edge transport kernel drop events (not-dropping → dropping). pub kernel_drop_events: u64, } impl CongestionStats { pub fn record_ce_forwarded(&mut self) { self.ce_forwarded += 1; } pub fn record_ce_received(&mut self) { self.ce_received += 1; } pub fn record_congestion_detected(&mut self) { self.congestion_detected += 1; } pub fn record_kernel_drop_event(&mut self) { self.kernel_drop_events += 1; } pub fn snapshot(&self) -> CongestionStatsSnapshot { CongestionStatsSnapshot { ce_forwarded: self.ce_forwarded, ce_received: self.ce_received, congestion_detected: self.congestion_detected, kernel_drop_events: self.kernel_drop_events, } } } /// Aggregate node statistics. #[derive(Default)] pub struct NodeStats { pub forwarding: ForwardingStats, pub discovery: DiscoveryStats, pub tree: TreeStats, pub bloom: BloomStats, pub errors: ErrorSignalStats, pub congestion: CongestionStats, } impl NodeStats { pub fn new() -> Self { Self::default() } pub fn snapshot(&self) -> NodeStatsSnapshot { NodeStatsSnapshot { forwarding: self.forwarding.snapshot(), discovery: self.discovery.snapshot(), tree: self.tree.snapshot(), bloom: self.bloom.snapshot(), errors: self.errors.snapshot(), congestion: self.congestion.snapshot(), } } } // --- Snapshot types (copyable, serializable) --- #[derive(Clone, Debug, Default, Serialize)] pub struct ForwardingStatsSnapshot { pub received_packets: u64, pub received_bytes: u64, pub decode_error_packets: u64, pub decode_error_bytes: u64, pub ttl_exhausted_packets: u64, pub ttl_exhausted_bytes: u64, pub delivered_packets: u64, pub delivered_bytes: u64, pub forwarded_packets: u64, pub forwarded_bytes: u64, pub drop_no_route_packets: u64, pub drop_no_route_bytes: u64, pub drop_mtu_exceeded_packets: u64, pub drop_mtu_exceeded_bytes: u64, pub drop_send_error_packets: u64, pub drop_send_error_bytes: u64, pub originated_packets: u64, pub originated_bytes: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct DiscoveryStatsSnapshot { pub req_received: u64, pub req_decode_error: u64, pub req_duplicate: u64, pub req_target_is_us: u64, pub req_forwarded: u64, pub req_ttl_exhausted: u64, pub req_initiated: u64, pub req_deduplicated: u64, pub req_backoff_suppressed: u64, pub req_forward_rate_limited: u64, pub req_bloom_miss: u64, pub req_no_tree_peer: u64, pub req_fallback_forwarded: u64, pub resp_received: u64, pub resp_decode_error: u64, pub resp_forwarded: u64, pub resp_identity_miss: u64, pub resp_proof_failed: u64, pub resp_accepted: u64, pub resp_timed_out: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct TreeStatsSnapshot { pub received: u64, pub decode_error: u64, pub unknown_peer: u64, pub addr_mismatch: u64, pub sig_failed: u64, pub stale: u64, pub accepted: u64, pub parent_switched: u64, pub loop_detected: u64, pub ancestry_changed: u64, pub sent: u64, pub rate_limited: u64, pub send_failed: u64, pub parent_switches: u64, pub parent_losses: u64, pub flap_dampened: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct BloomStatsSnapshot { pub received: u64, pub decode_error: u64, pub invalid: u64, pub unknown_peer: u64, pub stale: u64, pub fill_exceeded: u64, pub accepted: u64, pub sent: u64, pub debounce_suppressed: u64, pub send_failed: u64, pub deltas_sent: u64, pub full_sends: u64, pub nacks_sent: u64, pub nacks_received: u64, pub size_changes: u64, pub total_compressed_bytes: u64, pub total_raw_bytes: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct ErrorSignalStatsSnapshot { pub coords_required: u64, pub path_broken: u64, pub mtu_exceeded: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct CongestionStatsSnapshot { pub ce_forwarded: u64, pub ce_received: u64, pub congestion_detected: u64, pub kernel_drop_events: u64, } #[derive(Clone, Debug, Default, Serialize)] pub struct NodeStatsSnapshot { pub forwarding: ForwardingStatsSnapshot, pub discovery: DiscoveryStatsSnapshot, pub tree: TreeStatsSnapshot, pub bloom: BloomStatsSnapshot, pub errors: ErrorSignalStatsSnapshot, pub congestion: CongestionStatsSnapshot, }