use gregg_protocol::MemoryMetrics;
use crate::collector::error::{CollectError, CollectErrorKind};
use crate::collector::macos::ffi::RawVmStats;
use crate::collector::macos::normalize::percent;
#[derive(Debug, Clone, PartialEq)]
pub struct MemorySample {
pub used_bytes: u64,
pub total_bytes: u64,
}
impl MemorySample {
#[must_use]
pub fn into_metrics(self) -> MemoryMetrics {
let usage_pct = percent(self.used_bytes, self.total_bytes);
MemoryMetrics {
used_bytes: self.used_bytes,
total_bytes: self.total_bytes,
usage_pct,
}
}
}
pub fn compute_memory(raw: &RawVmStats, total_bytes: u64) -> Result<MemorySample, CollectError> {
if total_bytes == 0 {
return Ok(MemorySample {
used_bytes: 0,
total_bytes: 0,
});
}
let available_pages = raw.free_count.saturating_add(raw.inactive_count);
let available_bytes = available_pages.checked_mul(raw.page_size).ok_or_else(|| {
CollectError::new(
CollectErrorKind::Numeric,
"available pages * page_size overflowed u64",
)
})?;
let available_bytes = available_bytes.min(total_bytes);
let used_bytes = total_bytes - available_bytes;
Ok(MemorySample {
used_bytes,
total_bytes,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collector::macos::ffi::RawVmStats;
fn sample_vm() -> RawVmStats {
RawVmStats {
free_count: 100_000,
active_count: 200_000,
inactive_count: 150_000,
wire_count: 50_000,
page_size: 16_384,
}
}
#[test]
fn normal_case() {
let raw = sample_vm();
let total = 16_000_000_000;
let mem = compute_memory(&raw, total).expect("computes");
assert_eq!(mem.used_bytes, 11_904_000_000);
assert_eq!(mem.total_bytes, total);
}
#[test]
fn zero_total() {
let raw = sample_vm();
let mem = compute_memory(&raw, 0).expect("zero total");
assert_eq!(mem.used_bytes, 0);
assert_eq!(mem.total_bytes, 0);
}
#[test]
fn available_exceeding_total_is_clamped() {
let raw = RawVmStats {
free_count: u64::MAX / 16_384,
active_count: 0,
inactive_count: 0,
wire_count: 0,
page_size: 16_384,
};
let total = 1_000_000_000;
let mem = compute_memory(&raw, total).expect("clamped");
assert_eq!(mem.used_bytes, 0);
assert_eq!(mem.total_bytes, total);
}
#[test]
fn used_never_exceeds_total() {
let raw = sample_vm();
for total in [1, 1000, 16_000_000_000] {
let mem = compute_memory(&raw, total).expect("computes");
assert!(mem.used_bytes <= mem.total_bytes, "total={total}");
}
}
#[test]
fn into_metrics_produces_valid_percentage() {
let raw = sample_vm();
let mem = compute_memory(&raw, 16_000_000_000).expect("computes");
let metrics = mem.into_metrics();
assert!(metrics.usage_pct.is_finite());
assert!((0.0..=100.0).contains(&metrics.usage_pct));
}
}