Files
punktfunk/crates/punktfunk-core/src/stats.rs
T
enricobuehlerandClaude Fable 5 a2433d77cf fix(core): reordering no longer reads as packet loss — net late shards out of the loss estimate
Reversed/reordered delivery lets a FEC block reconstruct EARLY
(data + recovery >= k), counting still-in-flight shards into
fec_recovered_shards; window_loss_ppm then reported pure reordering as
loss, inflating LossReports — which size adaptive FEC and, since the
Automatic overhaul, feed the ABR controller (one severe window ends slow
start FOR GOOD, so a reorder burst could permanently kneecap a session's
climb).

Early reconstruct stays (it's the latency-right choice); the accounting
now nets it out. The reassembler counts a new fec_late_shards stat when a
parity-restored data shard ARRIVES after all — matched exactly: the
completed/abandoned-frame memory (ReassemblyWindow::completed, now a map)
remembers which shards each terminal frame reconstructed, and a late
arrival must match one (removed on hit), so wire duplicates of delivered
shards and stragglers of failed blocks count nothing. In-flight blocks
dedup via have_data. window_loss_ppm takes the late delta and estimates
from (recovered - late), saturating across window boundaries; both
callers (client core + probe) pass it.

The e2e reorder tests now assert the NET equals the true kill count in
both delivery orders, dup included (previously documented as a known
inflation). Not mirrored into the C-ABI PunktfunkStats — the loss windows
run in-core on every platform.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:59:17 +02:00

72 lines
3.1 KiB
Rust

//! Live counters for the frame-pacing / quality logic and the web UI.
use std::sync::atomic::{AtomicU64, Ordering};
/// Immutable snapshot, copied across the C ABI as `PunktfunkStats`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Stats {
pub frames_submitted: u64,
pub frames_completed: u64,
pub frames_dropped: u64,
pub packets_sent: u64,
pub packets_received: u64,
pub packets_dropped: u64,
/// Packets the host could NOT hand to the kernel because the send buffer was full (WouldBlock)
/// — the dominant loss mode at very high bitrate. Distinct from `packets_dropped` (recv-side
/// reassembler rejects). A non-zero, growing value means the link/encoder is outrunning the
/// send path; raise `net.core.wmem_max` / lower the bitrate, or wait for paced batched sending.
pub packets_send_dropped: u64,
pub fec_recovered_shards: u64,
/// Shards counted into [`fec_recovered_shards`](Self::fec_recovered_shards) that later ARRIVED
/// — reordered delivery lets a block reconstruct early from parity, so the still-in-flight
/// shards it "recovered" were late, not lost. Loss estimators must net this out
/// (`recovered - late`, see [`window_loss_ppm`](crate::quic::window_loss_ppm)) or plain
/// reordering reads as packet loss and spooks adaptive FEC + the bitrate controller.
/// Deliberately NOT mirrored into the C-ABI `PunktfunkStats` (loss windows run in-core).
pub fec_late_shards: u64,
pub bytes_sent: u64,
pub bytes_received: u64,
}
/// Atomic accumulators owned by a [`Session`](crate::session::Session). Snapshot to
/// [`Stats`] for readers. `Relaxed` ordering is fine: these are monotonic counters
/// read for display, never used to synchronize other memory.
#[derive(Default)]
pub struct StatsCounters {
pub frames_submitted: AtomicU64,
pub frames_completed: AtomicU64,
pub frames_dropped: AtomicU64,
pub packets_sent: AtomicU64,
pub packets_received: AtomicU64,
pub packets_dropped: AtomicU64,
pub packets_send_dropped: AtomicU64,
pub fec_recovered_shards: AtomicU64,
pub fec_late_shards: AtomicU64,
pub bytes_sent: AtomicU64,
pub bytes_received: AtomicU64,
}
impl StatsCounters {
#[inline]
pub fn add(counter: &AtomicU64, n: u64) {
counter.fetch_add(n, Ordering::Relaxed);
}
pub fn snapshot(&self) -> Stats {
let l = Ordering::Relaxed;
Stats {
frames_submitted: self.frames_submitted.load(l),
frames_completed: self.frames_completed.load(l),
frames_dropped: self.frames_dropped.load(l),
packets_sent: self.packets_sent.load(l),
packets_received: self.packets_received.load(l),
packets_dropped: self.packets_dropped.load(l),
packets_send_dropped: self.packets_send_dropped.load(l),
fec_recovered_shards: self.fec_recovered_shards.load(l),
fec_late_shards: self.fec_late_shards.load(l),
bytes_sent: self.bytes_sent.load(l),
bytes_received: self.bytes_received.load(l),
}
}
}