use std::time::{Duration, Instant};
pub trait Clock: Send + Sync + 'static {
fn now_micros(&self) -> i64;
fn micros_to_instant(&self, micros: i64) -> Option<Instant> {
let now_micros = self.now_micros();
let now_instant = Instant::now();
let delta = micros - now_micros;
if delta >= 0 {
Some(now_instant + Duration::from_micros(delta as u64))
} else {
now_instant.checked_sub(Duration::from_micros((-delta) as u64))
}
}
fn instant_to_micros(&self, instant: Instant) -> i64 {
let now_micros = self.now_micros();
let now_instant = Instant::now();
let delta = if instant >= now_instant {
instant.duration_since(now_instant).as_micros() as i64
} else {
-(now_instant.duration_since(instant).as_micros() as i64)
};
now_micros + delta
}
}
pub struct DefaultClock {
#[cfg_attr(target_os = "linux", allow(dead_code))]
epoch: Instant,
}
impl DefaultClock {
pub fn new() -> Self {
Self {
epoch: Instant::now(),
}
}
}
impl Default for DefaultClock {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for DefaultClock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DefaultClock").finish()
}
}
impl Clock for DefaultClock {
#[cfg(target_os = "linux")]
fn now_micros(&self) -> i64 {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let ret = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut ts) };
if ret != 0 {
log::error!(
"CLOCK_MONOTONIC_RAW unavailable (errno {}), falling back to CLOCK_MONOTONIC",
std::io::Error::last_os_error()
);
let ret2 = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
if ret2 != 0 {
log::error!(
"CLOCK_MONOTONIC also failed (errno {}); returning epoch (0)",
std::io::Error::last_os_error()
);
}
}
ts.tv_sec * 1_000_000 + ts.tv_nsec / 1_000
}
#[cfg(not(target_os = "linux"))]
fn now_micros(&self) -> i64 {
i64::try_from(Instant::now().duration_since(self.epoch).as_micros()).unwrap_or(i64::MAX)
}
}