gate4agent_node_core/
drive_loop_profile.rs1use gate4agent_runtime_native::tick_profile::{
17 duration_micros, Distribution, RingStats, SAMPLE_WINDOW,
18};
19use std::time::{Duration, Instant};
20
21#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct DriveLoopProfileSnapshot {
26 pub runtime_tick_us: Distribution,
27 pub event_drain_us: Distribution,
28 pub publish_terminal_frames_us: Distribution,
29 pub remainder_us: Distribution,
30 pub iterations_per_sec: Distribution,
37}
38
39#[derive(Clone, Debug)]
45pub struct DriveLoopProfiler {
46 runtime_tick_us: RingStats<SAMPLE_WINDOW>,
47 event_drain_us: RingStats<SAMPLE_WINDOW>,
48 publish_terminal_frames_us: RingStats<SAMPLE_WINDOW>,
49 remainder_us: RingStats<SAMPLE_WINDOW>,
50 iterations_per_sec: RingStats<SAMPLE_WINDOW>,
51 second_start: Instant,
54 iterations_this_second: u32,
55}
56
57impl Default for DriveLoopProfiler {
58 fn default() -> Self {
59 Self {
60 runtime_tick_us: RingStats::default(),
61 event_drain_us: RingStats::default(),
62 publish_terminal_frames_us: RingStats::default(),
63 remainder_us: RingStats::default(),
64 iterations_per_sec: RingStats::default(),
65 second_start: Instant::now(),
66 iterations_this_second: 0,
67 }
68 }
69}
70
71impl DriveLoopProfiler {
72 pub fn record_runtime_tick(&mut self, elapsed: Duration) {
76 self.runtime_tick_us.push(duration_micros(elapsed));
77 }
78
79 pub fn record_event_drain(&mut self, elapsed: Duration) {
83 self.event_drain_us.push(duration_micros(elapsed));
84 }
85
86 pub fn record_publish_terminal_frames(&mut self, elapsed: Duration) {
89 self.publish_terminal_frames_us.push(duration_micros(elapsed));
90 }
91
92 pub fn record_remainder(&mut self, elapsed: Duration) {
97 self.remainder_us.push(duration_micros(elapsed));
98 }
99
100 pub fn note_iteration(&mut self, now: Instant) {
113 let mut rolled = 0;
114 while now.saturating_duration_since(self.second_start) >= Duration::from_secs(1)
115 && rolled < SAMPLE_WINDOW
116 {
117 self.iterations_per_sec.push(self.iterations_this_second);
118 self.iterations_this_second = 0;
119 self.second_start += Duration::from_secs(1);
120 rolled += 1;
121 }
122 if rolled == SAMPLE_WINDOW {
123 self.second_start = now;
124 }
125 self.iterations_this_second = self.iterations_this_second.saturating_add(1);
126 }
127
128 pub fn snapshot(&self) -> DriveLoopProfileSnapshot {
129 DriveLoopProfileSnapshot {
130 runtime_tick_us: self.runtime_tick_us.stats(),
131 event_drain_us: self.event_drain_us.stats(),
132 publish_terminal_frames_us: self.publish_terminal_frames_us.stats(),
133 remainder_us: self.remainder_us.stats(),
134 iterations_per_sec: self.iterations_per_sec.stats(),
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn every_recorded_phase_reaches_the_snapshot() {
145 let mut profiler = DriveLoopProfiler::default();
146 profiler.record_runtime_tick(Duration::from_micros(100));
147 profiler.record_event_drain(Duration::from_micros(5));
148 profiler.record_publish_terminal_frames(Duration::from_micros(7));
149 profiler.record_remainder(Duration::from_micros(1));
150 let snapshot = profiler.snapshot();
151 assert_eq!(snapshot.runtime_tick_us.max, 100);
152 assert_eq!(snapshot.event_drain_us.max, 5);
153 assert_eq!(snapshot.publish_terminal_frames_us.max, 7);
154 assert_eq!(snapshot.remainder_us.max, 1);
155 }
156
157 #[test]
158 fn note_iteration_rolls_exactly_once_per_elapsed_second() {
159 let start = Instant::now();
160 let mut profiler = DriveLoopProfiler::default();
161 profiler.second_start = start;
162 profiler.note_iteration(start);
163 profiler.note_iteration(start + Duration::from_millis(10));
164 profiler.note_iteration(start + Duration::from_millis(1_001));
165 let snapshot = profiler.snapshot();
166 assert_eq!(snapshot.iterations_per_sec.count, 1);
168 assert_eq!(snapshot.iterations_per_sec.max, 2);
169 }
170
171 #[test]
172 fn note_iteration_caps_catch_up_rolls_after_a_long_gap() {
173 let start = Instant::now();
174 let mut profiler = DriveLoopProfiler::default();
175 profiler.second_start = start;
176 profiler.note_iteration(start + Duration::from_secs(10_000));
177 let snapshot = profiler.snapshot();
178 assert_eq!(snapshot.iterations_per_sec.count, SAMPLE_WINDOW);
179 assert_eq!(snapshot.iterations_per_sec.max, 0);
180 }
181}