merge: hot.rs
This commit is contained in:
@@ -0,0 +1,123 @@
|
|||||||
|
use crate::manifest::{Manifest, Project};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use chrono::Utc;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub fn target_size_bytes(path: &Path) -> Result<u64> {
|
||||||
|
if !path.exists() { return Ok(0); }
|
||||||
|
let out = std::process::Command::new("du")
|
||||||
|
.args(["-sb", path.to_str().unwrap()])
|
||||||
|
.output()
|
||||||
|
.context("running du")?;
|
||||||
|
let line = String::from_utf8_lossy(&out.stdout);
|
||||||
|
let bytes = line.split_whitespace()
|
||||||
|
.next()
|
||||||
|
.and_then(|s| s.parse::<u64>().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_used_gb(manifest: &Manifest) -> Result<f64> {
|
||||||
|
let mut total = 0u64;
|
||||||
|
for p in &manifest.projects {
|
||||||
|
total += target_size_bytes(&p.hot_target_path)?;
|
||||||
|
}
|
||||||
|
Ok(total as f64 / 1_073_741_824.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gc_stale_targets(manifest: &Manifest, stale_hours: u64) -> Result<Vec<String>> {
|
||||||
|
let now = Utc::now();
|
||||||
|
let threshold = chrono::Duration::hours(stale_hours as i64);
|
||||||
|
let mut evicted = Vec::new();
|
||||||
|
for p in &manifest.projects {
|
||||||
|
let is_stale = match p.last_active {
|
||||||
|
None => true,
|
||||||
|
Some(t) => (now - t) > threshold,
|
||||||
|
};
|
||||||
|
if is_stale && p.hot_target_path.exists() {
|
||||||
|
std::fs::remove_dir_all(&p.hot_target_path)
|
||||||
|
.with_context(|| format!("removing hot target for {}", p.name))?;
|
||||||
|
evicted.push(p.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(evicted)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gc_by_space(manifest: &mut Manifest, max_gb: f64) -> Result<Vec<String>> {
|
||||||
|
let mut evicted = Vec::new();
|
||||||
|
loop {
|
||||||
|
let used = total_used_gb(manifest)?;
|
||||||
|
if used <= max_gb { break; }
|
||||||
|
let lru_name = manifest.projects.iter()
|
||||||
|
.filter(|p| p.hot_target_path.exists())
|
||||||
|
.min_by_key(|p| p.last_active)
|
||||||
|
.map(|p| p.name.clone());
|
||||||
|
match lru_name {
|
||||||
|
None => break,
|
||||||
|
Some(name) => {
|
||||||
|
if let Some(p) = manifest.projects.iter().find(|p| p.name == name) {
|
||||||
|
if p.hot_target_path.exists() {
|
||||||
|
std::fs::remove_dir_all(&p.hot_target_path)?;
|
||||||
|
evicted.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(evicted)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::manifest::{Manifest, Project};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn make_project(dir: &TempDir, name: &str) -> Project {
|
||||||
|
let path = dir.path().join(name);
|
||||||
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
|
std::fs::write(path.join("dummy"), vec![0u8; 1024 * 1024]).unwrap();
|
||||||
|
Project {
|
||||||
|
name: name.into(),
|
||||||
|
warm_path: format!("/slab/projects/{}", name).into(),
|
||||||
|
hot_target_path: path.clone(),
|
||||||
|
last_build: None,
|
||||||
|
last_active: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_target_size_bytes() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let p = make_project(&dir, "test-proj");
|
||||||
|
let size = target_size_bytes(&p.hot_target_path).unwrap();
|
||||||
|
assert!(size >= 1024 * 1024, "expected at least 1MB, got {}", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gc_evicts_stale_project() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let mut manifest = Manifest::default();
|
||||||
|
manifest.projects.push(make_project(&dir, "stale-proj"));
|
||||||
|
manifest.projects[0].last_active =
|
||||||
|
Some(chrono::Utc::now() - chrono::Duration::hours(100));
|
||||||
|
|
||||||
|
let evicted = gc_stale_targets(&manifest, 48).unwrap();
|
||||||
|
assert_eq!(evicted.len(), 1);
|
||||||
|
assert_eq!(evicted[0], "stale-proj");
|
||||||
|
assert!(!dir.path().join("stale-proj").exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gc_skips_active_project() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let mut manifest = Manifest::default();
|
||||||
|
manifest.projects.push(make_project(&dir, "active-proj"));
|
||||||
|
manifest.projects[0].last_active =
|
||||||
|
Some(chrono::Utc::now() - chrono::Duration::hours(2));
|
||||||
|
|
||||||
|
let evicted = gc_stale_targets(&manifest, 48).unwrap();
|
||||||
|
assert!(evicted.is_empty());
|
||||||
|
assert!(dir.path().join("active-proj").exists());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user