synvael/crates/net/src/stats.rs

104 lines
4.3 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-only
//! Transport statistics for a client connection.
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
/// Application-level message counters, incremented by the client's chunk task and read by the UI thread.
///
/// Held behind an [`Arc`] and mutated with relaxed atomics: each counter is independent, nothing else is ordered against them, and a reader that observes a slightly stale value is reporting a diagnostic figure, not making a decision.
#[derive(Debug, Default)]
pub(crate) struct NetCounters {
/// Chunk deliveries received from the server.
chunks_received: AtomicU64,
/// Chunk drop notifications received from the server.
drops_received: AtomicU64,
/// Subscription updates written to the server.
subscribes_sent: AtomicU64,
}
impl NetCounters {
/// Records one received chunk delivery.
pub(crate) fn record_chunk(&self) {
self.chunks_received.fetch_add(1, Ordering::Relaxed);
}
/// Records one received chunk drop notification.
pub(crate) fn record_drop(&self) {
self.drops_received.fetch_add(1, Ordering::Relaxed);
}
/// Records one subscription update written to the server.
pub(crate) fn record_subscribe(&self) {
self.subscribes_sent.fetch_add(1, Ordering::Relaxed);
}
}
/// A snapshot of one client connection's transport state.
///
/// Every field is a value copied at the moment of the call. A snapshot taken before the handshake completes reports `connected == false` and zeroes throughout, which is a meaningful state rather than missing data.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct NetStats {
/// Whether a QUIC connection is currently established.
pub connected: bool,
/// Bytes carried in UDP datagrams sent on this connection, including QUIC framing and retransmissions.
pub bytes_sent: u64,
/// Bytes carried in UDP datagrams received on this connection, including QUIC framing.
pub bytes_received: u64,
/// UDP datagrams sent on this connection.
pub datagrams_sent: u64,
/// UDP datagrams received on this connection.
pub datagrams_received: u64,
/// Chunk deliveries received since the connection was established.
pub chunks_received: u64,
/// Chunk drop notifications received since the connection was established.
pub drops_received: u64,
/// Subscription updates written to the server since the connection was established.
pub subscribes_sent: u64,
/// The QUIC stack's current round-trip-time estimate, in milliseconds.
pub rtt_ms: f32,
/// Packets the congestion controller has declared lost on the current path.
pub lost_packets: u64,
/// Current congestion window, in bytes: the ceiling on data in flight.
pub congestion_window: u64,
/// Largest UDP payload the current path is known to carry, in bytes, as discovered by path MTU probing.
pub path_mtu: u16,
}
/// Reads a connection's transport statistics, or reports the disconnected state.
///
/// `connection` is [`None`] until the handshake completes and after the connection closes.
pub(crate) fn snapshot(
connection: Option<&quinn::Connection>,
counters: &Arc<NetCounters>,
) -> NetStats {
let mut stats = NetStats {
chunks_received: counters.chunks_received.load(Ordering::Relaxed),
drops_received: counters.drops_received.load(Ordering::Relaxed),
subscribes_sent: counters.subscribes_sent.load(Ordering::Relaxed),
..NetStats::default()
};
let Some(connection) = connection else {
return stats;
};
let quic = connection.stats();
// A connection handle outlives the connection itself; a close reason is how a torn-down connection distinguishes itself from a live one, and its final counters stay readable either way.
stats.connected = connection.close_reason().is_none();
stats.bytes_sent = quic.udp_tx.bytes;
stats.bytes_received = quic.udp_rx.bytes;
stats.datagrams_sent = quic.udp_tx.datagrams;
stats.datagrams_received = quic.udp_rx.datagrams;
stats.rtt_ms = quic.path.rtt.as_secs_f32() * 1000.0;
stats.lost_packets = quic.path.lost_packets;
stats.congestion_window = quic.path.cwnd;
stats.path_mtu = quic.path.current_mtu;
stats
}
#[cfg(test)]
#[path = "tests/stats.rs"]
mod tests;