py_spy_for_datakit/timer.rs
1use std::time::{Instant, Duration};
2#[cfg(windows)]
3use winapi::um::timeapi;
4
5use rand;
6use rand_distr::{Exp, Distribution};
7
8/// Timer is an iterator that sleeps an appropriate amount of time between iterations
9/// so that we can sample the process a certain number of times a second.
10/// We're using an irregular sampling strategy to avoid aliasing effects that can happen
11/// if the target process runs code at a similar schedule as the profiler:
12/// https://github.com/benfred/py-spy/issues/94
13pub struct Timer {
14 start: Instant,
15 desired: Duration,
16 exp: Exp<f64>,
17}
18
19impl Timer {
20 pub fn new(rate: f64) -> Timer {
21 // This changes a system-wide setting on Windows so that the OS wakes up every 1ms
22 // instead of the default 15.6ms. This is required to have a sleep call
23 // take less than 15ms, which we need since we usually profile at more than 64hz.
24 // The downside is that this will increase power usage: good discussions are:
25 // https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/
26 // and http://www.belshe.com/2010/06/04/chrome-cranking-up-the-clock/
27 #[cfg(windows)]
28 unsafe { timeapi::timeBeginPeriod(1); }
29
30 let start = Instant::now();
31 Timer{start, desired: Duration::from_secs(0), exp: Exp::new(rate).unwrap()}
32 }
33}
34
35impl Iterator for Timer {
36 type Item = Result<Duration, Duration>;
37
38 fn next(&mut self) -> Option<Self::Item> {
39 let elapsed = self.start.elapsed();
40
41 // figure out how many nanoseconds should come between the previous and
42 // the next sample using an exponential distribution to avoid aliasing
43 let nanos = 1_000_000_000.0 * self.exp.sample(&mut rand::thread_rng());
44
45 // since we want to account for the amount of time the sampling takes
46 // we keep track of when we should sleep to (rather than just sleeping
47 // the amount of time from the previous line).
48 self.desired += Duration::from_nanos(nanos as u64);
49
50 // sleep if appropriate, or warn if we are behind in sampling
51 if self.desired > elapsed {
52 std::thread::sleep(self.desired - elapsed);
53 Some(Ok(self.desired - elapsed))
54 } else {
55 Some(Err(elapsed - self.desired))
56 }
57 }
58}
59
60impl Drop for Timer {
61 fn drop(&mut self) {
62 #[cfg(windows)]
63 unsafe { timeapi::timeEndPeriod(1); }
64 }
65}