1#![cfg_attr(docsrs, feature(doc_cfg))]
33
34mod instant;
35#[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
36mod tsc_now;
37
38pub use instant::Anchor;
39#[cfg(all(feature = "atomic", target_has_atomic = "64"))]
40#[cfg_attr(docsrs, doc(cfg(all(feature = "atomic", target_has_atomic = "64"))))]
41pub use instant::Atomic;
42pub use instant::Instant;
43
44#[inline]
49pub fn is_tsc_available() -> bool {
50 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
51 {
52 tsc_now::is_tsc_available()
53 }
54 #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
55 {
56 false
57 }
58}
59
60#[inline]
61pub(crate) fn current_cycle() -> u64 {
62 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
63 {
64 if tsc_now::is_tsc_available() {
65 tsc_now::current_cycle()
66 } else {
67 current_cycle_fallback()
68 }
69 }
70 #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
71 {
72 current_cycle_fallback()
73 }
74}
75
76#[cfg(not(feature = "fallback-coarse"))]
77pub(crate) fn current_cycle_fallback() -> u64 {
78 web_time::SystemTime::now()
79 .duration_since(web_time::UNIX_EPOCH)
80 .map(|d| d.as_nanos() as u64)
81 .unwrap_or(0)
82}
83
84#[cfg(feature = "fallback-coarse")]
85pub(crate) fn current_cycle_fallback() -> u64 {
86 let coarse = coarsetime::Instant::now_without_cache_update();
87 coarsetime::Duration::from_ticks(coarse.as_ticks()).as_nanos()
88}
89
90#[inline]
91pub(crate) fn nanos_per_cycle() -> f64 {
92 #[cfg(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64")))]
93 {
94 tsc_now::nanos_per_cycle()
95 }
96 #[cfg(not(all(target_os = "linux", any(target_arch = "x86", target_arch = "x86_64"))))]
97 {
98 1.0
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use std::time::Duration;
105 use std::time::Instant as StdInstant;
106
107 use rand::Rng;
108 use wasm_bindgen_test::wasm_bindgen_test;
109
110 use super::*;
111
112 #[test]
113 #[wasm_bindgen_test]
114 fn test_is_tsc_available() {
115 let _ = is_tsc_available();
116 }
117
118 #[test]
119 #[wasm_bindgen_test]
120 fn test_monotonic() {
121 let mut prev = 0;
122 for _ in 0..10000 {
123 let cur = current_cycle();
124 assert!(cur >= prev);
125 prev = cur;
126 }
127 }
128
129 #[test]
130 #[wasm_bindgen_test]
131 fn test_nanos_per_cycle() {
132 let _ = nanos_per_cycle();
133 }
134
135 #[test]
136 #[wasm_bindgen_test]
137 fn test_unix_time() {
138 let now = Instant::now();
139 let anchor = Anchor::new();
140 let unix_nanos = now.as_unix_nanos(&anchor);
141 assert!(unix_nanos > 0);
142 }
143
144 #[test]
145 fn test_duration() {
146 let mut rng = rand::rng();
147 for _ in 0..10 {
148 let instant = Instant::now();
149 let std_instant = StdInstant::now();
150 std::thread::sleep(Duration::from_millis(rng.random_range(100..500)));
151 let check = move || {
152 let duration_ns_fastant = instant.elapsed();
153 let duration_ns_std = std_instant.elapsed();
154
155 #[cfg(target_os = "windows")]
156 let expect_max_delta_ns = 40_000_000;
157 #[cfg(not(target_os = "windows"))]
158 let expect_max_delta_ns = 5_000_000;
159
160 let real_delta = (duration_ns_std.as_nanos() as i128
161 - duration_ns_fastant.as_nanos() as i128)
162 .abs();
163 assert!(
164 real_delta < expect_max_delta_ns,
165 "real delta: {}",
166 real_delta
167 );
168 };
169 check();
170 std::thread::spawn(check)
171 .join()
172 .expect("failed to join thread");
173 }
174 }
175}