hyperhive/hive-c0re/src/stats_vacuum.rs

66 lines
2.3 KiB
Rust

//! Host-side vacuum of every per-agent turn-stats.sqlite. The harness
//! writes to `/state/hyperhive-turn-stats.sqlite` (bind-mounted from
//! `/var/lib/hyperhive/agents/<name>/state/`); we open the same file
//! from the host every hour and delete rows older than `KEEP_SECS`.
//! Mirrors `events_vacuum` in structure — host-side so the harness
//! can't disable it, age-only so a chatty burst doesn't evict old
//! rows sooner than expected. 90-day retention keeps enough history
//! for trend analysis without unbounded growth.
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use rusqlite::{Connection, Result, params};
use crate::coordinator::Coordinator;
const VACUUM_INTERVAL: Duration = Duration::from_secs(3600);
const KEEP_SECS: i64 = 90 * 24 * 3600;
/// Background loop: sweep every existing agent state dir hourly, run
/// the vacuum SQL against its turn-stats.sqlite if present. Errors
/// are logged but don't tear the loop down.
pub fn spawn(coord: Arc<Coordinator>) {
let mut shutdown = coord.shutdown_rx();
tokio::spawn(async move {
loop {
sweep_once();
tokio::select! {
_ = tokio::time::sleep(VACUUM_INTERVAL) => {}
_ = shutdown.changed() => {
tracing::info!("stats vacuum: shutdown signal received");
break;
}
}
}
});
}
fn sweep_once() {
for name in Coordinator::kept_state_names() {
let path =
Coordinator::agent_notes_dir(&name).join("hyperhive-turn-stats.sqlite");
if !path.exists() {
continue;
}
match vacuum_file(&path) {
Ok(0) => {}
Ok(n) => tracing::info!(agent = %name, removed = n, "turn-stats vacuum"),
Err(e) => tracing::warn!(agent = %name, error = ?e, "turn-stats vacuum failed"),
}
}
}
fn vacuum_file(path: &Path) -> Result<u64> {
let conn = Connection::open(path)?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.and_then(|d| i64::try_from(d.as_secs()).ok())
.unwrap_or(0);
let cutoff = now - KEEP_SECS;
let removed =
conn.execute("DELETE FROM turn_stats WHERE started_at < ?1", params![cutoff])?;
Ok(u64::try_from(removed).unwrap_or(0))
}