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}