diff --git a/crates/federation-net/src/engine.rs b/crates/federation-net/src/engine.rs index 784e75a..db71ea0 100644 --- a/crates/federation-net/src/engine.rs +++ b/crates/federation-net/src/engine.rs @@ -100,6 +100,119 @@ pub struct ByteStream { _connection: Connection, } +/// The currently selected transport path kind for a byte stream connection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConnectionPathKind { + /// A direct UDP path to the remote peer. + Direct, + /// A path through the peer's home relay. + Relay, + /// A custom transport path. + Custom, + /// Iroh has not reported a selected path yet. + Unknown, +} + +impl ConnectionPathKind { + /// Returns a compact stable label for logs and UI. + pub fn as_str(self) -> &'static str { + match self { + Self::Direct => "direct", + Self::Relay => "relay", + Self::Custom => "custom", + Self::Unknown => "unknown", + } + } +} + +impl fmt::Display for ConnectionPathKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Snapshot of low-level transport statistics for a byte stream connection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ByteStreamConnectionStats { + /// The authenticated peer on the other side of the stream. + pub peer_id: EndpointId, + /// The transport path currently selected for application traffic. + pub selected_path: ConnectionPathKind, + /// Number of open paths on the underlying Iroh connection. + pub open_paths: usize, + /// Number of open direct paths. + pub direct_paths: usize, + /// Number of open relay paths. + pub relay_paths: usize, + /// Number of open custom transport paths. + pub custom_paths: usize, + /// RTT of the selected path, when available. + pub selected_rtt: Option, + /// UDP bytes transmitted on the selected path. + pub selected_tx_bytes: u64, + /// UDP bytes received on the selected path. + pub selected_rx_bytes: u64, + /// UDP bytes transmitted on the whole connection. + pub total_tx_bytes: u64, + /// UDP bytes received on the whole connection. + pub total_rx_bytes: u64, + /// Packets lost on the whole connection. + pub lost_packets: u64, + /// Bytes lost on the whole connection. + pub lost_bytes: u64, +} + +impl ByteStreamConnectionStats { + fn from_connection(peer_id: EndpointId, connection: &Connection) -> Self { + let totals = connection.stats(); + let paths = connection.paths(); + let mut stats = Self { + peer_id, + selected_path: ConnectionPathKind::Unknown, + open_paths: paths.len(), + direct_paths: 0, + relay_paths: 0, + custom_paths: 0, + selected_rtt: None, + selected_tx_bytes: 0, + selected_rx_bytes: 0, + total_tx_bytes: totals.udp_tx.bytes, + total_rx_bytes: totals.udp_rx.bytes, + lost_packets: totals.lost_packets, + lost_bytes: totals.lost_bytes, + }; + + for path in paths.iter() { + let path_kind = if path.is_ip() { + stats.direct_paths += 1; + ConnectionPathKind::Direct + } else if path.is_relay() { + stats.relay_paths += 1; + ConnectionPathKind::Relay + } else { + stats.custom_paths += 1; + ConnectionPathKind::Custom + }; + if path.is_selected() { + let path_stats = path.stats(); + stats.selected_path = path_kind; + stats.selected_rtt = Some(path_stats.rtt); + stats.selected_tx_bytes = path_stats.udp_tx.bytes; + stats.selected_rx_bytes = path_stats.udp_rx.bytes; + } + } + + stats + } +} + +impl ByteStream { + /// Returns a point-in-time snapshot of the underlying Iroh connection. + pub fn connection_stats(&self) -> ByteStreamConnectionStats { + ByteStreamConnectionStats::from_connection(self.peer_id, &self._connection) + } +} + impl fmt::Debug for ByteStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ByteStream") diff --git a/crates/federation-net/src/lib.rs b/crates/federation-net/src/lib.rs index bb1a008..8d5426b 100644 --- a/crates/federation-net/src/lib.rs +++ b/crates/federation-net/src/lib.rs @@ -65,7 +65,10 @@ pub use config::{ DEFAULT_EVENT_CHANNEL_CAPACITY, DEFAULT_MAX_CONCURRENT_STREAMS_PER_PEER, DEFAULT_MAX_MESSAGE_SIZE, DEFAULT_REQUEST_TIMEOUT, NetworkConfig, NetworkConfigBuilder, }; -pub use engine::{ByteStream, Message, NetworkEngine, StreamAcceptor}; +pub use engine::{ + ByteStream, ByteStreamConnectionStats, ConnectionPathKind, Message, NetworkEngine, + StreamAcceptor, +}; pub use error::{NetworkError, Result}; pub use event::{ConnectionDirection, NetworkEvent, NetworkEventReceiver}; pub use protocol::{ALPN, NetworkId, PROTOCOL_VERSION, SchemaId}; diff --git a/crates/music-dht/src/lib.rs b/crates/music-dht/src/lib.rs index bcc61ac..1d2dd40 100644 --- a/crates/music-dht/src/lib.rs +++ b/crates/music-dht/src/lib.rs @@ -121,6 +121,6 @@ pub use service::{ // Re-exported types from the transport layer that appear in this API. pub use federation_net::{ - ByteStream, EndpointAddr, EndpointId, NetworkId, PeerTicket, RecvStream, RendezvousConfig, - SecretKey, SendStream, StreamAcceptor, + ByteStream, ByteStreamConnectionStats, ConnectionPathKind, EndpointAddr, EndpointId, NetworkId, + PeerTicket, RecvStream, RendezvousConfig, SecretKey, SendStream, StreamAcceptor, };