• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::time::Duration;
6 
7 use crate::info;
8 use crate::measure_timer_resolution;
9 use crate::nt_query_timer_resolution;
10 use crate::nt_set_timer_resolution;
11 use crate::set_time_period;
12 use crate::warn;
13 use crate::EnabledHighResTimer;
14 use crate::Result;
15 
16 /// Restores the Windows platform timer resolution to its original value on Drop.
17 struct NtSetTimerResolution {
18     previous_duration: Duration,
19     nt_set_timer_res: fn(Duration) -> Result<()>,
20 }
21 impl EnabledHighResTimer for NtSetTimerResolution {}
22 
23 impl NtSetTimerResolution {
new( query_timer_res: fn() -> Result<(Duration, Duration)>, nt_set_timer_res: fn(Duration) -> Result<()>, measure_timer_res: fn() -> Duration, ) -> Option<NtSetTimerResolution>24     fn new(
25         query_timer_res: fn() -> Result<(Duration, Duration)>,
26         nt_set_timer_res: fn(Duration) -> Result<()>,
27         measure_timer_res: fn() -> Duration,
28     ) -> Option<NtSetTimerResolution> {
29         match query_timer_res() {
30             Ok((current_res, max_res)) if max_res <= Duration::from_micros(500) => {
31                 match nt_set_timer_res(Duration::from_micros(500)) {
32                     Ok(()) => {
33                         let actual_res = measure_timer_res();
34                         if actual_res < Duration::from_millis(2) {
35                             info!("Successfully set timer res to 0.5ms with NtSetTimerResolution. Measured {}us.", actual_res.as_micros());
36                             Some(NtSetTimerResolution {
37                                 previous_duration: current_res,
38                                 nt_set_timer_res,
39                             })
40                         } else {
41                             warn!(
42                                 "Set timer res to 0.5ms with NtSetTimerResolution, but measured {}us.",
43                                 actual_res.as_micros()
44                             );
45                             None
46                         }
47                     }
48                     Err(e) => {
49                         warn!(
50                             "Failed to execute NtSetTimeResolution, got error code: {}",
51                             e
52                         );
53                         None
54                     }
55                 }
56             }
57             Ok((_, max_res)) => {
58                 info!(
59                     "System does not support 0.5ms timer. Max res: {}us",
60                     max_res.as_micros()
61                 );
62                 None
63             }
64             Err(e) => {
65                 warn!(
66                     "Failed to execute NtQueryTimeResolution, got error code: {}",
67                     e
68                 );
69                 None
70             }
71         }
72     }
73 }
74 
75 struct TimeBeginPeriod {
76     time_period_setter: fn(Duration, bool) -> Result<()>,
77 }
78 impl EnabledHighResTimer for TimeBeginPeriod {}
79 
80 impl Drop for NtSetTimerResolution {
drop(&mut self)81     fn drop(&mut self) {
82         if let Err(e) = (self.nt_set_timer_res)(self.previous_duration) {
83             warn!("Failed to unset nt timer resolution w/ error code: {}", e)
84         }
85     }
86 }
87 
88 impl Drop for TimeBeginPeriod {
drop(&mut self)89     fn drop(&mut self) {
90         if let Err(e) = (self.time_period_setter)(Duration::from_millis(1), false) {
91             warn!("Failed to unset timer resolution w/ error code: {}", e)
92         }
93     }
94 }
95 
96 /// Sets the Windows platform timer resolution to at least 1ms, or 0.5ms if possible on the
97 /// system.
enable_high_res_timers() -> Result<Box<dyn EnabledHighResTimer>>98 pub fn enable_high_res_timers() -> Result<Box<dyn EnabledHighResTimer>> {
99     enable_high_res_timers_inner(
100         set_time_period,
101         nt_query_timer_resolution,
102         nt_set_timer_resolution,
103         measure_timer_resolution,
104     )
105 }
106 
enable_high_res_timers_inner( time_period_setter: fn(Duration, bool) -> Result<()>, query_timer_res: fn() -> Result<(Duration, Duration)>, nt_set_timer_res: fn(Duration) -> Result<()>, measure_timer_res: fn() -> Duration, ) -> Result<Box<dyn EnabledHighResTimer>>107 fn enable_high_res_timers_inner(
108     time_period_setter: fn(Duration, bool) -> Result<()>,
109     query_timer_res: fn() -> Result<(Duration, Duration)>,
110     nt_set_timer_res: fn(Duration) -> Result<()>,
111     measure_timer_res: fn() -> Duration,
112 ) -> Result<Box<dyn EnabledHighResTimer>> {
113     // Determine if possible to set 500 micro timer res, and if so proceed with using
114     // undocumented winapis to do so. In case of any failures using the undocumented APIs,
115     // we'll fall back on timeBegin/EndPeriod.
116     let nt_timer_res =
117         NtSetTimerResolution::new(query_timer_res, nt_set_timer_res, measure_timer_res);
118 
119     match nt_timer_res {
120         Some(timer_res) => Ok(Box::new(timer_res)),
121         None => {
122             time_period_setter(Duration::from_millis(1), true)?;
123             let actual_res = measure_timer_res();
124             if actual_res > Duration::from_millis(2) {
125                 warn!(
126                     "Set timer res to 1ms using timeBeginPeriod, but measured >2ms (measured: {}us).",
127                     actual_res.as_micros(),
128                 );
129             } else {
130                 warn!(
131                     "Set timer res to 1ms using timeBeginPeriod. Measured {}us.",
132                     actual_res.as_micros(),
133                 );
134             }
135             Ok(Box::new(TimeBeginPeriod { time_period_setter }))
136         }
137     }
138 }
139 
140 #[cfg(test)]
141 /// Note that nearly all of these tests cannot run on Kokoro due to random slowness in that
142 /// environment.
143 mod tests {
144     use super::*;
145     use crate::Error;
146 
time_period_setter_broken(_d: Duration, _b: bool) -> Result<()>147     fn time_period_setter_broken(_d: Duration, _b: bool) -> Result<()> {
148         Err(Error::new(100))
149     }
150 
query_timer_failure() -> Result<(Duration, Duration)>151     fn query_timer_failure() -> Result<(Duration, Duration)> {
152         Err(Error::new(100))
153     }
154 
query_timer_res_high_res_available() -> Result<(Duration, Duration)>155     fn query_timer_res_high_res_available() -> Result<(Duration, Duration)> {
156         Ok((Duration::from_millis(15), Duration::from_micros(500)))
157     }
158 
query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)>159     fn query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)> {
160         Ok((Duration::from_millis(15), Duration::from_millis(1)))
161     }
162 
nt_set_timer_res_broken(_: Duration) -> Result<()>163     fn nt_set_timer_res_broken(_: Duration) -> Result<()> {
164         Err(Error::new(100))
165     }
measure_timer_res_1ms() -> Duration166     fn measure_timer_res_1ms() -> Duration {
167         Duration::from_millis(1)
168     }
169 
measure_timer_res_2ms() -> Duration170     fn measure_timer_res_2ms() -> Duration {
171         Duration::from_millis(2)
172     }
173 
assert_res_within_bound(actual_res: Duration)174     fn assert_res_within_bound(actual_res: Duration) {
175         assert!(
176             actual_res <= Duration::from_millis(2),
177             "actual_res was {:?}, expected <= 2ms",
178             actual_res
179         );
180     }
181 
182     // Note that on some systems, a <1ms timer is not available. In these cases, this test
183     // will not exercise the NtSetTimerResolution path.
184     #[test]
185     #[ignore]
test_nt_timer_works()186     fn test_nt_timer_works() {
187         let _timer_res = enable_high_res_timers_inner(
188             set_time_period,
189             nt_query_timer_resolution,
190             nt_set_timer_resolution,
191             measure_timer_res_1ms,
192         )
193         .unwrap();
194         assert_res_within_bound(measure_timer_resolution())
195     }
196 
197     #[test]
198     #[ignore]
test_nt_timer_falls_back_on_failure()199     fn test_nt_timer_falls_back_on_failure() {
200         let _timer_res = enable_high_res_timers_inner(
201             set_time_period,
202             query_timer_res_high_res_available,
203             nt_set_timer_res_broken,
204             measure_timer_res_1ms,
205         )
206         .unwrap();
207         assert_res_within_bound(measure_timer_resolution())
208     }
209 
210     #[test]
211     #[ignore]
test_nt_timer_falls_back_on_measurement_failure()212     fn test_nt_timer_falls_back_on_measurement_failure() {
213         let _timer_res = enable_high_res_timers_inner(
214             set_time_period,
215             query_timer_res_high_res_available,
216             nt_set_timer_res_broken,
217             measure_timer_res_2ms,
218         )
219         .unwrap();
220         assert_res_within_bound(measure_timer_resolution())
221     }
222 
223     #[test]
224     #[ignore]
test_nt_timer_falls_back_on_low_res_system()225     fn test_nt_timer_falls_back_on_low_res_system() {
226         let _timer_res = enable_high_res_timers_inner(
227             set_time_period,
228             query_timer_res_high_res_unavailable,
229             nt_set_timer_res_broken,
230             measure_timer_res_1ms,
231         )
232         .unwrap();
233         assert_res_within_bound(measure_timer_resolution())
234     }
235 
236     #[test]
237     #[ignore]
test_nt_timer_falls_back_on_query_failure()238     fn test_nt_timer_falls_back_on_query_failure() {
239         let _timer_res = enable_high_res_timers_inner(
240             set_time_period,
241             query_timer_failure,
242             nt_set_timer_res_broken,
243             measure_timer_res_1ms,
244         )
245         .unwrap();
246         assert_res_within_bound(measure_timer_resolution())
247     }
248 
249     #[test]
test_all_timer_sets_fail()250     fn test_all_timer_sets_fail() {
251         assert!(enable_high_res_timers_inner(
252             time_period_setter_broken,
253             query_timer_failure,
254             nt_set_timer_res_broken,
255             measure_timer_res_1ms,
256         )
257         .is_err());
258     }
259 }
260