use std::path::Path;
use gregg_protocol::{MemoryMetrics, MetricCapabilities, StatusSnapshot};
use super::cpu::{parse_proc_stat, CpuCounters};
use super::fixtures::read_fixture;
use super::identity::collect_identity;
use super::memory::{compute_memory, compute_swap, parse_meminfo};
use super::source::{MemorySource, ProcSource};
use super::{compute_percentages, parse_loadavg, LinuxCollector};
use crate::collector::error::CollectErrorKind;
use crate::collector::SystemCollector;
fn source_from(
fixtures: &[(&str, &str)],
cores: usize,
os_release_fixture: Option<&str>,
) -> ProcSource {
let mut mem = MemorySource::new().with_logical_cores(cores);
for (fixture_name, path) in fixtures {
mem = mem.with_file(Path::new(path), read_fixture(fixture_name));
}
if !mem.has_file("/proc/sys/kernel/hostname") {
mem = mem.with_file(Path::new("/proc/sys/kernel/hostname"), "test-host\n");
}
let mut source = ProcSource::for_memory(mem);
if let Some(name) = os_release_fixture {
let path = Path::new("/etc/os-release").to_path_buf();
source
.memory_source_mut()
.expect("memory source")
.add_file(&path, read_fixture(name));
source = source.with_os_release_path(path);
}
source
}
#[test]
fn parses_aggregate_cpu_row() {
let raw = read_fixture("ubuntu_x86_64_proc_stat_a.txt");
let parsed = parse_proc_stat(&raw).expect("parses");
let aggregate = parsed.aggregate.expect("aggregate present");
assert_eq!(aggregate.user, 100);
assert_eq!(aggregate.nice, 0);
assert_eq!(aggregate.system, 50);
assert_eq!(aggregate.idle, 8000);
assert_eq!(aggregate.iowait, 30);
assert_eq!(aggregate.irq, 5);
assert_eq!(aggregate.softirq, 2);
assert_eq!(aggregate.steal, 1);
}
#[test]
fn rejects_aggregate_row_with_too_few_fields() {
let raw = read_fixture("malformed_proc_stat_too_few_fields.txt");
let err = parse_proc_stat(&raw).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
assert!(err.message.contains("expected at least"));
}
#[test]
fn rejects_non_numeric_cpu_field() {
let raw = read_fixture("malformed_proc_stat_non_numeric.txt");
let err = parse_proc_stat(&raw).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
}
#[test]
fn parses_meminfo_with_all_fields() {
let raw = read_fixture("ubuntu_x86_64_proc_meminfo.txt");
let parsed = parse_meminfo(&raw).expect("parses");
assert_eq!(parsed.mem_total_kb, Some(16_384_000));
assert_eq!(parsed.mem_available_kb, Some(10_240_000));
assert_eq!(parsed.swap_total_kb, Some(4_096_000));
assert_eq!(parsed.swap_free_kb, Some(4_096_000));
}
#[test]
fn rejects_meminfo_missing_memtotal() {
let raw = read_fixture("malformed_proc_meminfo_missing_total.txt");
let parsed = parse_meminfo(&raw).expect("parses");
let err = compute_memory(&parsed).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
assert!(err.message.contains("MemTotal"));
}
#[test]
fn rejects_meminfo_with_available_exceeding_total() {
let raw = read_fixture("malformed_proc_meminfo_available_exceeds_total.txt");
let parsed = parse_meminfo(&raw).expect("parses");
let err = compute_memory(&parsed).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Numeric);
}
#[test]
#[allow(clippy::float_cmp)]
fn zero_swap_is_handled_without_panic() {
let raw = read_fixture("zero_swap_proc_meminfo.txt");
let parsed = parse_meminfo(&raw).expect("parses");
let swap = compute_swap(&parsed).expect("zero swap parses");
assert_eq!(swap.total_bytes, 0);
assert_eq!(swap.used_bytes, 0);
let metrics = swap.into_metrics();
assert_eq!(metrics.usage_pct, 0.0);
}
#[test]
fn memory_fallback_used_when_memavailable_missing() {
let raw = read_fixture("missing_mem_available_proc_meminfo.txt");
let parsed = parse_meminfo(&raw).expect("parses");
let mem = compute_memory(&parsed).expect("fallback computes");
assert!(mem.fallback_used);
let expected_available_bytes = 9_700_000 * 1024;
let expected_used_bytes = 16_384_000 * 1024 - expected_available_bytes;
assert_eq!(mem.used_bytes, expected_used_bytes);
}
#[test]
fn parses_loadavg_three_floats() {
let raw = read_fixture("ubuntu_x86_64_proc_loadavg.txt");
let parsed = parse_loadavg(&raw).expect("parses");
assert!((parsed.one - 1.32_f32).abs() < 1e-6);
assert!((parsed.five - 0.91_f32).abs() < 1e-6);
assert!((parsed.fifteen - 0.62_f32).abs() < 1e-6);
}
#[test]
fn rejects_malformed_loadavg_too_few_fields() {
let raw = read_fixture("malformed_proc_loadavg_too_few_fields.txt");
let err = parse_loadavg(&raw).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
}
#[test]
fn rejects_non_numeric_loadavg_field() {
let raw = read_fixture("malformed_proc_loadavg_non_numeric.txt");
let err = parse_loadavg(&raw).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
}
#[test]
fn rejects_negative_loadavg_value() {
let raw = read_fixture("malformed_proc_loadavg_negative.txt");
let err = parse_loadavg(&raw).expect_err("must fail");
assert_eq!(err.kind, CollectErrorKind::Parse);
}
#[test]
fn counter_delta_matches_hand_calculated_fixture() {
let prev = CpuCounters {
user: 100,
nice: 0,
system: 50,
idle: 8000,
iowait: 30,
irq: 5,
softirq: 2,
steal: 1,
};
let curr = CpuCounters {
user: 150,
nice: 0,
system: 60,
idle: 8050,
iowait: 50,
irq: 6,
softirq: 3,
steal: 1,
};
let sample = compute_percentages(&prev, &curr).expect("computes");
assert!((sample.usage_pct - 46.969_7_f32).abs() < 1e-3);
assert!((sample.iowait_pct - 15.151_5_f32).abs() < 1e-3);
}
#[test]
fn first_sample_is_warming() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let err = collector.sample().expect_err("first sample warms");
assert_eq!(err.kind, CollectErrorKind::Warming);
}
#[test]
fn warming_then_valid_sample_produces_protocol_snapshot() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
8,
Some("ubuntu_x86_64_os_release.txt"),
),
Some("deadpool"),
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("ubuntu_x86_64_proc_stat_b.txt"));
let metrics = collector.sample().expect("second sample succeeds");
let identity = collector.identity().expect("identity");
let snap: StatusSnapshot = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates");
assert!(snap.capabilities.cpu_iowait);
assert!(snap.cpu.iowait_pct.is_some());
assert!(snap.system.name.contains("deadpool"));
assert_eq!(snap.system.os_name, "Ubuntu 24.04 LTS");
assert_eq!(snap.cpu.logical_cores, 8);
assert!((snap.cpu.usage_pct - 46.969_7_f32).abs() < 1e-3);
assert!((snap.cpu.iowait_pct.expect("iowait") - 15.151_5_f32).abs() < 1e-3);
}
#[test]
fn counter_reset_is_reported_as_typed_error() {
let prev = CpuCounters {
user: 1000,
nice: 0,
system: 500,
idle: 80_000,
iowait: 300,
irq: 50,
softirq: 20,
steal: 10,
};
let curr = CpuCounters {
user: 100,
nice: 0,
system: 50,
idle: 8000,
iowait: 30,
irq: 5,
softirq: 2,
steal: 1,
};
let err = compute_percentages(&prev, &curr).expect_err("counter reset");
assert_eq!(err.kind, CollectErrorKind::CounterReset);
let mut collector = LinuxCollector::with_source(
source_from(
&[
("cpu_reset_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
8,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector
.sample()
.expect_err("warming baseline established");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("cpu_reset_proc_stat_b.txt"));
let err = collector.sample().expect_err("counter reset reported");
assert_eq!(err.kind, CollectErrorKind::CounterReset);
}
#[test]
fn zero_total_delta_is_typed_error_not_nan() {
let prev = CpuCounters {
user: 100,
nice: 0,
system: 50,
idle: 8000,
iowait: 30,
irq: 5,
softirq: 2,
steal: 1,
};
let curr = prev;
let err = compute_percentages(&prev, &curr).expect_err("zero delta");
assert_eq!(err.kind, CollectErrorKind::CounterReset);
}
#[test]
fn guest_counters_are_not_double_counted() {
let prev = CpuCounters {
user: 1000,
nice: 0,
system: 100,
idle: 8000,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
};
let curr = CpuCounters {
user: 1100,
nice: 0,
system: 100,
idle: 8000,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
};
let sample = compute_percentages(&prev, &curr).expect("computes");
assert!((sample.usage_pct - 100.0_f32).abs() < 1e-3);
}
#[test]
fn high_memory_counters_do_not_overflow() {
let raw_a = read_fixture("high_memory_proc_stat_a.txt");
let raw_b = read_fixture("high_memory_proc_stat_b.txt");
let prev = parse_proc_stat(&raw_a)
.expect("parses a")
.aggregate
.expect("aggregate a");
let curr = parse_proc_stat(&raw_b)
.expect("parses b")
.aggregate
.expect("aggregate b");
let sample = compute_percentages(&prev, &curr).expect("computes");
assert!(sample.usage_pct.is_finite());
assert!(sample.iowait_pct.is_finite());
assert!((0.0..=100.0).contains(&sample.usage_pct));
assert!((0.0..=100.0).contains(&sample.iowait_pct));
}
#[test]
fn arm64_fixture_produces_protocol_snapshot() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("arm64_proc_stat_a.txt", "/proc/stat"),
("arm64_proc_loadavg.txt", "/proc/loadavg"),
("arm64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("arm64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("arm64_proc_stat_b.txt"));
let metrics = collector.sample().expect("second sample succeeds");
let identity = collector.identity().expect("identity");
let snap = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates");
assert_eq!(snap.cpu.logical_cores, 4);
assert!((snap.cpu.usage_pct - 69.135_8_f32).abs() < 1e-3);
assert!((snap.cpu.iowait_pct.expect("iowait") - 12.345_68_f32).abs() < 1e-3);
}
#[test]
#[allow(clippy::float_cmp)]
fn zero_swap_host_produces_valid_snapshot() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("zero_swap_proc_stat_a.txt", "/proc/stat"),
("zero_swap_proc_loadavg.txt", "/proc/loadavg"),
("zero_swap_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("zero_swap_proc_stat_b.txt"));
let metrics = collector.sample().expect("second sample succeeds");
let identity = collector.identity().expect("identity");
let snap = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates");
assert_eq!(snap.swap.used_bytes, 0);
assert_eq!(snap.swap.total_bytes, 0);
assert_eq!(snap.swap.usage_pct, 0.0);
}
#[test]
fn identity_uses_pretty_name_when_present() {
let source = source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
8,
Some("ubuntu_x86_64_os_release.txt"),
);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "Ubuntu 24.04 LTS");
assert_eq!(identity.os_version, "24.04 LTS (Noble Numbat)");
}
#[test]
fn identity_falls_back_to_name_when_pretty_missing() {
let source = source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("minimal_os_release.txt"),
);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "TinyLinux");
assert_eq!(identity.os_version, "0.1");
}
#[test]
fn identity_decodes_escaped_quotes() {
let source = source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("escaped_os_release.txt"),
);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "ExoticOS \"Hardened\" Edition");
}
#[test]
fn identity_uses_generic_linux_when_os_release_missing() {
let source = source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
None,
);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "linux");
assert_eq!(identity.os_version, "unknown");
}
#[test]
fn capabilities_mark_iowait_supported() {
let collector = LinuxCollector::with_source(
source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let caps = collector.capabilities();
assert!(caps.cpu_iowait);
}
#[test]
fn repeated_samples_show_no_unbounded_growth() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
8,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
for _ in 0..1000 {
let _ = collector.sample();
}
}
#[test]
fn nondecreasing_counters_yield_finite_percentages() {
let cases: &[(CpuCounters, CpuCounters)] = &[
(
CpuCounters {
user: 0,
nice: 0,
system: 0,
idle: 0,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
},
CpuCounters {
user: 100,
nice: 0,
system: 0,
idle: 0,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
},
),
(
CpuCounters {
user: 1,
nice: 0,
system: 0,
idle: 1,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
},
CpuCounters {
user: 9,
nice: 0,
system: 0,
idle: 1,
iowait: 0,
irq: 0,
softirq: 0,
steal: 0,
},
),
(
CpuCounters {
user: 100,
nice: 0,
system: 50,
idle: 8000,
iowait: 30,
irq: 5,
softirq: 2,
steal: 1,
},
CpuCounters {
user: 150,
nice: 0,
system: 60,
idle: 8050,
iowait: 50,
irq: 6,
softirq: 3,
steal: 1,
},
),
];
for (prev, curr) in cases {
let sample = compute_percentages(prev, curr).expect("computes");
assert!(sample.usage_pct.is_finite());
assert!(sample.iowait_pct.is_finite());
assert!((0.0..=100.0).contains(&sample.usage_pct));
assert!((0.0..=100.0).contains(&sample.iowait_pct));
}
}
#[test]
fn used_memory_never_exceeds_total_after_normalization() {
let cases: &[&str] = &[
"ubuntu_x86_64_proc_meminfo.txt",
"arm64_proc_meminfo.txt",
"zero_swap_proc_meminfo.txt",
"high_memory_proc_meminfo.txt",
"missing_mem_available_proc_meminfo.txt",
];
for name in cases {
let raw = read_fixture(name);
let parsed = parse_meminfo(&raw).expect("parses");
let mem = compute_memory(&parsed).expect("computes");
assert!(mem.used_bytes <= mem.total_bytes, "fixture {name}");
let metrics: MemoryMetrics = mem.into_metrics();
assert!(metrics.used_bytes <= metrics.total_bytes);
}
}
#[test]
fn swap_used_never_exceeds_total_after_normalization() {
let cases: &[&str] = &[
"ubuntu_x86_64_proc_meminfo.txt",
"arm64_proc_meminfo.txt",
"zero_swap_proc_meminfo.txt",
"high_memory_proc_meminfo.txt",
"missing_mem_available_proc_meminfo.txt",
];
for name in cases {
let raw = read_fixture(name);
let parsed = parse_meminfo(&raw).expect("parses");
let swap = compute_swap(&parsed).expect("computes");
assert!(swap.used_bytes <= swap.total_bytes, "fixture {name}");
}
}
#[test]
fn loadavg_is_never_negative_or_nan() {
let samples: &[&str] = &[
"ubuntu_x86_64_proc_loadavg.txt",
"arm64_proc_loadavg.txt",
"zero_swap_proc_loadavg.txt",
"high_memory_proc_loadavg.txt",
];
for name in samples {
let parsed = parse_loadavg(&read_fixture(name)).expect("parses");
assert!(parsed.one.is_finite() && parsed.one >= 0.0);
assert!(parsed.five.is_finite() && parsed.five >= 0.0);
assert!(parsed.fifteen.is_finite() && parsed.fifteen >= 0.0);
}
}
#[test]
fn container_minimal_meminfo_uses_fallback() {
let raw = read_fixture("container_proc_meminfo.txt");
let parsed = parse_meminfo(&raw).expect("parses");
let mem = compute_memory(&parsed).expect("fallback computes");
assert!(mem.fallback_used);
assert!(mem.used_bytes <= mem.total_bytes);
}
#[test]
fn container_collector_produces_valid_snapshot() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("container_proc_stat_a.txt", "/proc/stat"),
("container_proc_loadavg.txt", "/proc/loadavg"),
("container_proc_meminfo.txt", "/proc/meminfo"),
],
2,
None, ),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("container_proc_stat_b.txt"));
let metrics = collector.sample().expect("second sample succeeds");
let identity = collector.identity().expect("identity");
let snap: StatusSnapshot = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates");
assert_eq!(snap.cpu.logical_cores, 2);
assert_eq!(snap.system.os_name, "linux");
assert_eq!(snap.system.os_version, "unknown");
}
#[test]
fn very_large_uptime_counters_produce_valid_percentages() {
let raw_a = read_fixture("very_large_uptime_proc_stat_a.txt");
let raw_b = read_fixture("very_large_uptime_proc_stat_b.txt");
let prev = parse_proc_stat(&raw_a)
.expect("parses a")
.aggregate
.expect("aggregate a");
let curr = parse_proc_stat(&raw_b)
.expect("parses b")
.aggregate
.expect("aggregate b");
let sample = compute_percentages(&prev, &curr).expect("computes");
assert!(sample.usage_pct.is_finite());
assert!(sample.iowait_pct.is_finite());
assert!((0.0..=100.0).contains(&sample.usage_pct));
assert!((0.0..=100.0).contains(&sample.iowait_pct));
}
#[test]
fn cpu_hotplug_different_core_count() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("hotplug_2core_proc_stat_a.txt", "/proc/stat"),
("container_proc_loadavg.txt", "/proc/loadavg"),
("container_proc_meminfo.txt", "/proc/meminfo"),
],
2,
None,
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("hotplug_4core_proc_stat_b.txt"));
inner.set_logical_cores(4);
let metrics = collector.sample().expect("sample after hotplug succeeds");
assert_eq!(metrics.logical_cores, 4, "core count must be refreshed");
let identity = collector.identity().expect("identity");
let snap = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates after hotplug");
assert_eq!(snap.cpu.logical_cores, 4);
}
#[test]
fn swap_usage_change_between_samples() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("ubuntu_x86_64_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("swap_change_proc_meminfo_a.txt", "/proc/meminfo"),
],
4,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("ubuntu_x86_64_proc_stat_b.txt"));
inner.add_file(
"/proc/meminfo",
read_fixture("swap_change_proc_meminfo_b.txt"),
);
let metrics = collector.sample().expect("sample succeeds");
let identity = collector.identity().expect("identity");
let snap = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate().expect("snapshot validates");
assert!(snap.swap.used_bytes < snap.swap.total_bytes);
}
#[test]
fn identity_empty_os_release_fields() {
let mut mem = MemorySource::new().with_logical_cores(1);
mem.add_file(
Path::new("/etc/os-release"),
"NAME=\"\"\nVERSION=\"\"\nID=\n".to_string(),
);
mem.add_file(Path::new("/proc/sys/kernel/hostname"), "test-host\n");
let source = ProcSource::for_memory(mem);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "");
assert_eq!(identity.os_version, "");
}
#[test]
fn identity_os_release_only_comments() {
let mut mem = MemorySource::new().with_logical_cores(1);
mem.add_file(
Path::new("/etc/os-release"),
"# This is a comment\n# Another comment\n".to_string(),
);
mem.add_file(Path::new("/proc/sys/kernel/hostname"), "test-host\n");
let source = ProcSource::for_memory(mem);
let identity = collect_identity(&source, None).expect("identity");
assert_eq!(identity.os_name, "linux");
assert_eq!(identity.os_version, "unknown");
}
#[test]
fn suspend_resume_counter_jump_produces_valid_snapshot() {
let mut collector = LinuxCollector::with_source(
source_from(
&[
("suspend_resume_proc_stat_a.txt", "/proc/stat"),
("ubuntu_x86_64_proc_loadavg.txt", "/proc/loadavg"),
("ubuntu_x86_64_proc_meminfo.txt", "/proc/meminfo"),
],
4,
Some("ubuntu_x86_64_os_release.txt"),
),
None,
)
.expect("collector constructs");
let _ = collector.sample().expect_err("warming");
let inner = collector
.source_mut()
.memory_source_mut()
.expect("memory source");
inner.add_file("/proc/stat", read_fixture("suspend_resume_proc_stat_b.txt"));
let metrics = collector.sample().expect("sample after resume succeeds");
let identity = collector.identity().expect("identity");
let snap = metrics
.into_snapshot(
gregg_protocol::SCHEMA_VERSION_V1,
1_716_460_800_000,
1000,
MetricCapabilities { cpu_iowait: true },
identity,
)
.expect("into_snapshot succeeds");
snap.validate()
.expect("snapshot validates after suspend/resume");
assert!(snap.cpu.usage_pct.is_finite());
assert!(snap.cpu.iowait_pct.is_some());
assert!((0.0..=100.0).contains(&snap.cpu.usage_pct));
}