gate4agent_runtime_native/
shell_efficiency.rs1use crate::tick_profile::{duration_micros, Distribution, RingStats, SAMPLE_WINDOW};
24use gate4agent_shell_native::ShellEfficiencyFacts;
25
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
30pub struct ShellEfficiencyProfileSnapshot {
31 pub terminal_state_us: Distribution,
34 pub terminal_state_captures_total: u64,
36 pub terminal_state_skips_total: u64,
39 pub terminal_frame_bytes: Distribution,
42 pub terminal_frames_published_total: u64,
44 pub terminal_frame_bytes_total: u64,
46 pub foreground_probe_queued_us: Distribution,
55 pub foreground_probe_lock_wait_us: Distribution,
56 pub foreground_probe_walk_us: Distribution,
57 pub foreground_probes_total: u64,
61}
62
63#[derive(Clone, Debug, Default)]
70pub struct ShellEfficiencyProfile {
71 terminal_state_us: RingStats<SAMPLE_WINDOW>,
72 terminal_state_captures_total: u64,
73 terminal_state_skips_total: u64,
74 terminal_frame_bytes: RingStats<SAMPLE_WINDOW>,
75 terminal_frames_published_total: u64,
76 terminal_frame_bytes_total: u64,
77 foreground_probe_queued_us: RingStats<SAMPLE_WINDOW>,
78 foreground_probe_lock_wait_us: RingStats<SAMPLE_WINDOW>,
79 foreground_probe_walk_us: RingStats<SAMPLE_WINDOW>,
80 foreground_probes_total: u64,
81}
82
83impl ShellEfficiencyProfile {
84 pub fn fold(&mut self, facts: &ShellEfficiencyFacts) {
88 for &sample in facts.terminal_state_samples() {
89 self.terminal_state_us.push(duration_micros(sample));
90 }
91 self.terminal_state_captures_total = self
92 .terminal_state_captures_total
93 .saturating_add(facts.terminal_state_captures());
94 self.terminal_state_skips_total = self
95 .terminal_state_skips_total
96 .saturating_add(facts.terminal_state_skips());
97
98 for &bytes in facts.terminal_frame_byte_samples() {
99 self.terminal_frame_bytes.push(bytes.min(u64::from(u32::MAX)) as u32);
100 }
101 self.terminal_frames_published_total = self
102 .terminal_frames_published_total
103 .saturating_add(facts.terminal_frames_published());
104 self.terminal_frame_bytes_total = self
105 .terminal_frame_bytes_total
106 .saturating_add(facts.terminal_frame_bytes_total());
107
108 for &sample in facts.foreground_probe_queued_samples() {
109 self.foreground_probe_queued_us.push(duration_micros(sample));
110 }
111 for &sample in facts.foreground_probe_lock_wait_samples() {
112 self.foreground_probe_lock_wait_us.push(duration_micros(sample));
113 }
114 for &sample in facts.foreground_probe_walk_samples() {
115 self.foreground_probe_walk_us.push(duration_micros(sample));
116 }
117 self.foreground_probes_total = self
118 .foreground_probes_total
119 .saturating_add(facts.foreground_probes());
120 }
121
122 pub fn snapshot(&self) -> ShellEfficiencyProfileSnapshot {
123 ShellEfficiencyProfileSnapshot {
124 terminal_state_us: self.terminal_state_us.stats(),
125 terminal_state_captures_total: self.terminal_state_captures_total,
126 terminal_state_skips_total: self.terminal_state_skips_total,
127 terminal_frame_bytes: self.terminal_frame_bytes.stats(),
128 terminal_frames_published_total: self.terminal_frames_published_total,
129 terminal_frame_bytes_total: self.terminal_frame_bytes_total,
130 foreground_probe_queued_us: self.foreground_probe_queued_us.stats(),
131 foreground_probe_lock_wait_us: self.foreground_probe_lock_wait_us.stats(),
132 foreground_probe_walk_us: self.foreground_probe_walk_us.stats(),
133 foreground_probes_total: self.foreground_probes_total,
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use gate4agent_shell_native::ForegroundProbeTiming;
142 use std::time::Duration;
143
144 #[test]
145 fn folding_a_drained_fact_set_updates_distributions_and_lifetime_counters() {
146 let mut facts = ShellEfficiencyFacts::default();
147 facts.record_terminal_state_capture(Duration::from_micros(1_500));
148 facts.record_terminal_state_skip();
149 facts.record_terminal_frame_published(4_096);
150 facts.record_foreground_probe(ForegroundProbeTiming {
151 queued: Duration::from_micros(2_500),
152 lock_wait: Duration::from_micros(700),
153 walk: Duration::from_micros(300),
154 });
155 let drained = facts.take();
156
157 let mut profile = ShellEfficiencyProfile::default();
158 profile.fold(&drained);
159 let snapshot = profile.snapshot();
160
161 assert_eq!(snapshot.terminal_state_us.max, 1_500);
162 assert_eq!(snapshot.terminal_state_captures_total, 1);
163 assert_eq!(snapshot.terminal_state_skips_total, 1);
164 assert_eq!(snapshot.terminal_frame_bytes.max, 4_096);
165 assert_eq!(snapshot.terminal_frames_published_total, 1);
166 assert_eq!(snapshot.terminal_frame_bytes_total, 4_096);
167 assert_eq!(snapshot.foreground_probe_queued_us.max, 2_500);
171 assert_eq!(snapshot.foreground_probe_lock_wait_us.max, 700);
172 assert_eq!(snapshot.foreground_probe_walk_us.max, 300);
173 assert_eq!(snapshot.foreground_probes_total, 1);
174 }
175
176 #[test]
177 fn one_probe_increments_the_lifetime_count_by_one_not_by_the_component_count() {
178 let mut facts = ShellEfficiencyFacts::default();
179 facts.record_foreground_probe(ForegroundProbeTiming {
180 queued: Duration::from_micros(10),
181 lock_wait: Duration::from_micros(20),
182 walk: Duration::from_micros(30),
183 });
184 let drained = facts.take();
185
186 let mut profile = ShellEfficiencyProfile::default();
187 profile.fold(&drained);
188 let snapshot = profile.snapshot();
189
190 assert_eq!(snapshot.foreground_probes_total, 1);
191 assert_eq!(snapshot.foreground_probe_queued_us.count, 1);
192 assert_eq!(snapshot.foreground_probe_lock_wait_us.count, 1);
193 assert_eq!(snapshot.foreground_probe_walk_us.count, 1);
194 }
195
196 #[test]
197 fn folding_an_empty_fact_set_changes_nothing() {
198 let mut profile = ShellEfficiencyProfile::default();
199 profile.fold(&ShellEfficiencyFacts::default());
200 assert_eq!(profile.snapshot(), ShellEfficiencyProfileSnapshot::default());
201 }
202}