gate4agent_shell_native/
efficiency.rs1use std::time::Duration;
20
21use gate4agent::pty::ForegroundProbeTiming;
22
23const MAX_EFFICIENCY_SAMPLES: usize = 32;
39
40#[derive(Debug)]
45pub struct ShellEfficiencyFacts {
46 terminal_state_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
47 terminal_state_sample_count: usize,
48 terminal_state_captures: u64,
52 terminal_state_skips: u64,
58 terminal_frame_byte_samples: [u64; MAX_EFFICIENCY_SAMPLES],
59 terminal_frame_byte_sample_count: usize,
60 terminal_frames_published: u64,
61 terminal_frame_bytes_total: u64,
62 foreground_probe_queued_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
69 foreground_probe_queued_sample_count: usize,
70 foreground_probe_lock_wait_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
71 foreground_probe_lock_wait_sample_count: usize,
72 foreground_probe_walk_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
73 foreground_probe_walk_sample_count: usize,
74 foreground_probes: u64,
78}
79
80impl Default for ShellEfficiencyFacts {
81 fn default() -> Self {
82 Self {
83 terminal_state_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
84 terminal_state_sample_count: 0,
85 terminal_state_captures: 0,
86 terminal_state_skips: 0,
87 terminal_frame_byte_samples: [0; MAX_EFFICIENCY_SAMPLES],
88 terminal_frame_byte_sample_count: 0,
89 terminal_frames_published: 0,
90 terminal_frame_bytes_total: 0,
91 foreground_probe_queued_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
92 foreground_probe_queued_sample_count: 0,
93 foreground_probe_lock_wait_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
94 foreground_probe_lock_wait_sample_count: 0,
95 foreground_probe_walk_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
96 foreground_probe_walk_sample_count: 0,
97 foreground_probes: 0,
98 }
99 }
100}
101
102impl ShellEfficiencyFacts {
103 pub fn record_terminal_state_capture(&mut self, elapsed: Duration) {
107 self.terminal_state_captures = self.terminal_state_captures.saturating_add(1);
108 if self.terminal_state_sample_count < MAX_EFFICIENCY_SAMPLES {
109 self.terminal_state_samples[self.terminal_state_sample_count] = elapsed;
110 self.terminal_state_sample_count += 1;
111 }
112 }
113
114 pub fn record_terminal_state_skip(&mut self) {
116 self.terminal_state_skips = self.terminal_state_skips.saturating_add(1);
117 }
118
119 pub fn record_terminal_frame_published(&mut self, bytes: u64) {
121 self.terminal_frames_published = self.terminal_frames_published.saturating_add(1);
122 self.terminal_frame_bytes_total = self.terminal_frame_bytes_total.saturating_add(bytes);
123 if self.terminal_frame_byte_sample_count < MAX_EFFICIENCY_SAMPLES {
124 self.terminal_frame_byte_samples[self.terminal_frame_byte_sample_count] = bytes;
125 self.terminal_frame_byte_sample_count += 1;
126 }
127 }
128
129 pub fn record_foreground_probe(&mut self, timing: ForegroundProbeTiming) {
135 self.foreground_probes = self.foreground_probes.saturating_add(1);
136 if self.foreground_probe_queued_sample_count < MAX_EFFICIENCY_SAMPLES {
137 self.foreground_probe_queued_samples[self.foreground_probe_queued_sample_count] =
138 timing.queued;
139 self.foreground_probe_queued_sample_count += 1;
140 }
141 if self.foreground_probe_lock_wait_sample_count < MAX_EFFICIENCY_SAMPLES {
142 self.foreground_probe_lock_wait_samples[self.foreground_probe_lock_wait_sample_count] =
143 timing.lock_wait;
144 self.foreground_probe_lock_wait_sample_count += 1;
145 }
146 if self.foreground_probe_walk_sample_count < MAX_EFFICIENCY_SAMPLES {
147 self.foreground_probe_walk_samples[self.foreground_probe_walk_sample_count] =
148 timing.walk;
149 self.foreground_probe_walk_sample_count += 1;
150 }
151 }
152
153 pub fn take(&mut self) -> Self {
158 std::mem::take(self)
159 }
160
161 pub fn terminal_state_samples(&self) -> &[Duration] {
162 &self.terminal_state_samples[..self.terminal_state_sample_count]
163 }
164
165 pub fn terminal_state_captures(&self) -> u64 {
166 self.terminal_state_captures
167 }
168
169 pub fn terminal_state_skips(&self) -> u64 {
170 self.terminal_state_skips
171 }
172
173 pub fn terminal_frame_byte_samples(&self) -> &[u64] {
174 &self.terminal_frame_byte_samples[..self.terminal_frame_byte_sample_count]
175 }
176
177 pub fn terminal_frames_published(&self) -> u64 {
178 self.terminal_frames_published
179 }
180
181 pub fn terminal_frame_bytes_total(&self) -> u64 {
182 self.terminal_frame_bytes_total
183 }
184
185 pub fn foreground_probe_queued_samples(&self) -> &[Duration] {
186 &self.foreground_probe_queued_samples[..self.foreground_probe_queued_sample_count]
187 }
188
189 pub fn foreground_probe_lock_wait_samples(&self) -> &[Duration] {
190 &self.foreground_probe_lock_wait_samples[..self.foreground_probe_lock_wait_sample_count]
191 }
192
193 pub fn foreground_probe_walk_samples(&self) -> &[Duration] {
194 &self.foreground_probe_walk_samples[..self.foreground_probe_walk_sample_count]
195 }
196
197 pub fn foreground_probes(&self) -> u64 {
198 self.foreground_probes
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn two_captures_then_a_drain_reports_two_and_a_second_drain_reports_zero() {
208 let mut facts = ShellEfficiencyFacts::default();
209 facts.record_terminal_state_capture(Duration::from_micros(10));
210 facts.record_terminal_state_capture(Duration::from_micros(20));
211
212 let drained = facts.take();
213 assert_eq!(drained.terminal_state_captures(), 2);
214 assert_eq!(drained.terminal_state_samples(), [
215 Duration::from_micros(10),
216 Duration::from_micros(20),
217 ]);
218
219 let drained_again = facts.take();
220 assert_eq!(drained_again.terminal_state_captures(), 0);
221 assert!(drained_again.terminal_state_samples().is_empty());
222 }
223
224 #[test]
225 fn a_session_with_an_unchanged_sequence_increments_skips_and_not_captures() {
226 let unchanged_sequence = 5_u64;
230 let last_captured_sequence = 5_u64;
231 let mut facts = ShellEfficiencyFacts::default();
232 if unchanged_sequence <= last_captured_sequence {
233 facts.record_terminal_state_skip();
234 } else {
235 facts.record_terminal_state_capture(Duration::from_micros(1));
236 }
237
238 assert_eq!(facts.terminal_state_skips(), 1);
239 assert_eq!(facts.terminal_state_captures(), 0);
240 }
241
242 #[test]
243 fn saturating_totals_do_not_wrap_past_u64_max() {
244 let mut facts = ShellEfficiencyFacts::default();
245 facts.terminal_frame_bytes_total = u64::MAX - 1;
246 facts.record_terminal_frame_published(10);
247 assert_eq!(facts.terminal_frame_bytes_total(), u64::MAX);
248
249 facts.terminal_state_captures = u64::MAX;
250 facts.record_terminal_state_capture(Duration::from_micros(1));
251 assert_eq!(facts.terminal_state_captures(), u64::MAX);
252 }
253}