use crate::backlog::{Backlog, BacklogDelta};
use crate::pressure::ContextTracker;
use chrono::Utc;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Homeostat {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub load_avg_1m: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mem_available_kb: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backlog: Option<Backlog>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backlog_delta: Option<BacklogDelta>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peak_prompt_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub peak_context_pressure: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub anticipated_guilt: Option<f32>,
}
impl Homeostat {
pub fn at_start() -> Homeostat {
Homeostat {
load_avg_1m: load_avg_1m(),
mem_available_kb: mem_available_kb(),
backlog: Some(Backlog::read()),
..Homeostat::default()
}
}
pub fn finish(mut self, pressure: &ContextTracker, window: Option<u64>) -> Homeostat {
self.peak_prompt_tokens = (pressure.peak_tokens() > 0).then(|| pressure.peak_tokens());
self.peak_context_pressure = pressure.peak_pressure(window);
if let Some(before) = &self.backlog {
self.anticipated_guilt =
crate::guilt::anticipated_guilt(before, self.peak_context_pressure, Utc::now());
self.backlog_delta = Some(Backlog::delta(before, &Backlog::read()));
}
self
}
}
fn load_avg_1m() -> Option<f32> {
std::fs::read_to_string("/proc/loadavg")
.ok()?
.split_whitespace()
.next()?
.parse()
.ok()
}
fn mem_available_kb() -> Option<u64> {
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
text.lines()
.find_map(|l| l.strip_prefix("MemAvailable:"))?
.split_whitespace()
.next()?
.parse()
.ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_snapshot_round_trips_and_an_older_record_without_one_still_loads() {
let h = Homeostat {
load_avg_1m: Some(0.56),
mem_available_kb: Some(21_000_000),
backlog: Some(Backlog::default()),
backlog_delta: Some(BacklogDelta::default()),
peak_prompt_tokens: Some(18_008),
peak_context_pressure: Some(0.0687),
anticipated_guilt: Some(0.0),
};
let json = serde_json::to_string(&h).unwrap();
assert_eq!(serde_json::from_str::<Homeostat>(&json).unwrap(), h);
let empty: Homeostat = serde_json::from_str("{}").unwrap();
assert_eq!(empty, Homeostat::default());
}
#[test]
fn the_recorded_pressure_is_the_runs_high_water_mark() {
let mut pressure = ContextTracker::new();
pressure.observe(4_000, 4_000);
pressure.observe(30_000, 30_000);
pressure.observe(6_000, 6_000);
let h = Homeostat::default().finish(&pressure, Some(60_000));
assert_eq!(h.peak_prompt_tokens, Some(30_000));
assert_eq!(h.peak_context_pressure, Some(0.5));
let no_window = Homeostat::default().finish(&pressure, None);
assert_eq!(no_window.peak_prompt_tokens, Some(30_000));
assert_eq!(no_window.peak_context_pressure, None);
let never = Homeostat::default().finish(&ContextTracker::new(), Some(60_000));
assert_eq!(never.peak_prompt_tokens, None, "absent, not zero");
assert_eq!(never.peak_context_pressure, None);
}
#[test]
fn a_sensor_that_cannot_be_read_is_absent_rather_than_zero() {
if let Some(load) = load_avg_1m() {
assert!(load >= 0.0);
}
if let Some(kb) = mem_available_kb() {
assert!(kb > 0, "MemAvailable parsed as zero would be a parse bug");
}
}
}