//! Support for counting various TCP stats for a Runtime. use futures::Stream; use tor_rtcompat::{TcpListener, TcpProvider}; use async_trait::async_trait; use futures::io::{AsyncRead, AsyncWrite}; use pin_project::pin_project; use std::io::Result as IoResult; use std::net::SocketAddr; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; /// Object that holds underlying counts for a Runtime. #[derive(Debug, Clone, Default)] pub(crate) struct TcpCount { /// number of TCP connections we've launched pub(crate) n_connect_attempt: usize, /// number of TCP connections we've successfully completed pub(crate) n_connect_ok: usize, /// number of incoming TCP connections we've received pub(crate) n_accept: usize, /// total number of bytes we've sent pub(crate) n_bytes_send: usize, /// total number of bytes we've received pub(crate) n_bytes_recv: usize, } /// A "Counting" wrapper around various objects, keeping running counts of TCP /// events. /// /// This can wrap most Tcp-related Runtime types. #[pin_project] pub(crate) struct Counting { /// The inner object that we're instrumenting #[pin] inner: R, /// A shared mutable set of counts. count: Arc>, } impl Clone for Counting where R: Clone, { fn clone(&self) -> Self { // TODO: Use educe instead. Self { inner: self.inner.clone(), count: self.count.clone(), } } } impl Counting { /// Return a new wrapper around a TcpProvider with a new set of statistics pub(crate) fn new_zeroed(inner: R) -> Self where R: TcpProvider, { Self { inner, count: Default::default(), } } /// Return a copy of our current statistics pub(crate) fn counts(&self) -> TcpCount { self.count.lock().expect("lock poisoned").clone() } } #[async_trait] impl TcpProvider for Counting { type TcpStream = Counting; type TcpListener = Counting; async fn connect(&self, addr: &SocketAddr) -> IoResult { { self.count.lock().expect("lock poisoned").n_connect_attempt += 1; } let inner = self.inner.connect(addr).await?; { self.count.lock().expect("lock poisoned").n_connect_ok += 1; } Ok(Counting { inner, count: self.count.clone(), }) } async fn listen(&self, addr: &SocketAddr) -> IoResult { let inner = self.inner.listen(addr).await?; Ok(Counting { inner, count: self.count.clone(), }) } } impl AsyncRead for Counting { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = self.project(); let outcome = this.inner.poll_read(cx, buf); if let Poll::Ready(Ok(n)) = outcome { this.count.lock().expect("poisoned lock").n_bytes_recv += n; } outcome } } impl AsyncWrite for Counting { fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { let this = self.project(); let outcome = this.inner.poll_write(cx, buf); if let Poll::Ready(Ok(n)) = outcome { this.count.lock().expect("poisoned lock").n_bytes_send += n; } outcome } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_close(cx) } } #[async_trait] impl TcpListener for Counting { type TcpStream = Counting; type Incoming = Counting; async fn accept(&self) -> IoResult<(Self::TcpStream, SocketAddr)> { let (inner, addr) = self.inner.accept().await?; { self.count.lock().expect("lock poisoned").n_accept += 1; } Ok(( Counting { inner, count: self.count.clone(), }, addr, )) } fn incoming(self) -> Self::Incoming { Counting { inner: self.inner.incoming(), count: self.count, } } fn local_addr(&self) -> IoResult { self.inner.local_addr() } } impl Stream for Counting where S: Stream>, { type Item = IoResult<(Counting, SocketAddr)>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); let outcome = this.inner.poll_next(cx); match outcome { Poll::Ready(Some(Ok((inner, addr)))) => { { this.count.lock().expect("lock poisoned").n_accept += 1; } Poll::Ready(Some(Ok(( Counting { inner, count: this.count.clone(), }, addr, )))) } Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, } } }