use sysinfo::{MemoryRefreshKind, RefreshKind, System};
use crate::errors::AppError;
pub fn available_memory_mb() -> u64 {
let sys =
System::new_with_specifics(RefreshKind::new().with_memory(MemoryRefreshKind::everything()));
let available_bytes = sys.available_memory();
available_bytes / (1024 * 1024)
}
pub fn calculate_safe_concurrency(
available_mb: u64,
cpu_count: usize,
ram_per_task_mb: u64,
max_concurrency: usize,
) -> usize {
let cpu_count = cpu_count.max(1);
let max_concurrency = max_concurrency.max(1);
let ram_per_task_mb = ram_per_task_mb.max(1);
let memory_bound = (available_mb / ram_per_task_mb) as usize;
let resource_bound = cpu_count.min(memory_bound).max(1);
let safe_with_margin = (resource_bound / 2).max(1);
safe_with_margin.min(max_concurrency)
}
pub fn check_available_memory(min_mb: u64) -> Result<u64, AppError> {
let available_mb = available_memory_mb();
if available_mb < min_mb {
return Err(AppError::LowMemory {
available_mb,
required_mb: min_mb,
});
}
Ok(available_mb)
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn check_available_memory_com_zero_sempre_passa() {
let resultado = check_available_memory(0);
assert!(
resultado.is_ok(),
"min_mb=0 deve sempre passar, got: {resultado:?}"
);
let mb = resultado.unwrap();
assert!(mb > 0, "sistema deve reportar memória positiva");
}
#[test]
fn check_available_memory_com_valor_gigante_falha() {
let resultado = check_available_memory(u64::MAX);
assert!(
matches!(resultado, Err(AppError::LowMemory { .. })),
"u64::MAX MiB deve falhar com LowMemory, got: {resultado:?}"
);
}
#[test]
fn low_memory_error_contem_valores_corretos() {
match check_available_memory(u64::MAX) {
Err(AppError::LowMemory {
available_mb,
required_mb,
}) => {
assert_eq!(required_mb, u64::MAX);
assert!(available_mb < u64::MAX);
}
outro => panic!("esperado LowMemory, got: {outro:?}"),
}
}
#[test]
fn calculate_safe_concurrency_respeita_metade_da_margem() {
let permits = calculate_safe_concurrency(8_000, 8, 1_000, 4);
assert_eq!(permits, 4);
}
#[test]
fn calculate_safe_concurrency_nunca_retorna_zero() {
let permits = calculate_safe_concurrency(100, 1, 10_000, 4);
assert_eq!(permits, 1);
}
#[test]
fn calculate_safe_concurrency_respeita_teto_maximo() {
let permits = calculate_safe_concurrency(128_000, 64, 500, 4);
assert_eq!(permits, 4);
}
}