const HARD_CEILING_BYTES: u64 = 16 * 1024 * 1024 * 1024;
const DEFAULT_FRACTION_PCT: u64 = 70;
const MAX_FRACTION_PCT: u64 = 85;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ThreadBudget {
pub total_cores: usize,
pub job_threads: usize,
}
impl ThreadBudget {
pub(crate) fn compute(job_threads_override: u32) -> Self {
let total_cores = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
let job_threads = if job_threads_override > 0 {
(job_threads_override as usize).min(total_cores)
} else {
total_cores.saturating_sub(1).max(1)
};
Self {
total_cores,
job_threads,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryBudget {
pub total_ram_bytes: Option<u64>,
pub budget_bytes: u64,
pub overridden: bool,
}
impl MemoryBudget {
pub(crate) fn compute(total_ram_bytes: Option<u64>, max_memory_mb_override: u32) -> Self {
let override_bytes =
(max_memory_mb_override > 0).then(|| (max_memory_mb_override as u64) * 1024 * 1024);
let budget_bytes = match (total_ram_bytes, override_bytes) {
(Some(ram), Some(want)) => want.min(ram * MAX_FRACTION_PCT / 100),
(Some(ram), None) => HARD_CEILING_BYTES.min(ram * DEFAULT_FRACTION_PCT / 100),
(None, Some(want)) => want,
(None, None) => HARD_CEILING_BYTES,
};
Self {
total_ram_bytes,
budget_bytes,
overridden: override_bytes.is_some(),
}
}
pub fn budget_mib(&self) -> u64 {
self.budget_bytes / (1024 * 1024)
}
}
#[cfg(test)]
mod tests {
use super::*;
const GIB: u64 = 1024 * 1024 * 1024;
#[test]
fn auto_thread_budget_leaves_a_core_for_the_main_thread() {
let tb = ThreadBudget::compute(0);
assert_eq!(tb.job_threads, tb.total_cores.saturating_sub(1).max(1));
assert!(tb.job_threads >= 1);
}
#[test]
fn thread_override_is_honored_but_capped_at_core_count() {
let tb = ThreadBudget::compute(2);
assert_eq!(tb.job_threads, 2.min(tb.total_cores));
let huge = ThreadBudget::compute(9999);
assert_eq!(huge.job_threads, huge.total_cores);
}
#[test]
fn default_memory_budget_is_the_smaller_of_ceiling_and_fraction() {
let small = MemoryBudget::compute(Some(8 * GIB), 0);
assert_eq!(small.budget_bytes, 8 * GIB * 70 / 100);
assert!(!small.overridden);
let large = MemoryBudget::compute(Some(256 * GIB), 0);
assert_eq!(large.budget_bytes, HARD_CEILING_BYTES);
}
#[test]
fn memory_override_is_honored_but_clamped_to_a_safe_fraction() {
let ok = MemoryBudget::compute(Some(32 * GIB), 4096);
assert_eq!(ok.budget_bytes, 4096 * 1024 * 1024);
assert!(ok.overridden);
let greedy = MemoryBudget::compute(Some(8 * GIB), 16384);
assert_eq!(greedy.budget_bytes, 8 * GIB * 85 / 100);
assert!(greedy.overridden);
}
#[test]
fn unknown_ram_falls_back_to_ceiling_or_bare_override() {
let fallback = MemoryBudget::compute(None, 0);
assert_eq!(fallback.budget_bytes, HARD_CEILING_BYTES);
assert!(!fallback.overridden);
let bare_override = MemoryBudget::compute(None, 2048);
assert_eq!(bare_override.budget_bytes, 2048 * 1024 * 1024);
assert!(bare_override.overridden);
}
}