use std::{
path::PathBuf,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
#[derive(Debug, Default)]
struct ActiveTime {
completed: Duration,
started: Option<Instant>,
}
impl ActiveTime {
fn elapsed(&self, now: Instant) -> Duration {
self.completed.saturating_add(
self.started
.map_or(Duration::ZERO, |start| now.saturating_duration_since(start)),
)
}
fn finish(&mut self, now: Instant) -> Duration {
self.completed = self.elapsed(now);
self.started = None;
self.completed
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct SessionActiveTime(Arc<Mutex<ActiveTime>>);
impl SessionActiveTime {
pub(crate) fn from_events(events: &[super::SessionEvent]) -> Self {
let millis = events
.iter()
.filter_map(active_time_millis)
.max()
.unwrap_or(0);
Self(Arc::new(Mutex::new(ActiveTime {
completed: Duration::from_millis(millis),
started: None,
})))
}
pub(crate) fn elapsed(&self, now: Instant) -> Duration {
self.0
.lock()
.unwrap_or_else(|error| error.into_inner())
.elapsed(now)
}
pub(crate) fn is_active(&self) -> bool {
self.0
.lock()
.unwrap_or_else(|error| error.into_inner())
.started
.is_some()
}
pub(crate) fn start(
&self,
session: Option<super::Session>,
cwd: PathBuf,
report_error: impl Fn(String) + Send + 'static,
) -> ActiveTimeGuard {
self.0
.lock()
.unwrap_or_else(|error| error.into_inner())
.started = Some(Instant::now());
ActiveTimeGuard {
clock: self.clone(),
session,
cwd,
report_error: Box::new(report_error),
}
}
}
pub(crate) fn active_time_millis(event: &super::SessionEvent) -> Option<u64> {
matches!(
event.kind(),
Some(super::SessionEventKind::SessionActiveTime | super::SessionEventKind::Compaction)
)
.then(|| {
event
.payload
.get("session_active_ms")
.and_then(serde_json::Value::as_u64)
})
.flatten()
}
pub(crate) struct ActiveTimeGuard {
clock: SessionActiveTime,
session: Option<super::Session>,
cwd: PathBuf,
report_error: Box<dyn Fn(String) + Send>,
}
impl Drop for ActiveTimeGuard {
fn drop(&mut self) {
let elapsed = self
.clock
.0
.lock()
.unwrap_or_else(|error| error.into_inner())
.finish(Instant::now());
let millis = u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX);
if let Err(error) = super::record_session_event(
self.session.as_ref(),
&self.cwd,
super::SessionEventKind::SessionActiveTime,
serde_json::json!({"session_active_ms": millis}),
) {
(self.report_error)(format!("could not save session active time: {error}"));
}
}
}