use crate::tick_profile::{duration_micros, Distribution, RingStats, SAMPLE_WINDOW};
use gate4agent_shell_native::ShellEfficiencyFacts;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ShellEfficiencyProfileSnapshot {
pub terminal_state_us: Distribution,
pub terminal_state_captures_total: u64,
pub terminal_state_skips_total: u64,
pub terminal_frame_bytes: Distribution,
pub terminal_frames_published_total: u64,
pub terminal_frame_bytes_total: u64,
pub foreground_probe_queued_us: Distribution,
pub foreground_probe_lock_wait_us: Distribution,
pub foreground_probe_walk_us: Distribution,
pub foreground_probes_total: u64,
}
#[derive(Clone, Debug, Default)]
pub struct ShellEfficiencyProfile {
terminal_state_us: RingStats<SAMPLE_WINDOW>,
terminal_state_captures_total: u64,
terminal_state_skips_total: u64,
terminal_frame_bytes: RingStats<SAMPLE_WINDOW>,
terminal_frames_published_total: u64,
terminal_frame_bytes_total: u64,
foreground_probe_queued_us: RingStats<SAMPLE_WINDOW>,
foreground_probe_lock_wait_us: RingStats<SAMPLE_WINDOW>,
foreground_probe_walk_us: RingStats<SAMPLE_WINDOW>,
foreground_probes_total: u64,
}
impl ShellEfficiencyProfile {
pub fn fold(&mut self, facts: &ShellEfficiencyFacts) {
for &sample in facts.terminal_state_samples() {
self.terminal_state_us.push(duration_micros(sample));
}
self.terminal_state_captures_total = self
.terminal_state_captures_total
.saturating_add(facts.terminal_state_captures());
self.terminal_state_skips_total = self
.terminal_state_skips_total
.saturating_add(facts.terminal_state_skips());
for &bytes in facts.terminal_frame_byte_samples() {
self.terminal_frame_bytes.push(bytes.min(u64::from(u32::MAX)) as u32);
}
self.terminal_frames_published_total = self
.terminal_frames_published_total
.saturating_add(facts.terminal_frames_published());
self.terminal_frame_bytes_total = self
.terminal_frame_bytes_total
.saturating_add(facts.terminal_frame_bytes_total());
for &sample in facts.foreground_probe_queued_samples() {
self.foreground_probe_queued_us.push(duration_micros(sample));
}
for &sample in facts.foreground_probe_lock_wait_samples() {
self.foreground_probe_lock_wait_us.push(duration_micros(sample));
}
for &sample in facts.foreground_probe_walk_samples() {
self.foreground_probe_walk_us.push(duration_micros(sample));
}
self.foreground_probes_total = self
.foreground_probes_total
.saturating_add(facts.foreground_probes());
}
pub fn snapshot(&self) -> ShellEfficiencyProfileSnapshot {
ShellEfficiencyProfileSnapshot {
terminal_state_us: self.terminal_state_us.stats(),
terminal_state_captures_total: self.terminal_state_captures_total,
terminal_state_skips_total: self.terminal_state_skips_total,
terminal_frame_bytes: self.terminal_frame_bytes.stats(),
terminal_frames_published_total: self.terminal_frames_published_total,
terminal_frame_bytes_total: self.terminal_frame_bytes_total,
foreground_probe_queued_us: self.foreground_probe_queued_us.stats(),
foreground_probe_lock_wait_us: self.foreground_probe_lock_wait_us.stats(),
foreground_probe_walk_us: self.foreground_probe_walk_us.stats(),
foreground_probes_total: self.foreground_probes_total,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use gate4agent_shell_native::ForegroundProbeTiming;
use std::time::Duration;
#[test]
fn folding_a_drained_fact_set_updates_distributions_and_lifetime_counters() {
let mut facts = ShellEfficiencyFacts::default();
facts.record_terminal_state_capture(Duration::from_micros(1_500));
facts.record_terminal_state_skip();
facts.record_terminal_frame_published(4_096);
facts.record_foreground_probe(ForegroundProbeTiming {
queued: Duration::from_micros(2_500),
lock_wait: Duration::from_micros(700),
walk: Duration::from_micros(300),
});
let drained = facts.take();
let mut profile = ShellEfficiencyProfile::default();
profile.fold(&drained);
let snapshot = profile.snapshot();
assert_eq!(snapshot.terminal_state_us.max, 1_500);
assert_eq!(snapshot.terminal_state_captures_total, 1);
assert_eq!(snapshot.terminal_state_skips_total, 1);
assert_eq!(snapshot.terminal_frame_bytes.max, 4_096);
assert_eq!(snapshot.terminal_frames_published_total, 1);
assert_eq!(snapshot.terminal_frame_bytes_total, 4_096);
assert_eq!(snapshot.foreground_probe_queued_us.max, 2_500);
assert_eq!(snapshot.foreground_probe_lock_wait_us.max, 700);
assert_eq!(snapshot.foreground_probe_walk_us.max, 300);
assert_eq!(snapshot.foreground_probes_total, 1);
}
#[test]
fn one_probe_increments_the_lifetime_count_by_one_not_by_the_component_count() {
let mut facts = ShellEfficiencyFacts::default();
facts.record_foreground_probe(ForegroundProbeTiming {
queued: Duration::from_micros(10),
lock_wait: Duration::from_micros(20),
walk: Duration::from_micros(30),
});
let drained = facts.take();
let mut profile = ShellEfficiencyProfile::default();
profile.fold(&drained);
let snapshot = profile.snapshot();
assert_eq!(snapshot.foreground_probes_total, 1);
assert_eq!(snapshot.foreground_probe_queued_us.count, 1);
assert_eq!(snapshot.foreground_probe_lock_wait_us.count, 1);
assert_eq!(snapshot.foreground_probe_walk_us.count, 1);
}
#[test]
fn folding_an_empty_fact_set_changes_nothing() {
let mut profile = ShellEfficiencyProfile::default();
profile.fold(&ShellEfficiencyFacts::default());
assert_eq!(profile.snapshot(), ShellEfficiencyProfileSnapshot::default());
}
}