http_handle/
runtime_autotune.rs1use std::num::NonZeroUsize;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct HostResourceProfile {
23 pub cpu_cores: usize,
25 pub memory_mib: usize,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct RuntimeTuneRecommendation {
44 pub max_inflight: usize,
46 pub max_queue: usize,
48 pub sendfile_threshold_bytes: u64,
50}
51
52impl RuntimeTuneRecommendation {
53 pub fn from_profile(profile: HostResourceProfile) -> Self {
67 let cores = profile.cpu_cores.max(1);
68 let mem = profile.memory_mib.max(256);
69 let max_inflight = (cores * 128).clamp(64, 4096);
70 let max_queue = (cores * 512).clamp(256, 16384);
71 let sendfile_threshold_bytes =
72 if mem < 1024 { 256 * 1024 } else { 64 * 1024 };
73 Self {
74 max_inflight,
75 max_queue,
76 sendfile_threshold_bytes,
77 }
78 }
79}
80
81#[cfg(feature = "high-perf")]
82impl RuntimeTuneRecommendation {
83 pub fn into_perf_limits(self) -> crate::perf_server::PerfLimits {
101 crate::perf_server::PerfLimits {
102 max_inflight: self.max_inflight,
103 max_queue: self.max_queue,
104 sendfile_threshold_bytes: self.sendfile_threshold_bytes,
105 }
106 }
107}
108
109pub fn detect_host_profile() -> HostResourceProfile {
123 let cpu_cores = std::thread::available_parallelism()
124 .unwrap_or_else(|_| NonZeroUsize::new(1).expect("non-zero"))
125 .get();
126 let memory_mib = detect_memory_mib().unwrap_or(2048);
127 HostResourceProfile {
128 cpu_cores,
129 memory_mib,
130 }
131}
132
133fn detect_memory_mib() -> Option<usize> {
134 if let Ok(val) = std::env::var("HTTP_HANDLE_MEMORY_MIB")
135 && let Ok(parsed) = val.parse::<usize>()
136 {
137 return Some(parsed);
138 }
139 #[cfg(target_os = "linux")]
140 {
141 if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo")
142 && let Some(line) =
143 meminfo.lines().find(|l| l.starts_with("MemTotal:"))
144 {
145 let kb = line
146 .split_whitespace()
147 .nth(1)
148 .and_then(|v| v.parse::<usize>().ok())?;
149 return Some(kb / 1024);
150 }
151 }
152 None
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use std::sync::{Mutex, OnceLock};
159
160 fn env_lock() -> &'static Mutex<()> {
161 static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
162 LOCK.get_or_init(|| Mutex::new(()))
163 }
164
165 #[test]
166 fn recommendation_scales_with_profile() {
167 let small = RuntimeTuneRecommendation::from_profile(
168 HostResourceProfile {
169 cpu_cores: 2,
170 memory_mib: 512,
171 },
172 );
173 let large = RuntimeTuneRecommendation::from_profile(
174 HostResourceProfile {
175 cpu_cores: 16,
176 memory_mib: 16384,
177 },
178 );
179 assert!(large.max_inflight > small.max_inflight);
180 assert!(large.max_queue > small.max_queue);
181 assert!(
182 small.sendfile_threshold_bytes
183 > large.sendfile_threshold_bytes
184 );
185 }
186
187 #[test]
188 fn detect_profile_has_sane_minimums() {
189 let profile = detect_host_profile();
190 assert!(profile.cpu_cores >= 1);
191 assert!(profile.memory_mib >= 1);
192 }
193
194 #[test]
195 fn detect_memory_uses_env_hint_when_valid() {
196 let _guard = env_lock().lock().expect("env lock");
197 let previous = std::env::var("HTTP_HANDLE_MEMORY_MIB").ok();
198 unsafe { std::env::set_var("HTTP_HANDLE_MEMORY_MIB", "3072") };
200 let got = detect_memory_mib();
201 if let Some(old) = previous {
202 unsafe { std::env::set_var("HTTP_HANDLE_MEMORY_MIB", old) };
204 } else {
205 unsafe { std::env::remove_var("HTTP_HANDLE_MEMORY_MIB") };
207 }
208 assert_eq!(got, Some(3072));
209 }
210
211 #[test]
212 fn detect_memory_ignores_invalid_env_hint() {
213 let _guard = env_lock().lock().expect("env lock");
214 let previous = std::env::var("HTTP_HANDLE_MEMORY_MIB").ok();
215 unsafe {
217 std::env::set_var("HTTP_HANDLE_MEMORY_MIB", "not-a-number")
218 };
219 let got = detect_memory_mib();
220 if let Some(old) = previous {
221 unsafe { std::env::set_var("HTTP_HANDLE_MEMORY_MIB", old) };
223 } else {
224 unsafe { std::env::remove_var("HTTP_HANDLE_MEMORY_MIB") };
226 }
227 assert!(got.is_none() || got.expect("value") >= 1);
228 }
229
230 #[cfg(feature = "high-perf")]
231 #[test]
232 fn recommendation_maps_to_perf_limits() {
233 let rec = RuntimeTuneRecommendation {
234 max_inflight: 123,
235 max_queue: 456,
236 sendfile_threshold_bytes: 789,
237 };
238 let limits = rec.into_perf_limits();
239 assert_eq!(limits.max_inflight, 123);
240 assert_eq!(limits.max_queue, 456);
241 assert_eq!(limits.sendfile_threshold_bytes, 789);
242 }
243}