use std::sync::{Arc, OnceLock};
use metrale_bench::benchmarks::default_tier_boot::MemoryReport;
pub type LedgerReader = Arc<dyn Fn() -> usize + Send + Sync>;
#[derive(Clone)]
pub struct DeviceBudget {
pub budget_bytes: u64,
pub ledger: Option<LedgerReader>,
}
#[derive(Clone)]
pub struct MemoryFacts {
pub kv_blocks: usize,
pub kv_block_bytes: u64,
pub max_batch_size: usize,
pub device: DeviceBudget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Readings {
pub ledger_live_bytes: Option<u64>,
pub mem_available_at_start_bytes: Option<u64>,
pub mem_available_now_bytes: Option<u64>,
pub rss_anon_bytes: Option<u64>,
}
const MEMINFO: &str = "/proc/meminfo";
const SELF_STATUS: &str = "/proc/self/status";
static MEM_AVAILABLE_AT_START: OnceLock<Option<u64>> = OnceLock::new();
pub(crate) fn record_mem_available_at_start() {
MEM_AVAILABLE_AT_START
.get_or_init(|| read_proc(MEMINFO).as_deref().and_then(mem_available_bytes));
}
pub(crate) fn budget_bytes(total_bytes: usize, utilization: f64) -> u64 {
(total_bytes as f64 * utilization) as u64
}
impl MemoryFacts {
pub(crate) fn readings(&self) -> Readings {
Readings {
ledger_live_bytes: self.device.ledger.as_ref().map(|read| read() as u64),
mem_available_at_start_bytes: MEM_AVAILABLE_AT_START.get().copied().flatten(),
mem_available_now_bytes: read_proc(MEMINFO).as_deref().and_then(mem_available_bytes),
rss_anon_bytes: read_proc(SELF_STATUS).as_deref().and_then(rss_anon_bytes),
}
}
pub(crate) fn report(&self, readings: Readings) -> MemoryReport {
MemoryReport {
kv_blocks: self.kv_blocks,
kv_block_bytes: self.kv_block_bytes,
max_batch_size: self.max_batch_size,
budget_bytes: self.device.budget_bytes,
ledger_live_bytes: readings.ledger_live_bytes,
mem_available_at_start_bytes: readings.mem_available_at_start_bytes,
mem_available_now_bytes: readings.mem_available_now_bytes,
rss_anon_bytes: readings.rss_anon_bytes,
}
}
}
pub(crate) fn mem_available_bytes(meminfo: &str) -> Option<u64> {
kib_field(meminfo, "MemAvailable")
}
pub(crate) fn rss_anon_bytes(status: &str) -> Option<u64> {
kib_field(status, "RssAnon")
}
fn kib_field(text: &str, key: &str) -> Option<u64> {
text.lines().find_map(|line| {
let rest = line.strip_prefix(key)?.strip_prefix(':')?;
let mut parts = rest.split_whitespace();
let kib: u64 = parts.next()?.parse().ok()?;
(parts.next()? == "kB" && parts.next().is_none())
.then(|| kib.checked_mul(1024))
.flatten()
})
}
#[cfg(target_os = "linux")]
pub(crate) fn read_proc(path: &str) -> Option<String> {
match std::fs::read_to_string(path) {
Ok(text) => Some(text),
Err(e) => {
tracing::warn!("GET /memory: reading {path} failed: {e}; the field reports null");
None
}
}
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn read_proc(_path: &str) -> Option<String> {
None
}
#[cfg(test)]
#[path = "memory_probe_tests.rs"]
mod tests;