use gregg_protocol::{MemoryMetrics, SwapMetrics};
use crate::collector::error::{CollectError, CollectErrorKind};
use crate::collector::linux::source::ParsedMeminfo;
#[derive(Debug, Clone, PartialEq)]
pub struct MemorySample {
pub used_bytes: u64,
pub total_bytes: u64,
pub fallback_used: bool,
}
impl MemorySample {
#[must_use]
pub fn into_metrics(self) -> MemoryMetrics {
MemoryMetrics {
usage_pct: crate::collector::clamped_usage_pct(self.used_bytes, self.total_bytes),
used_bytes: self.used_bytes,
total_bytes: self.total_bytes,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SwapSample {
pub used_bytes: u64,
pub total_bytes: u64,
}
impl SwapSample {
#[must_use]
pub fn into_metrics(self) -> SwapMetrics {
SwapMetrics {
usage_pct: crate::collector::clamped_usage_pct(self.used_bytes, self.total_bytes),
used_bytes: self.used_bytes,
total_bytes: self.total_bytes,
}
}
}
pub fn parse_meminfo(raw: &str) -> Result<ParsedMeminfo, CollectError> {
let mut info = ParsedMeminfo::default();
for line in raw.lines() {
let Some((key, rest)) = line.split_once(':') else {
continue;
};
let value_token = rest.split_whitespace().next();
let value_kb: Option<u64> = match value_token {
Some(token) => match token.parse::<u64>() {
Ok(n) => Some(n),
Err(_) => continue,
},
None => continue,
};
match key.trim() {
"MemTotal" => info.mem_total_kb = value_kb,
"MemAvailable" => info.mem_available_kb = value_kb,
"MemFree" => info.mem_free_kb = value_kb,
"Buffers" => info.buffers_kb = value_kb,
"Cached" => info.cached_kb = value_kb,
"SReclaimable" => info.s_reclaimable_kb = value_kb,
"SwapTotal" => info.swap_total_kb = value_kb,
"SwapFree" => info.swap_free_kb = value_kb,
_ => {}
}
}
Ok(info)
}
pub fn compute_memory(info: &ParsedMeminfo) -> Result<MemorySample, CollectError> {
let total_kb = info.mem_total_kb.ok_or_else(|| {
CollectError::new(CollectErrorKind::Parse, "/proc/meminfo missing MemTotal")
})?;
let total_bytes = kb_to_bytes(total_kb)?;
let (available_bytes, fallback_used) = if let Some(avail_kb) = info.mem_available_kb {
(kb_to_bytes(avail_kb)?, false)
} else {
let free = info.mem_free_kb.unwrap_or(0);
let buffers = info.buffers_kb.unwrap_or(0);
let cached = info.cached_kb.unwrap_or(0);
let reclaimable = info.s_reclaimable_kb.unwrap_or(0);
let avail_kb = free
.checked_add(buffers)
.and_then(|v| v.checked_add(cached))
.and_then(|v| v.checked_add(reclaimable))
.ok_or_else(|| {
CollectError::new(
CollectErrorKind::Numeric,
"memory fallback addition overflowed u64",
)
})?;
(kb_to_bytes(avail_kb)?, true)
};
if available_bytes > total_bytes {
return Err(CollectError::new(
CollectErrorKind::Numeric,
"available memory exceeds total memory",
));
}
let used_bytes = total_bytes - available_bytes;
Ok(MemorySample {
used_bytes,
total_bytes,
fallback_used,
})
}
pub fn compute_swap(info: &ParsedMeminfo) -> Result<SwapSample, CollectError> {
let total_kb = info.swap_total_kb.unwrap_or(0);
let free_kb = info.swap_free_kb.unwrap_or(0);
let total_bytes = kb_to_bytes(total_kb)?;
let free_clamped = free_kb.min(total_kb);
let used_kb = total_kb - free_clamped;
let used_bytes = kb_to_bytes(used_kb)?;
Ok(SwapSample {
used_bytes,
total_bytes,
})
}
fn kb_to_bytes(kb: u64) -> Result<u64, CollectError> {
kb.checked_mul(1024).ok_or_else(|| {
CollectError::new(
CollectErrorKind::Numeric,
"kilobyte-to-byte conversion overflowed u64",
)
})
}