Log rx and tx bytes when smoltcp stream is dropped

This commit is contained in:
David Lönnhager committed 2026-09-22 15:51:07 +02:00
1 parent 31d83ffaad
commit 26afc6189a
3 files changed
+56 -2

No files matched your search

+15 -2
View File
@@ -80,6 +80,7 @@ pub fn smoltcp_network(
cmd_tx,
notify: notify.clone(),
timeout: None,
log_stats_on_drop: false,
};
let poll_task = tokio::spawn(poll_loop(
@@ -110,6 +111,7 @@ pub struct SmoltcpHandle {
cmd_tx: mpsc::Sender<SocketCmd>,
notify: Arc<Notify>,
timeout: Option<Duration>,
log_stats_on_drop: bool,
}
impl SmoltcpHandle {
@@ -130,8 +132,13 @@ impl SmoltcpHandle {
.await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "poll loop closed"))?;
self.notify.notify_one();
rx.await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "poll loop dropped"))?
let mut stream = rx
.await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "poll loop dropped"))??;
if self.log_stats_on_drop {
stream = stream.log_stats_on_drop();
}
Ok(stream)
}
/// See [`smoltcp::socket::tcp::Socket::set_timeout`].
@@ -140,6 +147,12 @@ impl SmoltcpHandle {
self
}
/// Log stats (rx and tx bytes) when stream is dropped.
pub fn log_stats(mut self) -> Self {
self.log_stats_on_drop = true;
self
}
/// Create an ICMP socket bound to the given identifier.
///
/// Only ICMP echo replies matching this identifier will be delivered.
@@ -7,6 +7,7 @@ use std::{
pin::Pin,
sync::Arc,
task::{Context, Poll, ready},
time::Instant,
};
use tokio::{
io::{AsyncRead, AsyncWrite, ReadBuf},
@@ -26,6 +27,15 @@ pub struct SmoltcpTcpStream {
notify: Arc<Notify>,
/// Buffer received bytes
read_buf: BytesMut,
/// Traffic counters. Enabled by [`Self::log_stats_on_drop`].
stats: Option<Stats>,
}
/// Traffic counters for a stream, logged when it is dropped.
struct Stats {
rx_bytes: u64,
tx_bytes: u64,
start_time: Instant,
}
impl SmoltcpTcpStream {
@@ -40,6 +50,30 @@ impl SmoltcpTcpStream {
upstream_tx: PollSender::new(write_tx),
notify,
read_buf: BytesMut::new(),
stats: None,
}
}
/// Begin counting bytes received and sent on this stream. Log when dropped.
pub(crate) fn log_stats_on_drop(mut self) -> Self {
self.stats = Some(Stats {
rx_bytes: 0,
tx_bytes: 0,
start_time: Instant::now(),
});
self
}
}
impl Drop for SmoltcpTcpStream {
fn drop(&mut self) {
if let Some(stats) = &self.stats {
log::debug!(
"smoltcp TCP stream closed. RX: {} bytes, TX: {} bytes, duration: {:?}",
stats.rx_bytes,
stats.tx_bytes,
stats.start_time.elapsed(),
);
}
}
}
@@ -65,6 +99,9 @@ impl AsyncRead for SmoltcpTcpStream {
// promptly refill it from smoltcp's receive buffer rather than
// waiting for the next inbound packet or poll-delay tick.
this.notify.notify_one();
if let Some(stats) = &mut this.stats {
stats.rx_bytes += u64::try_from(data.len()).unwrap();
}
let n = std::cmp::min(buf.remaining(), data.len());
buf.put_slice(&data[..n]);
// `read_buf` is empty here (we only poll the channel once it is),
@@ -96,6 +133,9 @@ impl AsyncWrite for SmoltcpTcpStream {
.send_item(buf.to_vec())
.map_err(|_| broken_pipe())?;
this.notify.notify_one();
if let Some(stats) = &mut this.stats {
stats.tx_bytes += u64::try_from(n).unwrap();
}
Poll::Ready(Ok(n))
}
@@ -319,6 +319,7 @@ async fn request_ephemeral_peer_through(
let exchange = async {
let stream = net
.set_timeout(config.tcp_timeout)
.log_stats()
.tcp_connect(config_service)
.await
.map_err(Error::TcpSocketError)?;