use std::collections::HashMap;
use std::path::{Path, PathBuf};
use rkyv::{Archive, Deserialize, Serialize};
use crate::error::{HoldError, Result};
#[cfg(test)]
mod tests;
pub const METADATA_VERSION: u32 = 6;
pub(crate) const CAP_TRACE_SAMPLE_SOURCE_HEALTHY: &str = "healthy";
pub(crate) const CAP_TRACE_SAMPLE_SOURCE_POLICY_FLOOR: &str = "policy-floor";
pub(crate) const CAP_TRACE_SAMPLE_SOURCE_HELD: &str = "held";
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct FileState {
#[rkyv(with = rkyv::with::AsString)]
pub path: PathBuf,
pub size: u64,
pub hash: String,
pub mtime_nanos: u128,
}
#[derive(Archive, Deserialize, Serialize, Debug, Clone)]
pub struct StateMetadata {
pub version: u32,
pub files: HashMap<String, FileState>,
pub last_gc_mtime_nanos: Option<u128>,
pub gc_metrics: GcMetrics,
}
impl StateMetadata {
pub fn new() -> Self {
Self {
version: METADATA_VERSION,
files: HashMap::new(),
last_gc_mtime_nanos: None,
gc_metrics: GcMetrics::default(),
}
}
pub fn max_mtime_nanos(&self) -> Option<u128> {
self.files.values().map(|state| state.mtime_nanos).max()
}
pub fn upsert(&mut self, state: FileState) -> Result<()> {
let key = state
.path
.to_str()
.ok_or_else(|| HoldError::InvalidUtf8Path(state.path.clone()))?
.to_string();
self.files.insert(key, state);
Ok(())
}
pub fn remove(&mut self, path: &Path) -> Result<Option<FileState>> {
let key = path
.to_str()
.ok_or_else(|| HoldError::InvalidUtf8Path(path.to_path_buf()))?;
Ok(self.files.remove(key))
}
pub fn get(&self, path: &Path) -> Result<Option<&FileState>> {
let key = path
.to_str()
.ok_or_else(|| HoldError::InvalidUtf8Path(path.to_path_buf()))?;
Ok(self.files.get(key))
}
pub fn contains(&self, path: &Path) -> Result<bool> {
let key = path
.to_str()
.ok_or_else(|| HoldError::InvalidUtf8Path(path.to_path_buf()))?;
Ok(self.files.contains_key(key))
}
pub fn len(&self) -> usize {
self.files.len()
}
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
}
impl Default for StateMetadata {
fn default() -> Self {
Self::new()
}
}
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq, Default)]
pub struct GcMetrics {
pub runs: u32,
pub recent_auto_cap_runs: Vec<AutoCapRun>,
pub last_suggested_cap: Option<u64>,
pub last_cap_trace: Option<CapTrace>,
}
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq, Default)]
pub struct AutoCapRun {
pub cap: u64,
pub initial_size: u64,
pub final_size: u64,
pub bytes_freed: u64,
pub protected_artifact_bytes: u64,
pub eligible_artifact_bytes: u64,
pub retained_artifact_bytes: u64,
pub preserved_binary_bytes: u64,
pub unrecognized_bytes: u64,
}
impl AutoCapRun {
pub(crate) fn policy_floor(&self) -> u64 {
self.protected_artifact_bytes
.saturating_add(self.preserved_binary_bytes)
}
pub(crate) fn is_healthy(&self) -> bool {
self.final_size <= self.cap
}
pub(crate) fn proves_cap_unattainable(&self) -> bool {
!self.is_healthy() && self.policy_floor() > self.cap
}
}
#[derive(Archive, Deserialize, Serialize, Debug, Clone, PartialEq, Default)]
pub struct CapTrace {
pub baseline: u64,
pub growth_budget: u64,
pub observed_growth_pct: u64,
pub clamp_reason: String,
pub sample_source: String,
pub sample_count: u32,
pub ignored_over_cap_sample_count: u32,
pub policy_floor: u64,
pub policy_floor_sample_count: u32,
}