#![allow(dead_code)]
use gregg_protocol::{LoadAverage, MemoryMetrics, SystemIdentity};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NormalizedDrive {
pub name: String,
pub used_bytes: u64,
pub total_bytes: u64,
pub available_bytes: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DriveAggregate {
pub used_bytes: u64,
pub total_bytes: u64,
pub available_bytes: u64,
pub usage_pct: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::struct_excessive_bools)]
pub struct NormalizedSnapshot {
pub wire_version: u16,
pub observed_at_unix_ms: u64,
pub sample_interval_ms: u64,
pub cpu_iowait_supported: bool,
pub load_supported: bool,
pub swap_supported: bool,
pub commit_supported: bool,
pub system: SystemIdentity,
pub logical_cores: u32,
pub usage_pct: f32,
pub iowait_pct: Option<f32>,
pub load: Option<LoadAverage>,
pub memory: MemoryMetrics,
pub swap: Option<SwapMetrics>,
pub commit: Option<CommitMetrics>,
pub drives: Option<Vec<NormalizedDrive>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SwapMetrics {
pub used_bytes: u64,
pub total_bytes: u64,
pub usage_pct: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CommitMetrics {
pub used_bytes: u64,
pub limit_bytes: u64,
pub usage_pct: f32,
}
impl NormalizedSnapshot {
pub fn from_v1(snap: &gregg_protocol::StatusSnapshot) -> Self {
Self {
wire_version: gregg_protocol::SCHEMA_VERSION_V1,
observed_at_unix_ms: snap.observed_at_unix_ms,
sample_interval_ms: snap.sample_interval_ms,
cpu_iowait_supported: snap.capabilities.cpu_iowait,
load_supported: true,
swap_supported: true,
commit_supported: false,
system: snap.system.clone(),
logical_cores: snap.cpu.logical_cores,
usage_pct: snap.cpu.usage_pct,
iowait_pct: snap.cpu.iowait_pct,
load: Some(snap.load),
memory: snap.memory,
swap: Some(SwapMetrics {
used_bytes: snap.swap.used_bytes,
total_bytes: snap.swap.total_bytes,
usage_pct: snap.swap.usage_pct,
}),
commit: None,
drives: None,
}
}
pub fn from_v2(snap: &gregg_protocol::v2::StatusSnapshotV2) -> Self {
Self::from_v2_parts(snap, None)
}
pub fn from_v2_payload(payload: &gregg_protocol::v2::StatusPayloadV2) -> Self {
let drives = payload.drives.as_ref().map(|drives| {
drives
.iter()
.map(|drive| NormalizedDrive {
name: drive.name.clone(),
used_bytes: drive.used_bytes,
total_bytes: drive.total_bytes,
available_bytes: drive.available_bytes,
})
.collect()
});
Self::from_v2_parts(&payload.snapshot, drives)
}
fn from_v2_parts(
snap: &gregg_protocol::v2::StatusSnapshotV2,
drives: Option<Vec<NormalizedDrive>>,
) -> Self {
Self {
wire_version: gregg_protocol::v2::SCHEMA_VERSION_V2,
observed_at_unix_ms: snap.observed_at_unix_ms,
sample_interval_ms: snap.sample_interval_ms,
cpu_iowait_supported: snap.capabilities.cpu_iowait,
load_supported: snap.capabilities.load_average,
swap_supported: snap.capabilities.swap,
commit_supported: snap.capabilities.memory_commit,
system: snap.system.clone(),
logical_cores: snap.cpu.logical_cores,
usage_pct: snap.cpu.usage_pct,
iowait_pct: snap.cpu.iowait_pct,
load: snap.load,
memory: snap.memory,
swap: snap.swap.as_ref().map(|s| SwapMetrics {
used_bytes: s.used_bytes,
total_bytes: s.total_bytes,
usage_pct: s.usage_pct,
}),
commit: snap.commit.as_ref().map(|c| CommitMetrics {
used_bytes: c.used_bytes,
limit_bytes: c.limit_bytes,
usage_pct: c.usage_pct,
}),
drives,
}
}
}
pub fn aggregate_drives(drives: &[NormalizedDrive]) -> Option<DriveAggregate> {
if drives.is_empty() {
return None;
}
let mut used_bytes: u64 = 0;
let mut total_bytes: u64 = 0;
let mut available_bytes: u64 = 0;
for drive in drives {
if drive.total_bytes == 0 || drive.used_bytes > drive.total_bytes {
return None;
}
let available = drive
.available_bytes
.unwrap_or(drive.total_bytes - drive.used_bytes);
if available > drive.total_bytes {
return None;
}
used_bytes = used_bytes.checked_add(drive.used_bytes)?;
total_bytes = total_bytes.checked_add(drive.total_bytes)?;
available_bytes = available_bytes.checked_add(available)?;
}
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let usage_pct = ((used_bytes as f64) * 100.0 / (total_bytes as f64)) as f32;
Some(DriveAggregate {
used_bytes,
total_bytes,
available_bytes,
usage_pct: usage_pct.clamp(0.0, 100.0),
})
}
#[cfg(test)]
mod tests {
use super::*;
use gregg_protocol::test_support::{LinuxSnapshotBuilder, MacosSnapshotBuilder};
use gregg_protocol::v2::SCHEMA_VERSION_V2;
fn drive(name: &str, used_bytes: u64, total_bytes: u64) -> NormalizedDrive {
NormalizedDrive {
name: name.into(),
used_bytes,
total_bytes,
available_bytes: None,
}
}
#[test]
fn from_v1_preserves_load_and_swap() {
let snap = LinuxSnapshotBuilder::default().build();
let norm = NormalizedSnapshot::from_v1(&snap);
assert_eq!(norm.wire_version, gregg_protocol::SCHEMA_VERSION_V1);
assert!(norm.load.is_some());
assert!(norm.swap.is_some());
assert!(!norm.commit_supported);
}
#[test]
fn from_v1_macos_iowait_unsupported() {
let snap = MacosSnapshotBuilder::default().build();
let norm = NormalizedSnapshot::from_v1(&snap);
assert!(!norm.cpu_iowait_supported);
assert!(norm.iowait_pct.is_none());
}
#[test]
fn from_v2_linux_preserves_optional_fields() {
let snap = gregg_protocol::test_support::LinuxSnapshotV2Builder::default().build();
let norm = NormalizedSnapshot::from_v2(&snap);
assert_eq!(norm.wire_version, SCHEMA_VERSION_V2);
assert!(norm.load.is_some());
assert!(norm.swap.is_some());
assert!(!norm.commit_supported);
assert!(norm.cpu_iowait_supported);
}
#[test]
fn from_v2_windows_has_commit_no_swap_or_load() {
let snap = gregg_protocol::test_support::WindowsSnapshotV2Builder::default().build();
let norm = NormalizedSnapshot::from_v2(&snap);
assert_eq!(norm.wire_version, SCHEMA_VERSION_V2);
assert!(!norm.load_supported);
assert!(!norm.swap_supported);
assert!(norm.commit_supported);
assert!(norm.load.is_none());
assert!(norm.swap.is_none());
assert!(norm.commit.is_some());
}
#[test]
fn v1_and_old_v2_have_unavailable_drives() {
let v1 = NormalizedSnapshot::from_v1(&LinuxSnapshotBuilder::default().build());
assert!(v1.drives.is_none());
let v2 = NormalizedSnapshot::from_v2_payload(
&gregg_protocol::test_support::LinuxSnapshotV2Builder::default().build_payload(),
);
assert!(v2.drives.is_none());
}
#[test]
fn v2_drive_order_and_empty_state_are_preserved() {
let payload = gregg_protocol::test_support::LinuxSnapshotV2Builder::default()
.drives(Some(vec![
gregg_protocol::v2::DriveMetrics {
name: "/".into(),
used_bytes: 1,
total_bytes: 2,
available_bytes: None,
},
gregg_protocol::v2::DriveMetrics {
name: "/home".into(),
used_bytes: 3,
total_bytes: 4,
available_bytes: None,
},
]))
.build_payload();
let norm = NormalizedSnapshot::from_v2_payload(&payload);
assert_eq!(
norm.drives
.as_ref()
.unwrap()
.iter()
.map(|d| d.name.as_str())
.collect::<Vec<_>>(),
vec!["/", "/home"]
);
let empty = gregg_protocol::test_support::LinuxSnapshotV2Builder::default()
.drives(Some(Vec::new()))
.build_payload();
assert_eq!(
NormalizedSnapshot::from_v2_payload(&empty).drives,
Some(Vec::new())
);
}
#[test]
fn aggregate_drives_computes_exact_totals() {
let aggregate = aggregate_drives(&[drive("/", 2, 10), drive("/home", 3, 20)]).unwrap();
assert_eq!(aggregate.used_bytes, 5);
assert_eq!(aggregate.total_bytes, 30);
assert_eq!(aggregate.available_bytes, 25);
assert!((aggregate.usage_pct - 16.666_666).abs() < 0.0001);
}
#[test]
fn aggregate_drives_sums_explicit_availability_independently() {
let aggregate = aggregate_drives(&[
NormalizedDrive {
name: "/".into(),
used_bytes: 6,
total_bytes: 10,
available_bytes: Some(2),
},
NormalizedDrive {
name: "/home".into(),
used_bytes: 3,
total_bytes: 10,
available_bytes: Some(4),
},
])
.unwrap();
assert_eq!(aggregate.used_bytes, 9);
assert_eq!(aggregate.total_bytes, 20);
assert_eq!(aggregate.available_bytes, 6);
}
#[test]
fn aggregate_drives_rejects_empty_invalid_and_overflowing_input() {
assert!(aggregate_drives(&[]).is_none());
assert!(aggregate_drives(&[drive("/", 0, 0)]).is_none());
assert!(aggregate_drives(&[drive("/", 2, 1)]).is_none());
assert!(
aggregate_drives(&[drive("/", u64::MAX, u64::MAX), drive("/home", 0, 1),]).is_none()
);
}
}