use crate::os::sys;
#[inline]
pub fn now_ns() -> u64 {
#[cfg(unix)]
{
let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { sys::clock_gettime(sys::CLOCK_MONOTONIC, &mut ts) };
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}
#[cfg(windows)]
{
use std::sync::atomic::{AtomicU64, Ordering};
static FREQ: AtomicU64 = AtomicU64::new(0);
let mut f = FREQ.load(Ordering::Relaxed);
if f == 0 {
let mut v = 0i64;
unsafe { sys::QueryPerformanceFrequency(&mut v) };
f = v.max(1) as u64;
FREQ.store(f, Ordering::Relaxed);
}
let mut c = 0i64;
unsafe { sys::QueryPerformanceCounter(&mut c) };
let c = c as u64;
(c / f) * 1_000_000_000 + (c % f) * 1_000_000_000 / f
}
}
pub fn wall_ns() -> u64 {
#[cfg(target_os = "linux")]
{
let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { sys::clock_gettime(sys::CLOCK_REALTIME, &mut ts) };
if ts.tv_sec < 0 {
return 0;
}
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}
#[cfg(not(target_os = "linux"))]
{
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos() as u64)
}
}
#[inline]
pub fn coarse_ns() -> u64 {
#[cfg(target_os = "linux")]
{
let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { sys::clock_gettime(sys::CLOCK_MONOTONIC_COARSE, &mut ts) };
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}
#[cfg(not(target_os = "linux"))]
{
now_ns()
}
}
pub fn process_cpu_ns() -> u64 {
#[cfg(unix)]
{
let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { sys::clock_gettime(sys::CLOCK_PROCESS_CPUTIME_ID, &mut ts) };
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}
#[cfg(windows)]
{
let (mut c, mut e, mut k, mut u) = (0u64, 0u64, 0u64, 0u64);
unsafe { sys::GetProcessTimes(sys::GetCurrentProcess(), &mut c, &mut e, &mut k, &mut u) };
(k + u) * 100
}
}
#[cfg(test)]
pub(crate) fn thread_cpu_ns_for_tests() -> u64 {
#[cfg(unix)]
{
let mut ts = sys::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { sys::clock_gettime(sys::CLOCK_THREAD_CPUTIME_ID, &mut ts) };
ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64
}
#[cfg(windows)]
{
let (mut c, mut e, mut k, mut u) = (0u64, 0u64, 0u64, 0u64);
unsafe { sys::GetThreadTimes(sys::GetCurrentThread(), &mut c, &mut e, &mut k, &mut u) };
(k + u) * 100
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_clocks_count_work_not_sleep() {
let (p0, t0) = (process_cpu_ns(), thread_cpu_ns_for_tests());
std::thread::sleep(std::time::Duration::from_millis(50));
let t1 = thread_cpu_ns_for_tests();
assert!(t1 - t0 < 30_000_000, "sleeping used {} ns of thread CPU", t1 - t0);
let end = now_ns() + 60_000_000;
let mut x = 0u64;
while now_ns() < end {
x = x.wrapping_add(1);
}
let (p2, t2) = (process_cpu_ns(), thread_cpu_ns_for_tests());
assert!(t2 - t1 >= 20_000_000, "spinning 60 ms counted {} ns ({x})", t2 - t1);
assert!(p2 - p0 >= t2 - t1 - 16_000_000);
}
#[test]
fn clocks_are_monotonic_and_track_real_time() {
let a = now_ns();
let c0 = coarse_ns();
std::thread::sleep(std::time::Duration::from_millis(30));
let b = now_ns();
let c1 = coarse_ns();
assert!(b >= a + 25_000_000, "{a} {b}");
assert!(b < a + 5_000_000_000);
assert!(c1 >= c0 + 10_000_000);
let n = 1_000_000;
let t = std::time::Instant::now();
let mut x = 0u64;
for _ in 0..n {
x = x.wrapping_add(now_ns());
}
let precise = t.elapsed().as_nanos() as f64 / n as f64;
let t = std::time::Instant::now();
for _ in 0..n {
x = x.wrapping_add(coarse_ns());
}
let coarse = t.elapsed().as_nanos() as f64 / n as f64;
eprintln!("now_ns {precise:.1} ns, coarse_ns {coarse:.1} ns per read ({x})");
}
}