Skip to content

Commit fb73602

Browse files
committed
feat(transport): add TCP stats on Linux
1 parent c0c5728 commit fb73602

3 files changed

Lines changed: 167 additions & 93 deletions

File tree

msg-transport/src/tcp/metered.rs

Lines changed: 0 additions & 91 deletions
This file was deleted.

msg-transport/src/tcp/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use msg_common::async_error;
1111

1212
use crate::{Acceptor, PeerAddress, Transport, TransportExt};
1313

14-
mod metered;
15-
pub use metered::TcpStats;
14+
mod stats;
15+
pub use stats::TcpStats;
1616

1717
#[derive(Debug, Default)]
1818
pub struct Config;

msg-transport/src/tcp/stats.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use std::{os::fd::AsRawFd, time::Duration};
2+
3+
use tokio::net::TcpStream;
4+
5+
#[derive(Debug, Default)]
6+
pub struct TcpStats {
7+
/// The congestion window in bytes.
8+
pub cwnd: u32,
9+
/// Our receive window in bytes.
10+
pub rwnd: u32,
11+
/// Our send window (= the peer's advertised receive window) in bytes.
12+
pub snd_wnd: u32,
13+
/// The most recent RTT sample.
14+
pub last_rtt: Duration,
15+
/// The smoothed round-trip time.
16+
pub smoothed_rtt: Duration,
17+
/// The round-trip time variance.
18+
pub rtt_var: Duration,
19+
/// Total bytes sent on the socket.
20+
pub tx_bytes: u64,
21+
/// Total bytes received on the socket.
22+
pub rx_bytes: u64,
23+
/// Total sender retransmitted bytes on the socket.
24+
pub retransmitted_bytes: u64,
25+
/// Total sender retransmitted packets on the socket.
26+
pub retransmitted_packets: u64,
27+
/// The current retransmission timeout.
28+
pub rto: Duration,
29+
}
30+
31+
#[cfg(target_os = "macos")]
32+
impl TryFrom<&TcpStream> for TcpStats {
33+
type Error = std::io::Error;
34+
35+
/// Gathers stats from the given TCP socket file descriptor, sourced from the OS with
36+
/// [`libc::getsockopt`].
37+
fn try_from(stream: &TcpStream) -> Result<Self, Self::Error> {
38+
let mut info = unsafe { std::mem::zeroed::<libc::tcp_connection_info>() };
39+
let mut len = std::mem::size_of::<libc::tcp_connection_info>() as libc::socklen_t;
40+
41+
let rc = unsafe {
42+
libc::getsockopt(
43+
stream.as_raw_fd(),
44+
libc::IPPROTO_TCP,
45+
libc::TCP_CONNECTION_INFO,
46+
&mut info as *mut _ as *mut _,
47+
&mut len,
48+
)
49+
};
50+
51+
if rc != 0 {
52+
return Err(std::io::Error::last_os_error());
53+
}
54+
55+
Ok(info.into())
56+
}
57+
}
58+
59+
#[cfg(target_os = "macos")]
60+
impl From<libc::tcp_connection_info> for TcpStats {
61+
/// Converts a [`libc::tcp_connection_info`] into [`TcpStats`].
62+
fn from(info: libc::tcp_connection_info) -> Self {
63+
// Window sizes
64+
let cwnd = info.tcpi_snd_cwnd;
65+
let rwnd = info.tcpi_rcv_wnd;
66+
let snd_wnd = info.tcpi_snd_wnd;
67+
68+
// RTT
69+
let last_rtt = Duration::from_millis(info.tcpi_rttcur as u64);
70+
let smoothed_rtt = Duration::from_millis(info.tcpi_srtt as u64);
71+
let rtt_var = Duration::from_millis(info.tcpi_rttvar as u64);
72+
73+
// Volumes
74+
let tx_bytes = info.tcpi_txbytes;
75+
let rx_bytes = info.tcpi_rxbytes;
76+
77+
// Retransmissions
78+
let retransmitted_bytes = info.tcpi_txretransmitbytes;
79+
let retransmitted_packets = info.tcpi_rxretransmitpackets;
80+
let rto = Duration::from_millis(info.tcpi_rto as u64);
81+
82+
Self {
83+
cwnd,
84+
rwnd,
85+
snd_wnd,
86+
last_rtt,
87+
smoothed_rtt,
88+
rtt_var,
89+
tx_bytes,
90+
rx_bytes,
91+
retransmitted_bytes,
92+
retransmitted_packets,
93+
rto,
94+
}
95+
}
96+
}
97+
98+
#[cfg(target_os = "linux")]
99+
impl TryFrom<&TcpStream> for TcpStats {
100+
type Error = std::io::Error;
101+
102+
/// Gathers stats from the given TCP socket file descriptor, sourced from the OS with
103+
/// [`libc::getsockopt`].
104+
fn try_from(stream: &TcpStream) -> Result<Self, Self::Error> {
105+
let mut info = unsafe { std::mem::zeroed::<libc::tcp_info>() };
106+
let mut len = std::mem::size_of::<libc::tcp_info>() as libc::socklen_t;
107+
108+
let rc = unsafe {
109+
libc::getsockopt(
110+
stream.as_raw_fd(),
111+
libc::IPPROTO_TCP,
112+
libc::TCP_INFO,
113+
&mut info as *mut _ as *mut _,
114+
&mut len,
115+
)
116+
};
117+
118+
if rc != 0 {
119+
return Err(std::io::Error::last_os_error());
120+
}
121+
122+
Ok(info.into())
123+
}
124+
}
125+
126+
#[cfg(target_os = "linux")]
127+
impl From<libc::tcp_info> for TcpStats {
128+
/// Converts a [`libc::tcp_info`] into [`TcpStats`].
129+
fn from(info: libc::tcp_info) -> Self {
130+
// On Linux, tcpi_snd_cwnd is in segments; convert to bytes using snd_mss.
131+
let cwnd = info.tcpi_snd_cwnd.saturating_mul(info.tcpi_snd_mss);
132+
// The advertised receive window space is already in bytes.
133+
let rwnd = info.tcpi_rcv_space;
134+
let snd_wnd = info.tcpi_snd_wnd;
135+
136+
// RTT fields are reported in microseconds.
137+
let last_rtt = Duration::from_micros(info.tcpi_rtt as u64);
138+
let smoothed_rtt = Duration::from_micros(info.tcpi_rtt as u64); // best approximation available
139+
let rtt_var = Duration::from_micros(info.tcpi_rttvar as u64);
140+
141+
// Volumes; tcpi_bytes_acked/received are bytes, retrans_bytes is bytes.
142+
let tx_bytes = info.tcpi_bytes_acked;
143+
let rx_bytes = info.tcpi_bytes_received;
144+
145+
// Retransmissions
146+
let retransmitted_bytes = info.tcpi_retrans_bytes;
147+
let retransmitted_packets = info.tcpi_total_retrans as u64;
148+
// RTO is in microseconds.
149+
let rto = Duration::from_micros(info.tcpi_rto as u64);
150+
151+
Self {
152+
cwnd,
153+
rwnd,
154+
snd_wnd,
155+
last_rtt,
156+
smoothed_rtt,
157+
rtt_var,
158+
tx_bytes,
159+
rx_bytes,
160+
retransmitted_bytes,
161+
retransmitted_packets,
162+
rto,
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)