1use serde::{Deserialize, Serialize};
8use std::sync::RwLock;
9use std::{collections::BTreeMap, path::PathBuf};
10
11static ACTIVE_SNAPSHOT: RwLock<Option<RuntimeConfigSnapshot>> = RwLock::new(None);
21
22pub fn install_runtime_snapshot(snapshot: RuntimeConfigSnapshot) {
24 *ACTIVE_SNAPSHOT
25 .write()
26 .expect("runtime snapshot lock poisoned") = Some(snapshot);
27}
28
29pub fn active_runtime_snapshot() -> RuntimeConfigSnapshot {
32 ACTIVE_SNAPSHOT
33 .read()
34 .expect("runtime snapshot lock poisoned")
35 .clone()
36 .unwrap_or_default()
37}
38
39#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41pub struct RuntimeConfigSnapshot {
42 pub entries: Vec<RuntimeConfigEntry>,
44}
45
46impl RuntimeConfigSnapshot {
47 pub fn capture_current() -> Self {
49 Self::from_env_vars(std::env::vars())
50 }
51
52 pub fn from_env_vars<I, K, V>(vars: I) -> Self
54 where
55 I: IntoIterator<Item = (K, V)>,
56 K: Into<String>,
57 V: Into<String>,
58 {
59 let mut sorted = BTreeMap::new();
60 for (key, value) in vars {
61 let key = key.into();
62 if key.starts_with("FERRUM_") {
63 sorted.insert(key, value.into());
64 }
65 }
66
67 Self {
68 entries: sorted
69 .into_iter()
70 .map(|(key, effective_value)| RuntimeConfigEntry {
71 affects: infer_effects(&key),
72 key,
73 effective_value,
74 source: RuntimeConfigSource::Env,
75 })
76 .collect(),
77 }
78 }
79
80 pub fn from_entries<I>(entries: I) -> Self
83 where
84 I: IntoIterator<Item = RuntimeConfigEntry>,
85 {
86 let mut sorted = BTreeMap::new();
87 for entry in entries {
88 sorted.insert(entry.key.clone(), entry);
89 }
90 Self {
91 entries: sorted.into_values().collect(),
92 }
93 }
94
95 pub fn upsert(
97 &mut self,
98 key: impl Into<String>,
99 effective_value: impl Into<String>,
100 source: RuntimeConfigSource,
101 ) {
102 self.upsert_entry(RuntimeConfigEntry::new(key, effective_value, source));
103 }
104
105 pub fn upsert_entry(&mut self, entry: RuntimeConfigEntry) {
107 let mut entries = std::mem::take(&mut self.entries);
108 entries.retain(|existing| existing.key != entry.key);
109 entries.push(entry);
110 *self = Self::from_entries(entries);
111 }
112
113 pub fn with_entry(
115 mut self,
116 key: impl Into<String>,
117 effective_value: impl Into<String>,
118 source: RuntimeConfigSource,
119 ) -> Self {
120 self.upsert(key, effective_value, source);
121 self
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct RuntimeConfigEntry {
128 pub key: String,
129 pub effective_value: String,
130 pub source: RuntimeConfigSource,
131 pub affects: Vec<RuntimeConfigEffect>,
132}
133
134impl RuntimeConfigEntry {
135 pub fn new(
136 key: impl Into<String>,
137 effective_value: impl Into<String>,
138 source: RuntimeConfigSource,
139 ) -> Self {
140 let key = key.into();
141 Self {
142 affects: infer_effects(&key),
143 key,
144 effective_value: effective_value.into(),
145 source,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum RuntimeConfigSource {
154 Default,
155 ConfigFile,
156 Cli,
157 Env,
158 ScriptCase,
159 MemoryProfile,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum RuntimeConfigEffect {
166 Correctness,
167 Performance,
168 Memory,
169 Diagnostics,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "snake_case")]
175pub enum EnvTriState {
176 Default,
177 ForcedOff,
178 ForcedOn,
179}
180
181pub fn parse_bool_env_value(raw: &str) -> Result<bool, String> {
182 match raw.trim().to_ascii_lowercase().as_str() {
183 "1" | "true" | "yes" | "on" => Ok(true),
184 "0" | "false" | "no" | "off" => Ok(false),
185 other => Err(format!("invalid boolean env value: {other:?}")),
186 }
187}
188
189pub fn parse_usize_env_value(raw: &str) -> Result<usize, String> {
190 raw.trim()
191 .parse::<usize>()
192 .map_err(|_| format!("invalid integer env value: {raw:?}"))
193}
194
195pub fn parse_path_env_value(raw: &str) -> Result<PathBuf, String> {
196 let trimmed = raw.trim();
197 if trimmed.is_empty() {
198 return Err("path env value must not be empty".to_string());
199 }
200 Ok(PathBuf::from(trimmed))
201}
202
203pub fn parse_tri_state_env_value(raw: Option<&str>) -> Result<EnvTriState, String> {
204 let Some(raw) = raw else {
205 return Ok(EnvTriState::Default);
206 };
207 if raw.trim().is_empty() {
208 return Ok(EnvTriState::Default);
209 }
210 Ok(if parse_bool_env_value(raw)? {
211 EnvTriState::ForcedOn
212 } else {
213 EnvTriState::ForcedOff
214 })
215}
216
217fn infer_effects(key: &str) -> Vec<RuntimeConfigEffect> {
218 let mut effects = Vec::new();
219
220 if key.contains("DIAG")
221 || key.contains("PROF")
222 || key.contains("TRACE")
223 || key.contains("DUMP")
224 || key.contains("LOG_CONFIG")
225 || key.contains("CAPTURE")
226 || key.contains("DEBUG")
227 {
228 effects.push(RuntimeConfigEffect::Diagnostics);
229 }
230
231 if key.contains("KV")
232 || key.contains("BATCHED_TOKENS")
233 || key.contains("PAGED_MAX_SEQS")
234 || key.contains("MODEL_LEN")
235 || key.contains("MEMORY")
236 {
237 effects.push(RuntimeConfigEffect::Memory);
238 }
239
240 if key.contains("PREFIX_CACHE")
241 || key.contains("MODEL_PATH")
242 || key.contains("MODEL_LEN")
243 || key.contains("SPEC_")
244 || key.contains("REF_")
245 || key.contains("DTYPE")
246 {
247 effects.push(RuntimeConfigEffect::Correctness);
248 }
249
250 if effects.is_empty()
251 || key.contains("MOE")
252 || key.contains("VLLM")
253 || key.contains("MARLIN")
254 || key.contains("PAGED")
255 || key.contains("GRAPH")
256 || key.contains("SCHED")
257 || key.contains("BATCH")
258 || key.contains("ATTN")
259 || key.contains("FLASH")
260 || key.contains("CUDA")
261 || key.contains("TRITON")
262 || key.contains("GREEDY")
263 || key.contains("FA")
264 {
265 effects.push(RuntimeConfigEffect::Performance);
266 }
267
268 effects.sort();
269 effects.dedup();
270 effects
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn parses_boolean_values() {
279 assert_eq!(parse_bool_env_value("1").unwrap(), true);
280 assert_eq!(parse_bool_env_value("off").unwrap(), false);
281 assert!(parse_bool_env_value("maybe").is_err());
282 }
283
284 #[test]
285 fn parses_integer_values() {
286 assert_eq!(parse_usize_env_value("4096").unwrap(), 4096);
287 assert!(parse_usize_env_value("-1").is_err());
288 assert!(parse_usize_env_value("many").is_err());
289 }
290
291 #[test]
292 fn parses_path_values() {
293 assert_eq!(
294 parse_path_env_value("/tmp/model").unwrap(),
295 PathBuf::from("/tmp/model")
296 );
297 assert!(parse_path_env_value(" ").is_err());
298 }
299
300 #[test]
301 fn parses_tri_state_values() {
302 assert_eq!(
303 parse_tri_state_env_value(None).unwrap(),
304 EnvTriState::Default
305 );
306 assert_eq!(
307 parse_tri_state_env_value(Some("0")).unwrap(),
308 EnvTriState::ForcedOff
309 );
310 assert_eq!(
311 parse_tri_state_env_value(Some("on")).unwrap(),
312 EnvTriState::ForcedOn
313 );
314 assert!(parse_tri_state_env_value(Some("auto")).is_err());
315 }
316
317 #[test]
318 fn snapshot_is_sorted_and_classified() {
319 let snapshot = RuntimeConfigSnapshot::from_env_vars([
320 ("OTHER_ENV", "ignored"),
321 ("FERRUM_PREFIX_CACHE", "1"),
322 ("FERRUM_MOE_GRAPH", "1"),
323 ]);
324 let keys: Vec<_> = snapshot
325 .entries
326 .iter()
327 .map(|entry| entry.key.as_str())
328 .collect();
329 assert_eq!(keys, vec!["FERRUM_MOE_GRAPH", "FERRUM_PREFIX_CACHE"]);
330 assert_eq!(snapshot.entries[0].source, RuntimeConfigSource::Env);
331 assert!(snapshot.entries[0]
332 .affects
333 .contains(&RuntimeConfigEffect::Performance));
334 assert!(snapshot.entries[1]
335 .affects
336 .contains(&RuntimeConfigEffect::Correctness));
337 }
338
339 #[test]
340 fn upsert_preserves_non_env_source_and_stable_order() {
341 let mut snapshot = RuntimeConfigSnapshot::from_env_vars([
342 ("FERRUM_KV_DTYPE", "fp16"),
343 ("FERRUM_MOE_GRAPH", "1"),
344 ]);
345 snapshot.upsert("FERRUM_KV_DTYPE", "int8", RuntimeConfigSource::Cli);
346 snapshot.upsert(
347 "FERRUM_PROFILE_JSONL",
348 "/tmp/profile.jsonl",
349 RuntimeConfigSource::Cli,
350 );
351
352 let keys: Vec<_> = snapshot
353 .entries
354 .iter()
355 .map(|entry| entry.key.as_str())
356 .collect();
357 assert_eq!(
358 keys,
359 [
360 "FERRUM_KV_DTYPE",
361 "FERRUM_MOE_GRAPH",
362 "FERRUM_PROFILE_JSONL"
363 ]
364 );
365 let kv = snapshot
366 .entries
367 .iter()
368 .find(|entry| entry.key == "FERRUM_KV_DTYPE")
369 .unwrap();
370 assert_eq!(kv.effective_value, "int8");
371 assert_eq!(kv.source, RuntimeConfigSource::Cli);
372 assert!(kv.affects.contains(&RuntimeConfigEffect::Correctness));
373
374 let profile = snapshot
375 .entries
376 .iter()
377 .find(|entry| entry.key == "FERRUM_PROFILE_JSONL")
378 .unwrap();
379 assert_eq!(profile.source, RuntimeConfigSource::Cli);
380 assert!(profile.affects.contains(&RuntimeConfigEffect::Diagnostics));
381 }
382}