pub fn flag(key: &str) -> bool {
match std::env::var(key) {
Ok(v) => {
let v = v.trim();
!(v.is_empty()
|| v == "0"
|| v.eq_ignore_ascii_case("false")
|| v.eq_ignore_ascii_case("no")
|| v.eq_ignore_ascii_case("off"))
}
Err(_) => false,
}
}
pub fn nonempty(key: &str) -> Option<String> {
match std::env::var(key) {
Ok(v) => {
let v = v.trim();
(!v.is_empty()).then(|| v.to_string())
}
Err(_) => None,
}
}
pub fn parse<T: std::str::FromStr>(key: &str) -> Option<T> {
std::env::var(key).ok().and_then(|v| v.trim().parse().ok())
}
pub fn debug_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| flag("DOCLING_RS_DEBUG"))
}
#[macro_export]
macro_rules! debug_log {
($($arg:tt)*) => {
if $crate::env::debug_enabled() {
eprintln!($($arg)*);
}
};
}
pub fn cpu_budget() -> usize {
let host = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
match cgroup_cpu_quota() {
Some(q) => host.min(q).max(1),
None => host,
}
}
fn cgroup_cpu_quota() -> Option<usize> {
if let Ok(s) = std::fs::read_to_string("/sys/fs/cgroup/cpu.max") {
return parse_cpu_max(&s);
}
for dir in ["/sys/fs/cgroup/cpu", "/sys/fs/cgroup/cpu,cpuacct"] {
if let (Ok(quota), Ok(period)) = (
std::fs::read_to_string(format!("{dir}/cpu.cfs_quota_us")),
std::fs::read_to_string(format!("{dir}/cpu.cfs_period_us")),
) {
return parse_cfs("a, &period);
}
}
None
}
fn parse_cpu_max(s: &str) -> Option<usize> {
let mut it = s.split_whitespace();
let quota = it.next()?;
if quota == "max" {
return None;
}
let quota: u64 = quota.parse().ok()?;
let period: u64 = it.next()?.parse().ok()?;
if period == 0 || quota == 0 {
return None;
}
Some(quota.div_ceil(period) as usize)
}
fn parse_cfs(quota: &str, period: &str) -> Option<usize> {
let quota: i64 = quota.trim().parse().ok()?;
if quota <= 0 {
return None;
}
let period: i64 = period.trim().parse().ok()?;
if period <= 0 {
return None;
}
Some((quota as u64).div_ceil(period as u64) as usize)
}
pub fn cgroup_memory_limit_mb() -> Option<u64> {
for path in [
"/sys/fs/cgroup/memory.max",
"/sys/fs/cgroup/memory/memory.limit_in_bytes",
] {
if let Ok(s) = std::fs::read_to_string(path) {
let s = s.trim();
if s == "max" {
return None;
}
let bytes: u64 = s.parse().ok()?;
if bytes >= 1 << 60 {
return None;
}
return Some(bytes / (1024 * 1024));
}
}
None
}
pub fn rss_mb() -> Option<u64> {
let status = std::fs::read_to_string("/proc/self/status").ok()?;
let line = status.lines().find(|l| l.starts_with("VmRSS:"))?;
let kb: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
Some(kb / 1024)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flag_spellings() {
for on in ["1", "true", "yes", "on", "anything", " 1 ", "TRUE"] {
std::env::set_var("DOCLING_TEST_FLAG_ON", on);
assert!(flag("DOCLING_TEST_FLAG_ON"), "{on:?} should enable");
}
for off in ["", "0", "false", "no", "off", " OFF ", "No"] {
std::env::set_var("DOCLING_TEST_FLAG_OFF", off);
assert!(!flag("DOCLING_TEST_FLAG_OFF"), "{off:?} should disable");
}
assert!(!flag("DOCLING_TEST_FLAG_UNSET"));
}
#[test]
fn nonempty_trims_and_drops_blank() {
std::env::set_var("DOCLING_TEST_NONEMPTY", " x ");
assert_eq!(nonempty("DOCLING_TEST_NONEMPTY").as_deref(), Some("x"));
std::env::set_var("DOCLING_TEST_NONEMPTY_BLANK", " ");
assert_eq!(nonempty("DOCLING_TEST_NONEMPTY_BLANK"), None);
assert_eq!(nonempty("DOCLING_TEST_NONEMPTY_UNSET"), None);
}
#[test]
fn cpu_quota_parsers_cover_both_cgroup_versions() {
assert_eq!(super::parse_cpu_max("max 100000\n"), None);
assert_eq!(super::parse_cpu_max("400000 100000"), Some(4));
assert_eq!(super::parse_cpu_max("250000 100000"), Some(3));
assert_eq!(super::parse_cpu_max("garbage"), None);
assert_eq!(super::parse_cfs("-1\n", "100000\n"), None);
assert_eq!(super::parse_cfs("400000", "100000"), Some(4));
assert_eq!(super::parse_cfs("150000", "100000"), Some(2));
assert_eq!(super::parse_cfs("x", "100000"), None);
}
#[test]
fn rss_reads_on_linux() {
#[cfg(target_os = "linux")]
assert!(super::rss_mb().unwrap() > 0);
}
#[test]
fn parse_trims_and_ignores_garbage() {
std::env::set_var("DOCLING_TEST_PARSE", " 42 ");
assert_eq!(parse::<usize>("DOCLING_TEST_PARSE"), Some(42));
std::env::set_var("DOCLING_TEST_PARSE_BAD", "many");
assert_eq!(parse::<usize>("DOCLING_TEST_PARSE_BAD"), None);
assert_eq!(parse::<usize>("DOCLING_TEST_PARSE_UNSET"), None);
}
}