sqlite_graphrag/
memory_guard.rs1use sysinfo::{MemoryRefreshKind, RefreshKind, System};
13
14use crate::errors::AppError;
15
16pub fn available_memory_mb() -> u64 {
18 let sys =
19 System::new_with_specifics(RefreshKind::new().with_memory(MemoryRefreshKind::everything()));
20 let available_bytes = sys.available_memory();
21 available_bytes / (1024 * 1024)
22}
23
24pub fn calculate_safe_concurrency(
31 available_mb: u64,
32 cpu_count: usize,
33 ram_per_task_mb: u64,
34 max_concurrency: usize,
35) -> usize {
36 let cpu_count = cpu_count.max(1);
37 let max_concurrency = max_concurrency.max(1);
38 let ram_per_task_mb = ram_per_task_mb.max(1);
39
40 let memory_bound = (available_mb / ram_per_task_mb) as usize;
41 let resource_bound = cpu_count.min(memory_bound).max(1);
42 let safe_with_margin = (resource_bound / 2).max(1);
43
44 safe_with_margin.min(max_concurrency)
45}
46
47pub fn check_available_memory(min_mb: u64) -> Result<u64, AppError> {
59 let available_mb = available_memory_mb();
60
61 if available_mb < min_mb {
62 return Err(AppError::LowMemory {
63 available_mb,
64 required_mb: min_mb,
65 });
66 }
67
68 Ok(available_mb)
69}
70
71#[cfg(test)]
72mod testes {
73 use super::*;
74
75 #[test]
76 fn check_available_memory_com_zero_sempre_passa() {
77 let resultado = check_available_memory(0);
78 assert!(
79 resultado.is_ok(),
80 "min_mb=0 deve sempre passar, got: {resultado:?}"
81 );
82 let mb = resultado.unwrap();
83 assert!(mb > 0, "sistema deve reportar memória positiva");
84 }
85
86 #[test]
87 fn check_available_memory_com_valor_gigante_falha() {
88 let resultado = check_available_memory(u64::MAX);
89 assert!(
90 matches!(resultado, Err(AppError::LowMemory { .. })),
91 "u64::MAX MiB deve falhar com LowMemory, got: {resultado:?}"
92 );
93 }
94
95 #[test]
96 fn low_memory_error_contem_valores_corretos() {
97 match check_available_memory(u64::MAX) {
98 Err(AppError::LowMemory {
99 available_mb,
100 required_mb,
101 }) => {
102 assert_eq!(required_mb, u64::MAX);
103 assert!(available_mb < u64::MAX);
104 }
105 outro => panic!("esperado LowMemory, got: {outro:?}"),
106 }
107 }
108
109 #[test]
110 fn calculate_safe_concurrency_respeita_metade_da_margem() {
111 let permits = calculate_safe_concurrency(8_000, 8, 1_000, 4);
112 assert_eq!(permits, 4);
113 }
114
115 #[test]
116 fn calculate_safe_concurrency_nunca_retorna_zero() {
117 let permits = calculate_safe_concurrency(100, 1, 10_000, 4);
118 assert_eq!(permits, 1);
119 }
120
121 #[test]
122 fn calculate_safe_concurrency_respeita_teto_maximo() {
123 let permits = calculate_safe_concurrency(128_000, 64, 500, 4);
124 assert_eq!(permits, 4);
125 }
126}