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}