win_time_sys/performance.rs
1// Copyright (c) 2026 Mike Grier
2//! The performance counter, and the clock that reads it ([WT-D-3], [WT-D-4]).
3//!
4//! QPC's raw ticks, not `std::time::Instant` -- which reads the same counter but keeps its value to
5//! itself -- so a point can be recorded and read back. The counter is steady within a boot and, as
6//! Microsoft documents it, "independent of and isn't synchronized to any external time reference":
7//! its zero is unspecified, and a point means nothing after a restart.
8//!
9//! The tick's length is the counter's frequency, known only at run time. Microsoft documents it as
10//! "fixed at system boot and is consistent across all processors", so it is read once and kept.
11//!
12//! [WT-D-3]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/win-time-sys/DESIGN-NOTES.md#wt-d-3
13//! [WT-D-4]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/win-time-sys/DESIGN-NOTES.md#wt-d-4
14
15use core::num::NonZeroU64;
16use std::sync::OnceLock;
17
18use windows_sys::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency};
19
20use crate::clock::{Clock, Steady};
21use crate::timeline::{TimePoint, Timeline};
22
23#[cfg(test)]
24mod tests;
25
26/// The performance counter: ticks at its frequency, from an unspecified zero within this boot.
27///
28/// Its points do not compare with interrupt time's, though both count from around the boot:
29///
30/// ```compile_fail,E0308
31/// use win_time_sys::{Clock, InterruptClock, PerformanceClock};
32///
33/// let _ = PerformanceClock.now() < InterruptClock.now();
34/// ```
35pub enum PerformanceCounter {}
36
37impl Timeline for PerformanceCounter {
38 fn ticks_per_second() -> NonZeroU64 {
39 static FREQUENCY: OnceLock<NonZeroU64> = OnceLock::new();
40 *FREQUENCY.get_or_init(frequency)
41 }
42}
43
44/// [`PerformanceCounter`], through `QueryPerformanceCounter`.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
46pub struct PerformanceClock;
47
48impl Clock for PerformanceClock {
49 type Timeline = PerformanceCounter;
50
51 fn now(&self) -> TimePoint<PerformanceCounter> {
52 let mut ticks = 0;
53 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
54 let succeeded = unsafe { QueryPerformanceCounter(&mut ticks) };
55 // Documented to "always succeed" on Windows XP and later, with a count that is never
56 // negative.
57 debug_assert_ne!(succeeded, 0, "QueryPerformanceCounter failed");
58 TimePoint::from_ticks(ticks.cast_unsigned())
59 }
60}
61
62// The counter is steady within a boot and never set.
63impl Steady for PerformanceClock {}
64
65/// The counter's frequency, in ticks per second.
66fn frequency() -> NonZeroU64 {
67 let mut per_second = 0;
68 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
69 let succeeded = unsafe { QueryPerformanceFrequency(&mut per_second) };
70 // Documented to "always succeed" on Windows XP and later, so the frequency is never zero.
71 assert_ne!(succeeded, 0, "QueryPerformanceFrequency failed");
72 NonZeroU64::new(per_second.cast_unsigned()).expect("a performance counter frequency of zero")
73}