use metrics::{Counter, Histogram, counter, gauge, histogram};
use std::{
fmt::{self, Debug, Formatter},
time::{Duration, Instant},
};
use tokio::{
sync::mpsc::{
Receiver, Sender, channel,
error::{SendError, SendTimeoutError, TryRecvError, TrySendError},
},
time::sleep,
};
pub struct CorroSender<T> {
send_count: Counter,
failed_sends: Counter,
send_time: Histogram,
inner: Sender<T>,
}
impl<T> Clone for CorroSender<T> {
fn clone(&self) -> Self {
Self {
send_count: self.send_count.clone(),
failed_sends: self.failed_sends.clone(),
send_time: self.send_time.clone(),
inner: self.inner.clone(),
}
}
}
impl<T: Debug> Debug for CorroSender<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
pub struct CorroReceiver<T> {
recv_count: Counter,
inner: Receiver<T>,
}
impl<T: Debug> Debug for CorroReceiver<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.inner.fmt(f)
}
}
pub fn bounded<T: Send + 'static>(
capacity: usize,
label: &'static str,
) -> (CorroSender<T>, CorroReceiver<T>) {
gauge!("corro.runtime.channel.max_capacity", "channel_name" => label).set(capacity as f64);
let send_count = counter!("corro.runtime.channel.send_count", "channel_name" => label);
let recv_count = counter!("corro.runtime.channel.recv_count", "channel_name" => label);
let failed_sends = counter!("corro.runtime.channel.failed_send_count", "channel_name" => label);
let capacity_gauge = gauge!("corro.runtime.channel.capacity", "channel_name" => label);
let send_time = histogram!("corro.runtime.channel.send_delay", "channel_name" => label);
let (tx, rx) = channel(capacity);
let inner_channel = tx.clone();
tokio::spawn(async move {
loop {
sleep(Duration::from_secs(1)).await;
if inner_channel.is_closed() {
break;
}
capacity_gauge.set(inner_channel.capacity() as f64);
}
});
(
CorroSender {
send_count,
failed_sends,
send_time,
inner: tx,
},
CorroReceiver {
recv_count,
inner: rx,
},
)
}
impl<T> CorroSender<T> {
pub async fn send(&self, value: T) -> Result<(), SendError<T>> {
let before = Instant::now();
self.inner
.send(value)
.await
.inspect(|_r| {
self.send_time.record(before.elapsed().as_secs_f64());
self.send_count.increment(1);
})
.inspect_err(|_e| {
self.failed_sends.increment(1);
})
}
pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> {
self.inner
.try_send(value)
.inspect(|_r| {
self.send_count.increment(1);
})
.inspect_err(|_e| {
self.failed_sends.increment(1);
})
}
pub fn blocking_send(&self, value: T) -> Result<(), SendError<T>> {
let before = Instant::now();
self.inner
.blocking_send(value)
.inspect(|_r| {
self.send_time.record(before.elapsed().as_secs_f64());
self.send_count.increment(1);
})
.inspect_err(|_e| {
self.failed_sends.increment(1);
})
}
pub async fn send_timeout(
&self,
value: T,
timeout: Duration,
) -> Result<(), SendTimeoutError<T>> {
let before = Instant::now();
self.inner
.send_timeout(value, timeout)
.await
.inspect(|_r| {
self.send_time.record(before.elapsed().as_secs_f64());
self.send_count.increment(1);
})
.inspect_err(|_e| {
self.failed_sends.increment(1);
})
}
}
impl<T> CorroReceiver<T> {
pub async fn recv(&mut self) -> Option<T> {
self.inner.recv().await.inspect(|_r| {
self.recv_count.increment(1);
})
}
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
self.inner.try_recv().inspect(|_r| {
self.recv_count.increment(1);
})
}
pub fn blocking_recv(&mut self) -> Option<T> {
self.inner.blocking_recv().inspect(|_r| {
self.recv_count.increment(1);
})
}
}