use std::time::Duration;
use gate4agent::pty::ForegroundProbeTiming;
const MAX_EFFICIENCY_SAMPLES: usize = 32;
#[derive(Debug)]
pub struct ShellEfficiencyFacts {
terminal_state_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
terminal_state_sample_count: usize,
terminal_state_captures: u64,
terminal_state_skips: u64,
terminal_frame_byte_samples: [u64; MAX_EFFICIENCY_SAMPLES],
terminal_frame_byte_sample_count: usize,
terminal_frames_published: u64,
terminal_frame_bytes_total: u64,
foreground_probe_queued_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
foreground_probe_queued_sample_count: usize,
foreground_probe_lock_wait_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
foreground_probe_lock_wait_sample_count: usize,
foreground_probe_walk_samples: [Duration; MAX_EFFICIENCY_SAMPLES],
foreground_probe_walk_sample_count: usize,
foreground_probes: u64,
}
impl Default for ShellEfficiencyFacts {
fn default() -> Self {
Self {
terminal_state_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
terminal_state_sample_count: 0,
terminal_state_captures: 0,
terminal_state_skips: 0,
terminal_frame_byte_samples: [0; MAX_EFFICIENCY_SAMPLES],
terminal_frame_byte_sample_count: 0,
terminal_frames_published: 0,
terminal_frame_bytes_total: 0,
foreground_probe_queued_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
foreground_probe_queued_sample_count: 0,
foreground_probe_lock_wait_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
foreground_probe_lock_wait_sample_count: 0,
foreground_probe_walk_samples: [Duration::ZERO; MAX_EFFICIENCY_SAMPLES],
foreground_probe_walk_sample_count: 0,
foreground_probes: 0,
}
}
}
impl ShellEfficiencyFacts {
pub fn record_terminal_state_capture(&mut self, elapsed: Duration) {
self.terminal_state_captures = self.terminal_state_captures.saturating_add(1);
if self.terminal_state_sample_count < MAX_EFFICIENCY_SAMPLES {
self.terminal_state_samples[self.terminal_state_sample_count] = elapsed;
self.terminal_state_sample_count += 1;
}
}
pub fn record_terminal_state_skip(&mut self) {
self.terminal_state_skips = self.terminal_state_skips.saturating_add(1);
}
pub fn record_terminal_frame_published(&mut self, bytes: u64) {
self.terminal_frames_published = self.terminal_frames_published.saturating_add(1);
self.terminal_frame_bytes_total = self.terminal_frame_bytes_total.saturating_add(bytes);
if self.terminal_frame_byte_sample_count < MAX_EFFICIENCY_SAMPLES {
self.terminal_frame_byte_samples[self.terminal_frame_byte_sample_count] = bytes;
self.terminal_frame_byte_sample_count += 1;
}
}
pub fn record_foreground_probe(&mut self, timing: ForegroundProbeTiming) {
self.foreground_probes = self.foreground_probes.saturating_add(1);
if self.foreground_probe_queued_sample_count < MAX_EFFICIENCY_SAMPLES {
self.foreground_probe_queued_samples[self.foreground_probe_queued_sample_count] =
timing.queued;
self.foreground_probe_queued_sample_count += 1;
}
if self.foreground_probe_lock_wait_sample_count < MAX_EFFICIENCY_SAMPLES {
self.foreground_probe_lock_wait_samples[self.foreground_probe_lock_wait_sample_count] =
timing.lock_wait;
self.foreground_probe_lock_wait_sample_count += 1;
}
if self.foreground_probe_walk_sample_count < MAX_EFFICIENCY_SAMPLES {
self.foreground_probe_walk_samples[self.foreground_probe_walk_sample_count] =
timing.walk;
self.foreground_probe_walk_sample_count += 1;
}
}
pub fn take(&mut self) -> Self {
std::mem::take(self)
}
pub fn terminal_state_samples(&self) -> &[Duration] {
&self.terminal_state_samples[..self.terminal_state_sample_count]
}
pub fn terminal_state_captures(&self) -> u64 {
self.terminal_state_captures
}
pub fn terminal_state_skips(&self) -> u64 {
self.terminal_state_skips
}
pub fn terminal_frame_byte_samples(&self) -> &[u64] {
&self.terminal_frame_byte_samples[..self.terminal_frame_byte_sample_count]
}
pub fn terminal_frames_published(&self) -> u64 {
self.terminal_frames_published
}
pub fn terminal_frame_bytes_total(&self) -> u64 {
self.terminal_frame_bytes_total
}
pub fn foreground_probe_queued_samples(&self) -> &[Duration] {
&self.foreground_probe_queued_samples[..self.foreground_probe_queued_sample_count]
}
pub fn foreground_probe_lock_wait_samples(&self) -> &[Duration] {
&self.foreground_probe_lock_wait_samples[..self.foreground_probe_lock_wait_sample_count]
}
pub fn foreground_probe_walk_samples(&self) -> &[Duration] {
&self.foreground_probe_walk_samples[..self.foreground_probe_walk_sample_count]
}
pub fn foreground_probes(&self) -> u64 {
self.foreground_probes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_captures_then_a_drain_reports_two_and_a_second_drain_reports_zero() {
let mut facts = ShellEfficiencyFacts::default();
facts.record_terminal_state_capture(Duration::from_micros(10));
facts.record_terminal_state_capture(Duration::from_micros(20));
let drained = facts.take();
assert_eq!(drained.terminal_state_captures(), 2);
assert_eq!(drained.terminal_state_samples(), [
Duration::from_micros(10),
Duration::from_micros(20),
]);
let drained_again = facts.take();
assert_eq!(drained_again.terminal_state_captures(), 0);
assert!(drained_again.terminal_state_samples().is_empty());
}
#[test]
fn a_session_with_an_unchanged_sequence_increments_skips_and_not_captures() {
let unchanged_sequence = 5_u64;
let last_captured_sequence = 5_u64;
let mut facts = ShellEfficiencyFacts::default();
if unchanged_sequence <= last_captured_sequence {
facts.record_terminal_state_skip();
} else {
facts.record_terminal_state_capture(Duration::from_micros(1));
}
assert_eq!(facts.terminal_state_skips(), 1);
assert_eq!(facts.terminal_state_captures(), 0);
}
#[test]
fn saturating_totals_do_not_wrap_past_u64_max() {
let mut facts = ShellEfficiencyFacts::default();
facts.terminal_frame_bytes_total = u64::MAX - 1;
facts.record_terminal_frame_published(10);
assert_eq!(facts.terminal_frame_bytes_total(), u64::MAX);
facts.terminal_state_captures = u64::MAX;
facts.record_terminal_state_capture(Duration::from_micros(1));
assert_eq!(facts.terminal_state_captures(), u64::MAX);
}
}