pub struct ContextSwitchHandler {
off_cpu_sampling_interval_ns: u64,
}
impl ContextSwitchHandler {
pub fn new(off_cpu_sampling_interval_ns: u64) -> Self {
Self {
off_cpu_sampling_interval_ns,
}
}
pub fn handle_switch_out(&self, timestamp: u64, thread: &mut ThreadContextSwitchData) {
match &thread.state {
ThreadState::Unknown => {
thread.state = ThreadState::Off {
off_switch_timestamp: timestamp,
};
}
ThreadState::On {
last_observed_on_timestamp,
} => {
let on_duration = timestamp - last_observed_on_timestamp;
thread.on_cpu_duration_since_last_sample += on_duration;
thread.state = ThreadState::Off {
off_switch_timestamp: timestamp,
};
}
ThreadState::Off { .. } => {
}
}
}
pub fn handle_switch_in(
&self,
timestamp: u64,
thread: &mut ThreadContextSwitchData,
) -> Option<OffCpuSampleGroup> {
let off_cpu_sample = match thread.state {
ThreadState::On {
last_observed_on_timestamp,
} => {
let on_duration = timestamp - last_observed_on_timestamp;
thread.on_cpu_duration_since_last_sample += on_duration;
None
}
ThreadState::Off {
off_switch_timestamp,
} => {
let off_duration = timestamp - off_switch_timestamp;
thread.off_cpu_duration_since_last_off_cpu_sample += off_duration;
self.maybe_consume_off_cpu(timestamp, thread)
}
ThreadState::Unknown => {
None
}
};
thread.state = ThreadState::On {
last_observed_on_timestamp: timestamp,
};
off_cpu_sample
}
pub fn handle_sample(
&self,
timestamp: u64,
thread: &mut ThreadContextSwitchData,
) -> Option<OffCpuSampleGroup> {
let off_cpu_sample = match thread.state {
ThreadState::On {
last_observed_on_timestamp,
} => {
let on_duration = timestamp - last_observed_on_timestamp;
thread.on_cpu_duration_since_last_sample += on_duration;
None
}
ThreadState::Off {
off_switch_timestamp,
} => {
let off_duration = timestamp - off_switch_timestamp;
thread.off_cpu_duration_since_last_off_cpu_sample += off_duration;
self.maybe_consume_off_cpu(timestamp, thread)
}
ThreadState::Unknown => {
None
}
};
thread.state = ThreadState::On {
last_observed_on_timestamp: timestamp,
};
off_cpu_sample
}
fn maybe_consume_off_cpu(
&self,
timestamp: u64,
thread: &mut ThreadContextSwitchData,
) -> Option<OffCpuSampleGroup> {
let interval = self.off_cpu_sampling_interval_ns;
if thread.off_cpu_duration_since_last_off_cpu_sample < interval {
return None;
}
let sample_count = thread.off_cpu_duration_since_last_off_cpu_sample / interval;
debug_assert!(sample_count >= 1);
let consumed_duration = sample_count * interval;
let remaining_duration =
thread.off_cpu_duration_since_last_off_cpu_sample - consumed_duration;
let begin_timestamp =
timestamp - (thread.off_cpu_duration_since_last_off_cpu_sample - interval);
let end_timestamp = timestamp - remaining_duration;
debug_assert_eq!(
end_timestamp - begin_timestamp,
(sample_count - 1) * interval
);
thread.off_cpu_duration_since_last_off_cpu_sample = remaining_duration;
Some(OffCpuSampleGroup {
begin_timestamp,
end_timestamp,
sample_count,
})
}
pub fn consume_cpu_delta(&self, thread: &mut ThreadContextSwitchData) -> u64 {
std::mem::replace(&mut thread.on_cpu_duration_since_last_sample, 0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OffCpuSampleGroup {
pub begin_timestamp: u64,
pub end_timestamp: u64,
pub sample_count: u64,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct ThreadContextSwitchData {
state: ThreadState,
on_cpu_duration_since_last_sample: u64,
off_cpu_duration_since_last_off_cpu_sample: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum ThreadState {
Unknown,
Off { off_switch_timestamp: u64 },
On { last_observed_on_timestamp: u64 },
}
impl Default for ThreadState {
fn default() -> Self {
ThreadState::Unknown
}
}
#[cfg(test)]
mod test {
use super::{ContextSwitchHandler, OffCpuSampleGroup, ThreadContextSwitchData};
#[test]
fn it_works() {
let mut thread = ThreadContextSwitchData::default();
let handler = ContextSwitchHandler::new(10);
let s = handler.handle_switch_in(0, &mut thread);
assert_eq!(s, None);
handler.handle_switch_out(3, &mut thread);
let s = handler.handle_switch_in(5, &mut thread);
assert_eq!(s, None);
let s = handler.handle_sample(12, &mut thread);
let delta = handler.consume_cpu_delta(&mut thread);
assert_eq!(s, None);
assert_eq!(delta, 10);
handler.handle_switch_out(13, &mut thread);
let s = handler.handle_switch_in(15, &mut thread);
assert_eq!(s, None);
handler.handle_switch_out(16, &mut thread);
let s = handler.handle_switch_in(21, &mut thread);
assert_eq!(s, None);
handler.handle_switch_out(23, &mut thread);
let s = handler.handle_switch_in(27, &mut thread);
assert_eq!(
s,
Some(OffCpuSampleGroup {
begin_timestamp: 24,
end_timestamp: 24,
sample_count: 1
})
);
let delta = handler.consume_cpu_delta(&mut thread);
assert_eq!(delta, 4);
handler.handle_switch_out(30, &mut thread);
let s = handler.handle_switch_in(48, &mut thread);
assert_eq!(
s,
Some(OffCpuSampleGroup {
begin_timestamp: 37,
end_timestamp: 47,
sample_count: 2
})
);
let delta = handler.consume_cpu_delta(&mut thread);
assert_eq!(delta, 3);
let s = handler.handle_sample(51, &mut thread);
let delta = handler.consume_cpu_delta(&mut thread);
assert_eq!(s, None);
assert_eq!(delta, 3);
let s = handler.handle_sample(61, &mut thread);
let delta = handler.consume_cpu_delta(&mut thread);
assert_eq!(s, None);
assert_eq!(delta, 10);
}
}