use gate4agent_runtime_native::tick_profile::{
duration_micros, Distribution, RingStats, SAMPLE_WINDOW,
};
use std::time::{Duration, Instant};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(super) struct DriveLoopProfileSnapshot {
pub(super) runtime_tick_us: Distribution,
pub(super) event_drain_us: Distribution,
pub(super) publish_terminal_frames_us: Distribution,
pub(super) remainder_us: Distribution,
pub(super) iterations_per_sec: Distribution,
}
#[derive(Clone, Debug)]
pub(super) struct DriveLoopProfiler {
runtime_tick_us: RingStats<SAMPLE_WINDOW>,
event_drain_us: RingStats<SAMPLE_WINDOW>,
publish_terminal_frames_us: RingStats<SAMPLE_WINDOW>,
remainder_us: RingStats<SAMPLE_WINDOW>,
iterations_per_sec: RingStats<SAMPLE_WINDOW>,
second_start: Instant,
iterations_this_second: u32,
}
impl Default for DriveLoopProfiler {
fn default() -> Self {
Self {
runtime_tick_us: RingStats::default(),
event_drain_us: RingStats::default(),
publish_terminal_frames_us: RingStats::default(),
remainder_us: RingStats::default(),
iterations_per_sec: RingStats::default(),
second_start: Instant::now(),
iterations_this_second: 0,
}
}
}
impl DriveLoopProfiler {
pub(super) fn record_runtime_tick(&mut self, elapsed: Duration) {
self.runtime_tick_us.push(duration_micros(elapsed));
}
pub(super) fn record_event_drain(&mut self, elapsed: Duration) {
self.event_drain_us.push(duration_micros(elapsed));
}
pub(super) fn record_publish_terminal_frames(&mut self, elapsed: Duration) {
self.publish_terminal_frames_us.push(duration_micros(elapsed));
}
pub(super) fn record_remainder(&mut self, elapsed: Duration) {
self.remainder_us.push(duration_micros(elapsed));
}
pub(super) fn note_iteration(&mut self, now: Instant) {
let mut rolled = 0;
while now.saturating_duration_since(self.second_start) >= Duration::from_secs(1)
&& rolled < SAMPLE_WINDOW
{
self.iterations_per_sec.push(self.iterations_this_second);
self.iterations_this_second = 0;
self.second_start += Duration::from_secs(1);
rolled += 1;
}
if rolled == SAMPLE_WINDOW {
self.second_start = now;
}
self.iterations_this_second = self.iterations_this_second.saturating_add(1);
}
pub(super) fn snapshot(&self) -> DriveLoopProfileSnapshot {
DriveLoopProfileSnapshot {
runtime_tick_us: self.runtime_tick_us.stats(),
event_drain_us: self.event_drain_us.stats(),
publish_terminal_frames_us: self.publish_terminal_frames_us.stats(),
remainder_us: self.remainder_us.stats(),
iterations_per_sec: self.iterations_per_sec.stats(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_recorded_phase_reaches_the_snapshot() {
let mut profiler = DriveLoopProfiler::default();
profiler.record_runtime_tick(Duration::from_micros(100));
profiler.record_event_drain(Duration::from_micros(5));
profiler.record_publish_terminal_frames(Duration::from_micros(7));
profiler.record_remainder(Duration::from_micros(1));
let snapshot = profiler.snapshot();
assert_eq!(snapshot.runtime_tick_us.max, 100);
assert_eq!(snapshot.event_drain_us.max, 5);
assert_eq!(snapshot.publish_terminal_frames_us.max, 7);
assert_eq!(snapshot.remainder_us.max, 1);
}
#[test]
fn note_iteration_rolls_exactly_once_per_elapsed_second() {
let start = Instant::now();
let mut profiler = DriveLoopProfiler::default();
profiler.second_start = start;
profiler.note_iteration(start);
profiler.note_iteration(start + Duration::from_millis(10));
profiler.note_iteration(start + Duration::from_millis(1_001));
let snapshot = profiler.snapshot();
assert_eq!(snapshot.iterations_per_sec.count, 1);
assert_eq!(snapshot.iterations_per_sec.max, 2);
}
#[test]
fn note_iteration_caps_catch_up_rolls_after_a_long_gap() {
let start = Instant::now();
let mut profiler = DriveLoopProfiler::default();
profiler.second_start = start;
profiler.note_iteration(start + Duration::from_secs(10_000));
let snapshot = profiler.snapshot();
assert_eq!(snapshot.iterations_per_sec.count, SAMPLE_WINDOW);
assert_eq!(snapshot.iterations_per_sec.max, 0);
}
}