use std::sync::{Arc, Mutex, OnceLock, PoisonError};
use kithara_platform::{
thread::sleep as thread_sleep,
time::{Duration, Instant, sleep},
};
use super::event::ProbeEvent;
pub(super) type SharedLog = Arc<Mutex<Vec<ProbeEvent>>>;
static GLOBAL_LOG: OnceLock<SharedLog> = OnceLock::new();
pub(crate) fn shared_log() -> SharedLog {
GLOBAL_LOG
.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
.clone()
}
#[must_use]
pub fn install() -> Recorder {
Recorder {
log: shared_log(),
start_at: Instant::now(),
install_id: crate::probe::current_install_id(),
}
}
#[derive(Clone)]
pub struct Recorder {
start_at: Instant,
log: SharedLog,
install_id: u64,
}
impl Recorder {
#[must_use]
pub fn events_with_probe(&self, name: &str) -> Vec<ProbeEvent> {
self.snapshot()
.into_iter()
.filter(|e| e.probe_name() == Some(name))
.collect()
}
#[must_use]
pub fn snapshot(&self) -> Vec<ProbeEvent> {
let log = self.log.lock().unwrap_or_else(PoisonError::into_inner);
log.iter()
.filter(|e| e.u64("install_id") == Some(self.install_id) && e.at >= self.start_at)
.cloned()
.collect()
}
#[must_use]
pub fn start_at(&self) -> Instant {
self.start_at
}
pub fn wait_for_probe<F>(&self, predicate: F, budget: Duration) -> Option<ProbeEvent>
where
F: Fn(&ProbeEvent) -> bool,
{
let deadline = Instant::now() + budget;
loop {
if let Some(evt) = self.snapshot().into_iter().find(|e| predicate(e)) {
return Some(evt);
}
if Instant::now() >= deadline {
return None;
}
thread_sleep(Duration::from_millis(5));
}
}
pub async fn wait_for_probe_async<F>(
&self,
predicate: F,
budget: Duration,
) -> Option<ProbeEvent>
where
F: Fn(&ProbeEvent) -> bool,
{
let deadline = Instant::now() + budget;
loop {
if let Some(evt) = self.snapshot().into_iter().find(|e| predicate(e)) {
return Some(evt);
}
if Instant::now() >= deadline {
return None;
}
sleep(Duration::from_millis(5)).await;
}
}
}