py_spy/
timer.rs

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