#![deny(missing_docs)]
#![deny(clippy::all)]
#![allow(clippy::must_use_candidate)]
use crossbeam_utils::atomic::AtomicCell;
use std::time::Duration;
use std::{cell::RefCell, sync::Arc};
use once_cell::sync::OnceCell;
mod clocks;
use self::clocks::{Counter, Monotonic};
mod mock;
pub use self::mock::{IntoNanoseconds, Mock};
mod instant;
pub use self::instant::Instant;
mod upkeep;
pub use self::upkeep::{Error, Handle, Upkeep};
mod stats;
use self::stats::Variance;
static GLOBAL_CLOCK: OnceCell<Clock> = OnceCell::new();
static GLOBAL_RECENT: AtomicCell<u64> = AtomicCell::new(0);
static GLOBAL_CALIBRATION: OnceCell<Calibration> = OnceCell::new();
thread_local! {
static CLOCK_OVERRIDE: RefCell<Option<Clock>> = RefCell::new(None);
}
const MINIMUM_CAL_ROUNDS: u64 = 500;
const MAXIMUM_CAL_ERROR_NS: u64 = 10;
const MAXIMUM_CAL_TIME_NS: u64 = 200 * 1000 * 1000;
#[derive(Debug)]
enum ClockType {
Monotonic(Monotonic),
Counter(AtomicCell<u64>, Monotonic, Counter, Calibration),
Mock(Arc<Mock>),
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct Calibration {
ref_time: u64,
src_time: u64,
scale_factor: u64,
scale_shift: u32,
}
impl Calibration {
fn new() -> Calibration {
Calibration {
ref_time: 0,
src_time: 0,
scale_factor: 1,
scale_shift: 1,
}
}
fn reset_timebases(&mut self, reference: Monotonic, source: &Counter) {
self.ref_time = reference.now();
self.src_time = source.now();
}
fn scale_src_to_ref(&self, src_raw: u64) -> u64 {
let delta = src_raw.saturating_sub(self.src_time);
let scaled = mul_div_po2_u64(delta, self.scale_factor, self.scale_shift);
scaled + self.ref_time
}
fn calibrate(&mut self, reference: Monotonic, source: &Counter) {
let mut variance = Variance::default();
let deadline = reference.now() + MAXIMUM_CAL_TIME_NS as u64;
self.reset_timebases(reference, source);
let loop_delta = 1000;
loop {
let mut last = reference.now();
let target = last + loop_delta;
while last < target {
last = reference.now();
}
if last >= deadline {
break;
}
self.adjust_cal_ratio(reference, source);
let r_time = reference.now();
let s_raw = source.now();
let s_time = self.scale_src_to_ref(s_raw);
variance.add(s_time as f64 - r_time as f64);
if variance.has_significant_result() {
let mean = variance.mean().abs();
let mean_error = variance.mean_error().abs();
let mwe = variance.mean_with_error();
let samples = variance.samples();
if samples > MINIMUM_CAL_ROUNDS
&& mwe < MAXIMUM_CAL_ERROR_NS as f64
&& mean_error / mean <= 1.0
{
break;
}
}
}
}
fn adjust_cal_ratio(&mut self, reference: Monotonic, source: &Counter) {
let ref_end = reference.now();
let src_end = source.now();
let ref_d = ref_end.wrapping_sub(self.ref_time);
let src_d = src_end.wrapping_sub(self.src_time);
let src_d_po2 = src_d
.checked_next_power_of_two()
.unwrap_or_else(|| 2_u64.pow(63));
let po2_ratio = src_d_po2 as f64 / src_d as f64;
self.scale_factor = (ref_d as f64 * po2_ratio) as u64;
self.scale_shift = src_d_po2.trailing_zeros();
}
}
impl Default for Calibration {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Clock {
inner: ClockType,
}
impl Clock {
pub fn new() -> Clock {
let reference = Monotonic::default();
let inner = if has_tsc_support() {
let source = Counter::default();
let calibration = GLOBAL_CALIBRATION.get_or_init(|| {
let mut calibration = Calibration::new();
calibration.calibrate(reference, &source);
calibration
});
ClockType::Counter(AtomicCell::new(0), reference, source, *calibration)
} else {
ClockType::Monotonic(reference)
};
Clock { inner }
}
pub fn mock() -> (Clock, Arc<Mock>) {
let mock = Arc::new(Mock::new());
let clock = Clock {
inner: ClockType::Mock(mock.clone()),
};
(clock, mock)
}
pub fn now(&self) -> Instant {
match &self.inner {
ClockType::Monotonic(monotonic) => Instant(monotonic.now()),
ClockType::Counter(last, _, counter, _) => {
let now = counter.now();
let last = last
.fetch_update(|current| Some(current.max(now)))
.expect("should never return an error");
let actual = std::cmp::max(now, last);
self.scaled(actual)
}
ClockType::Mock(mock) => Instant(mock.value()),
}
}
pub fn raw(&self) -> u64 {
match &self.inner {
ClockType::Monotonic(monotonic) => monotonic.now(),
ClockType::Counter(_, _, counter, _) => counter.now(),
ClockType::Mock(mock) => mock.value(),
}
}
pub fn scaled(&self, value: u64) -> Instant {
let scaled = match &self.inner {
ClockType::Counter(_, _, _, calibration) => calibration.scale_src_to_ref(value),
_ => value,
};
Instant(scaled)
}
pub fn delta(&self, start: u64, end: u64) -> Duration {
if end <= start {
return Duration::new(0, 0);
}
let delta = end.wrapping_sub(start);
let scaled = match &self.inner {
ClockType::Counter(_, _, _, calibration) => {
mul_div_po2_u64(delta, calibration.scale_factor, calibration.scale_shift)
}
_ => delta,
};
Duration::from_nanos(scaled)
}
pub fn recent(&self) -> Instant {
match &self.inner {
ClockType::Mock(mock) => Instant(mock.value()),
_ => Instant(GLOBAL_RECENT.load()),
}
}
#[cfg(test)]
fn reset_timebase(&mut self) -> bool {
match &mut self.inner {
ClockType::Counter(_, reference, source, calibration) => {
calibration.reset_timebases(*reference, source);
true
}
_ => false,
}
}
}
impl Default for Clock {
fn default() -> Clock {
Clock::new()
}
}
impl Clone for ClockType {
fn clone(&self) -> Self {
match self {
ClockType::Mock(mock) => ClockType::Mock(mock.clone()),
ClockType::Monotonic(monotonic) => ClockType::Monotonic(*monotonic),
ClockType::Counter(last, monotonic, counter, calibration) => ClockType::Counter(
AtomicCell::new(last.load()),
*monotonic,
counter.clone(),
*calibration,
),
}
}
}
pub fn with_clock<T>(clock: &Clock, f: impl FnOnce() -> T) -> T {
CLOCK_OVERRIDE.with(|current| {
let old = current.replace(Some(clock.clone()));
let result = f();
current.replace(old);
result
})
}
pub fn set_recent(instant: Instant) {
GLOBAL_RECENT.store(instant.0);
}
#[inline]
pub(crate) fn get_now() -> Instant {
if let Some(instant) = CLOCK_OVERRIDE.with(|clock| clock.borrow().as_ref().map(Clock::now)) {
instant
} else {
GLOBAL_CLOCK.get_or_init(Clock::new).now()
}
}
#[inline]
pub(crate) fn get_recent() -> Instant {
let recent = GLOBAL_RECENT.load();
if recent == 0 {
get_now()
} else {
Instant(recent)
}
}
#[inline]
fn mul_div_po2_u64(value: u64, numer: u64, denom: u32) -> u64 {
let mut v = u128::from(value);
v *= u128::from(numer);
v >>= denom;
v as u64
}
#[allow(dead_code)]
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
fn has_tsc_support() -> bool {
let cpuid = raw_cpuid::CpuId::new();
let has_invariant_tsc = cpuid
.get_advanced_power_mgmt_info()
.map_or(false, |apm| apm.has_invariant_tsc());
let has_rdtscp = cpuid
.get_extended_processor_and_feature_identifiers()
.map_or(false, |epf| epf.has_rdtscp());
has_invariant_tsc && has_rdtscp
}
#[allow(dead_code)]
#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2")))]
fn has_tsc_support() -> bool {
false
}
#[cfg(test)]
pub mod tests {
use super::{Clock, Counter, Monotonic};
use average::{Merge, Variance};
use std::time::{Duration, Instant};
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
mod configure_wasm_tests {
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_mock() {
let (clock, mock) = Clock::mock();
assert_eq!(clock.now().0, 0);
mock.increment(42);
assert_eq!(clock.now().0, 42);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_now() {
let clock = Clock::new();
assert!(clock.now().0 > 0);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_raw() {
let clock = Clock::new();
assert!(clock.raw() > 0);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_scaled() {
let clock = Clock::new();
let raw = clock.raw();
let scaled = clock.scaled(raw);
assert!(scaled.0 > 0);
}
#[test]
#[cfg_attr(not(feature = "flaky_tests"), ignore)]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn test_reference_source_calibration() {
let mut clock = Clock::new();
let reference = Monotonic::default();
let loops = 10000;
let mut overall = Variance::new();
let mut src_samples = [0u64; 1024];
let mut ref_samples = [0u64; 1024];
for _ in 0..loops {
clock.reset_timebase();
for i in 0..1024 {
src_samples[i] = clock.now().0;
ref_samples[i] = reference.now();
}
let is_src_monotonic = src_samples
.iter()
.map(Some)
.reduce(|last, current| last.and_then(|lv| current.filter(|cv| *cv >= lv)))
.flatten()
.copied();
assert_eq!(is_src_monotonic, Some(src_samples[1023]));
let is_ref_monotonic = ref_samples
.iter()
.map(Some)
.reduce(|last, current| last.and_then(|lv| current.filter(|cv| *cv >= lv)))
.flatten()
.copied();
assert_eq!(is_ref_monotonic, Some(ref_samples[1023]));
let local = src_samples
.iter()
.zip(ref_samples.iter())
.map(|(s, r)| *s as f64 - *r as f64)
.map(|f| f.abs())
.collect::<Variance>();
overall.merge(&local);
}
println!(
"reference/source delta: mean={} error={} mean-var={} samples={}",
overall.mean(),
overall.error(),
overall.variance_of_mean(),
overall.len(),
);
assert!(overall.mean() < 1000.0);
}
#[test]
#[cfg_attr(not(feature = "flaky_tests"), ignore)]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
wasm_bindgen_test::wasm_bindgen_test
)]
fn measure_source_reference_self_timing() {
let source = Counter::default();
let reference = Monotonic::default();
let loops = 10000;
let mut src_deltas = Vec::new();
let mut src_samples = [0u64; 100];
for _ in 0..loops {
let start = Instant::now();
for i in 0..100 {
src_samples[i] = source.now();
}
src_deltas.push(start.elapsed().as_secs_f64());
}
let mut ref_deltas = Vec::new();
let mut ref_samples = [0u64; 100];
for _ in 0..loops {
let start = Instant::now();
for i in 0..100 {
ref_samples[i] = reference.now();
}
ref_deltas.push(start.elapsed().as_secs_f64());
}
let src_variance = src_deltas.into_iter().collect::<Variance>();
let ref_variance = ref_deltas.into_iter().collect::<Variance>();
let src_variance_ns = Duration::from_secs_f64(src_variance.mean() / 100.0);
let ref_variance_ns = Duration::from_secs_f64(ref_variance.mean() / 100.0);
println!(
"source call average: {:?}, reference call average: {:?}",
src_variance_ns, ref_variance_ns
);
}
}