use std::sync::Arc;
use std::sync::{atomic::AtomicBool, Mutex};
use anyhow::{bail, Result};
use crossbeam_queue::ArrayQueue;
use futures::Future;
use indicatif::ProgressStyle;
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tracing::{error, info_span, Instrument, Span};
use tracing_indicatif::span_ext::IndicatifSpanExt;
pub const UPDATE_INTERVAL: u64 = 300;
const PROGRESS_BAR_STYLE: &str = " {msg} {wide_bar} {pos}/{len}";
pub trait MonitorableQueue: Send + Sync + 'static {
fn len(&self) -> impl Future<Output = usize> + Send;
fn is_empty(&self) -> impl Future<Output = bool> + Send {
async { self.len().await == 0 }
}
}
impl<T> MonitorableQueue for Arc<Mutex<Vec<T>>>
where
T: Send + Sync + 'static,
{
async fn len(&self) -> usize {
match self.lock() {
Ok(queue) => queue.len(),
Err(_) => {
error!("Failed to lock queue");
0
}
}
}
}
impl<T> MonitorableQueue for Arc<ArrayQueue<T>>
where
T: Send + Sync + 'static,
{
async fn len(&self) -> usize {
self.as_ref().len()
}
}
pub struct QueueMonitor {
thread_handle: Option<JoinHandle<Result<()>>>, stop_flag: Arc<AtomicBool>,
}
impl QueueMonitor {
pub fn new<T>(
title: &str,
queues: Vec<T>,
queue_sizes: Vec<u64>,
labels: Vec<String>,
update_interval_override: Option<u64>,
) -> Result<Self>
where
T: MonitorableQueue,
{
if queues.len() != queue_sizes.len() && queues.len() != labels.len() {
bail!("Number of queues, queue sizes and labels must be equal")
}
let stop_flag = Arc::new(AtomicBool::new(false));
let progress_span = info_span!("");
progress_span.pb_set_message(title);
let thread_handle = Some(tokio::spawn(
Self::view(
queues,
queue_sizes,
labels,
stop_flag.clone(),
update_interval_override,
)
.instrument(progress_span),
));
Ok(Self {
thread_handle,
stop_flag,
})
}
async fn view<T>(
queues: Vec<T>,
queue_sizes: Vec<u64>,
labels: Vec<String>,
stop_flag: Arc<AtomicBool>,
update_interval_override: Option<u64>,
) -> Result<()>
where
T: MonitorableQueue,
{
let _ = Span::current().enter();
let update_interval = update_interval_override.unwrap_or(UPDATE_INTERVAL);
let status_spans: Vec<Span> = queue_sizes
.iter()
.zip(labels.iter())
.map(|(queue_size, label)| {
let progress_span = info_span!("");
progress_span.pb_set_message(label);
progress_span.pb_set_position(0);
progress_span.pb_set_style(&ProgressStyle::with_template(PROGRESS_BAR_STYLE)?);
progress_span.pb_set_length(*queue_size);
Ok(progress_span)
})
.collect::<Result<Vec<Span>>>()?;
while !stop_flag.load(std::sync::atomic::Ordering::Relaxed) {
for (queue, status_span) in queues.iter().zip(status_spans.iter()) {
let _ = status_span.enter();
status_span.pb_set_position(queue.len().await as u64);
}
tokio::time::sleep(Duration::from_millis(update_interval)).await;
}
Ok(())
}
pub async fn stop(&mut self) -> Result<()> {
self.stop_flag
.store(true, std::sync::atomic::Ordering::Relaxed);
if let Some(handle) = self.thread_handle.take() {
handle.await??
}
Ok(())
}
}