#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SystemSpecs {
pub cpus: usize,
pub total_ram_bytes: u64,
}
impl SystemSpecs {
pub fn detect() -> Self {
Self {
cpus: cpu_count(),
total_ram_bytes: total_ram_bytes(),
}
}
}
pub fn cpu_count() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.max(1)
}
pub fn total_ram_bytes() -> u64 {
detect_ram_bytes().unwrap_or(4 * 1024 * 1024 * 1024)
}
#[cfg(target_os = "linux")]
fn detect_ram_bytes() -> Option<u64> {
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
parse_meminfo_kb(&text).map(|kb| kb.saturating_mul(1024))
}
#[cfg(target_os = "linux")]
fn parse_meminfo_kb(text: &str) -> Option<u64> {
for line in text.lines() {
let Some(rest) = line.strip_prefix("MemTotal:") else {
continue;
};
let rest = rest.trim();
let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
if digits.is_empty() {
return None;
}
return digits.parse::<u64>().ok();
}
None
}
#[cfg(target_os = "macos")]
fn detect_ram_bytes() -> Option<u64> {
let out = std::process::Command::new("sysctl")
.arg("-n")
.arg("hw.memsize")
.output()
.ok()?;
if !out.status.success() {
return None;
}
String::from_utf8_lossy(&out.stdout)
.trim()
.parse::<u64>()
.ok()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn detect_ram_bytes() -> Option<u64> {
None
}
pub const MIN_DECODE_CHUNK_BYTES: usize = 32 * 1024 * 1024;
pub const MAX_DECODE_CHUNK_BYTES: usize = 512 * 1024 * 1024;
pub fn default_decode_chunk_bytes(specs: SystemSpecs) -> usize {
let want = specs.total_ram_bytes / 32;
want.clamp(MIN_DECODE_CHUNK_BYTES as u64, MAX_DECODE_CHUNK_BYTES as u64) as usize
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_never_returns_zeros() {
let specs = SystemSpecs::detect();
assert!(specs.cpus >= 1);
assert!(specs.total_ram_bytes >= 1024 * 1024 * 1024);
}
#[test]
fn chunk_cap_scales_with_ram_and_stays_bounded() {
let gb = |n: u64| SystemSpecs {
cpus: 8,
total_ram_bytes: n * 1024 * 1024 * 1024,
};
assert_eq!(default_decode_chunk_bytes(gb(8)), 256 * 1024 * 1024);
assert_eq!(default_decode_chunk_bytes(gb(4)), 128 * 1024 * 1024);
assert_eq!(default_decode_chunk_bytes(gb(128)), MAX_DECODE_CHUNK_BYTES);
assert_eq!(
default_decode_chunk_bytes(SystemSpecs {
cpus: 2,
total_ram_bytes: 512 * 1024 * 1024,
}),
MIN_DECODE_CHUNK_BYTES
);
assert_eq!(
default_decode_chunk_bytes(SystemSpecs {
cpus: 2,
total_ram_bytes: 0,
}),
MIN_DECODE_CHUNK_BYTES
);
}
#[cfg(target_os = "linux")]
#[test]
fn meminfo_parser_reads_memtotal_kb() {
let sample = "MemTotal: 16384000 kB\nMemFree: 12345 kB\n";
assert_eq!(parse_meminfo_kb(sample), Some(16384000));
let reordered = "MemFree: 12345 kB\nSwapTotal: 0 kB\nMemTotal: 8000000 kB\n";
assert_eq!(parse_meminfo_kb(reordered), Some(8000000));
assert_eq!(parse_meminfo_kb("MemFree: 1 kB\n"), None);
assert_eq!(parse_meminfo_kb("MemTotal: kB\n"), None);
assert_eq!(parse_meminfo_kb(""), None);
}
#[cfg(target_os = "linux")]
#[test]
fn live_meminfo_agrees_with_detection() {
let specs = SystemSpecs::detect();
assert!(specs.total_ram_bytes >= 512 * 1024 * 1024);
}
}