104 lines
4.3 KiB
Rust
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;
|