feat(obs): report broadcast frame counters

PLAN.md calls out "Broadcast traffic flowing" as a user-facing diagnostic.
The tunnel stats only reported total Ethernet frame counts, so the client
could not distinguish whether broadcast traffic was actually crossing the
tunnel.

Add defaulted broadcast tx/rx counters to TunnelStats while preserving the
existing constructor and old JSON compatibility. Client and gateway accounting
now increments those counters from validated Ethernet frames, and the client
diagnostics line reports the broadcast flow next to total frame counts.

Relay peer stats logs include the new counters so operators can see broadcast
activity from forwarded stats snapshots too.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-obs -p lanparty-client-core -p lanparty-gateway \
  -p lanparty-client-win -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
This commit is contained in:
2026-05-21 21:54:35 +02:00
parent 9722adbd70
commit 21a69626e0
8 changed files with 180 additions and 38 deletions
+3
View File
@@ -6,3 +6,6 @@ edition.workspace = true
[dependencies]
lanparty-proto = { path = "../lanparty-proto" }
serde.workspace = true
[dev-dependencies]
serde_json.workspace = true
+48 -1
View File
@@ -135,6 +135,10 @@ impl FrameLog {
pub struct TunnelStats {
ethernet_frames_tx: u64,
ethernet_frames_rx: u64,
#[serde(default)]
broadcast_frames_tx: u64,
#[serde(default)]
broadcast_frames_rx: u64,
datagrams_tx: u64,
datagrams_rx: u64,
dropped_frames: u64,
@@ -154,6 +158,8 @@ impl TunnelStats {
Self {
ethernet_frames_tx,
ethernet_frames_rx,
broadcast_frames_tx: 0,
broadcast_frames_rx: 0,
datagrams_tx,
datagrams_rx,
dropped_frames,
@@ -161,6 +167,17 @@ impl TunnelStats {
}
}
#[must_use]
pub const fn with_broadcast_frames(
mut self,
broadcast_frames_tx: u64,
broadcast_frames_rx: u64,
) -> Self {
self.broadcast_frames_tx = broadcast_frames_tx;
self.broadcast_frames_rx = broadcast_frames_rx;
self
}
#[must_use]
pub const fn ethernet_frames_tx(&self) -> u64 {
self.ethernet_frames_tx
@@ -171,6 +188,16 @@ impl TunnelStats {
self.ethernet_frames_rx
}
#[must_use]
pub const fn broadcast_frames_tx(&self) -> u64 {
self.broadcast_frames_tx
}
#[must_use]
pub const fn broadcast_frames_rx(&self) -> u64 {
self.broadcast_frames_rx
}
#[must_use]
pub const fn datagrams_tx(&self) -> u64 {
self.datagrams_tx
@@ -433,7 +460,7 @@ mod tests {
#[test]
fn exposes_client_diagnostics() {
let mac = MacAddr::new([0x02, 1, 2, 3, 4, 5]);
let stats = TunnelStats::new(1, 2, 3, 4, 5, 6);
let stats = TunnelStats::new(1, 2, 3, 4, 5, 6).with_broadcast_frames(7, 8);
let diagnostics = ClientDiagnostics::new(
RelayDiagnostics::new(true, true, true),
QuicDiagnostics::new(true, Some(1400)),
@@ -455,6 +482,26 @@ mod tests {
assert_eq!(diagnostics.tap().mac(), Some(mac));
assert_eq!(diagnostics.tap().mtu(), Some(1200));
assert_eq!(diagnostics.tap().ip().unwrap().to_string(), "10.73.42.51");
assert_eq!(diagnostics.stats().broadcast_frames_tx(), 7);
assert_eq!(diagnostics.stats().broadcast_frames_rx(), 8);
assert_eq!(diagnostics.stats().dropped_frames(), 5);
}
#[test]
fn defaults_missing_broadcast_stats_to_zero() {
let stats: TunnelStats = serde_json::from_str(
r#"{
"ethernet_frames_tx": 1,
"ethernet_frames_rx": 2,
"datagrams_tx": 3,
"datagrams_rx": 4,
"dropped_frames": 5,
"malformed_frames": 6
}"#,
)
.unwrap();
assert_eq!(stats.broadcast_frames_tx(), 0);
assert_eq!(stats.broadcast_frames_rx(), 0);
}
}