use crate::model::MetricState;
use crate::units::{Percent, Rate};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum MemorySemantics {
LinuxMemAvailable,
MacosVmStatistics,
SysinfoBaseline,
}
impl MemorySemantics {
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::LinuxMemAvailable => {
"used = total - MemAvailable; page cache and buffers are not counted as \
application use"
}
Self::MacosVmStatistics => {
"available = free + inactive + purgeable; wired and compressed pages are \
reported separately and are not reclaimable like Linux page cache"
}
Self::SysinfoBaseline => {
"cross-platform baseline; coarser than the native definition and not \
byte-for-byte comparable with it"
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MemoryDetail {
pub cached: MetricState<u64>,
pub buffers: MetricState<u64>,
pub shared: MetricState<u64>,
pub active: MetricState<u64>,
pub inactive: MetricState<u64>,
pub wired: MetricState<u64>,
pub compressed: MetricState<u64>,
pub dirty: MetricState<u64>,
}
impl MemoryDetail {
pub const WARMING_UP: Self = Self {
cached: MetricState::WarmingUp,
buffers: MetricState::WarmingUp,
shared: MetricState::WarmingUp,
active: MetricState::WarmingUp,
inactive: MetricState::WarmingUp,
wired: MetricState::WarmingUp,
compressed: MetricState::WarmingUp,
dirty: MetricState::WarmingUp,
};
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SwapSnapshot {
pub total_bytes: u64,
pub used: MetricState<u64>,
pub usage: MetricState<Percent>,
pub in_rate: MetricState<Rate>,
pub out_rate: MetricState<Rate>,
}
impl SwapSnapshot {
#[must_use]
pub const fn is_enabled(&self) -> bool {
self.total_bytes > 0
}
#[must_use]
pub const fn disabled() -> Self {
Self {
total_bytes: 0,
used: MetricState::Available(0),
usage: MetricState::Unsupported,
in_rate: MetricState::Unsupported,
out_rate: MetricState::Unsupported,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MemorySnapshot {
pub total_bytes: u64,
pub available: MetricState<u64>,
pub used: MetricState<u64>,
pub free: MetricState<u64>,
pub usage: MetricState<Percent>,
pub detail: MemoryDetail,
pub swap: SwapSnapshot,
pub semantics: MemorySemantics,
pub cgroup_limit_bytes: MetricState<u64>,
}
impl MemorySnapshot {
#[must_use]
pub const fn warming_up(total_bytes: u64, semantics: MemorySemantics) -> Self {
Self {
total_bytes,
available: MetricState::WarmingUp,
used: MetricState::WarmingUp,
free: MetricState::WarmingUp,
usage: MetricState::WarmingUp,
detail: MemoryDetail::WARMING_UP,
swap: SwapSnapshot {
total_bytes: 0,
used: MetricState::WarmingUp,
usage: MetricState::WarmingUp,
in_rate: MetricState::WarmingUp,
out_rate: MetricState::WarmingUp,
},
semantics,
cgroup_limit_bytes: MetricState::WarmingUp,
}
}
#[must_use]
pub fn effective_limit_bytes(&self) -> u64 {
match self.cgroup_limit_bytes.fresh() {
Some(&limit) if limit > 0 && limit < self.total_bytes => limit,
_ => self.total_bytes,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_platform_semantics_explains_itself() {
for semantics in [
MemorySemantics::LinuxMemAvailable,
MemorySemantics::MacosVmStatistics,
MemorySemantics::SysinfoBaseline,
] {
assert!(!semantics.description().is_empty());
}
assert!(
MemorySemantics::LinuxMemAvailable
.description()
.contains("page cache")
);
}
#[test]
fn disabled_swap_is_a_fact_not_an_unavailable_metric() {
let swap = SwapSnapshot::disabled();
assert!(!swap.is_enabled());
assert_eq!(
swap.used.fresh(),
Some(&0),
"0 of 0 bytes used is a real measurement"
);
assert!(swap.usage.fresh().is_none());
}
#[test]
fn a_cgroup_limit_below_the_host_total_becomes_the_effective_ceiling() {
let mut memory =
MemorySnapshot::warming_up(32 * 1024 * 1024 * 1024, MemorySemantics::LinuxMemAvailable);
assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
memory.cgroup_limit_bytes = MetricState::Available(2 * 1024 * 1024 * 1024);
assert_eq!(memory.effective_limit_bytes(), 2 * 1024 * 1024 * 1024);
}
#[test]
fn an_unlimited_cgroup_does_not_shrink_the_ceiling() {
let mut memory =
MemorySnapshot::warming_up(32 * 1024 * 1024 * 1024, MemorySemantics::LinuxMemAvailable);
memory.cgroup_limit_bytes = MetricState::Available(u64::MAX);
assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
memory.cgroup_limit_bytes = MetricState::Available(0);
assert_eq!(memory.effective_limit_bytes(), 32 * 1024 * 1024 * 1024);
}
#[test]
fn warming_up_preserves_the_requested_semantics() {
let memory = MemorySnapshot::warming_up(1024, MemorySemantics::MacosVmStatistics);
assert_eq!(memory.semantics, MemorySemantics::MacosVmStatistics);
assert_eq!(memory.total_bytes, 1024);
assert!(memory.available.is_warming_up());
}
}