use core::mem::size_of;
use crate::model::{MeasuredValue, MetricState, ProcessIdentity, ProcessSnapshot};
use crate::units::{
ByteUnits, Ellipsis, Percent, Rate, format_byte_rate, format_bytes, truncate_middle,
truncate_tail,
};
use super::{most_representative, propagate_unavailable};
pub const MAX_RETAINED_NAME_WIDTH: usize = 24;
pub const MAX_RETAINED_COMMAND_WIDTH: usize = 64;
pub(super) const MAX_RETAINED_TEXT_BYTES: usize =
(MAX_RETAINED_NAME_WIDTH + MAX_RETAINED_COMMAND_WIDTH) * 4;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ContributorMetric {
Cpu,
ResidentMemory,
DiskRead,
DiskWrite,
}
impl ContributorMetric {
pub const ALL: [Self; 4] = [
Self::Cpu,
Self::ResidentMemory,
Self::DiskRead,
Self::DiskWrite,
];
pub const COUNT: usize = Self::ALL.len();
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Cpu => "CPU",
Self::ResidentMemory => "MEM",
Self::DiskRead => "READ",
Self::DiskWrite => "WRITE",
}
}
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::Cpu => "top CPU contributors",
Self::ResidentMemory => "top resident-memory contributors",
Self::DiskRead => "top disk readers",
Self::DiskWrite => "top disk writers",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ContributorTrend {
Points(f32),
Bytes(i64),
ByteRate(f64),
}
impl ContributorTrend {
#[must_use]
pub fn render(self, units: ByteUnits) -> String {
match self {
Self::Points(points) => format!("{points:+.0}%"),
Self::Bytes(bytes) => {
format!(
"{}{}",
sign(bytes < 0),
format_bytes(bytes.unsigned_abs(), units)
)
}
Self::ByteRate(per_second) => match Rate::new(per_second.abs()) {
Some(rate) => format!(
"{}{}",
sign(per_second < 0.0),
format_byte_rate(rate, units)
),
None => "n/a".to_owned(),
},
}
}
}
const fn sign(negative: bool) -> char {
if negative { '-' } else { '+' }
}
#[derive(Clone, Debug, PartialEq)]
pub struct Contributor {
pub identity: ProcessIdentity,
pub name: Box<str>,
pub command: Box<str>,
pub value: MeasuredValue,
pub trend: MetricState<ContributorTrend>,
}
impl Contributor {
#[must_use]
pub fn heap_bytes(&self) -> usize {
self.name.len() + self.command.len()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricContributors {
metric: ContributorMetric,
entries: Vec<Contributor>,
coverage: MetricState<Percent>,
}
impl MetricContributors {
#[must_use]
pub const fn warming_up(metric: ContributorMetric) -> Self {
Self {
metric,
entries: Vec::new(),
coverage: MetricState::WarmingUp,
}
}
#[must_use]
pub const fn metric(&self) -> ContributorMetric {
self.metric
}
#[must_use]
pub fn entries(&self) -> &[Contributor] {
&self.entries
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub const fn coverage(&self) -> MetricState<Percent> {
self.coverage
}
#[must_use]
pub fn heap_bytes(&self) -> usize {
self.entries.capacity() * size_of::<Contributor>()
+ self
.entries
.iter()
.map(Contributor::heap_bytes)
.sum::<usize>()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ContributorSet {
cpu: MetricContributors,
memory: MetricContributors,
disk_read: MetricContributors,
disk_write: MetricContributors,
}
impl ContributorSet {
#[must_use]
pub const fn warming_up() -> Self {
Self {
cpu: MetricContributors::warming_up(ContributorMetric::Cpu),
memory: MetricContributors::warming_up(ContributorMetric::ResidentMemory),
disk_read: MetricContributors::warming_up(ContributorMetric::DiskRead),
disk_write: MetricContributors::warming_up(ContributorMetric::DiskWrite),
}
}
#[must_use]
pub const fn max_retained(top_k: usize) -> usize {
top_k.saturating_mul(ContributorMetric::COUNT)
}
#[must_use]
pub fn from_processes(
processes: &[ProcessSnapshot],
previous: Option<&Self>,
top_k: usize,
) -> Self {
Self {
cpu: select(processes, ContributorMetric::Cpu, top_k, previous),
memory: select(
processes,
ContributorMetric::ResidentMemory,
top_k,
previous,
),
disk_read: select(processes, ContributorMetric::DiskRead, top_k, previous),
disk_write: select(processes, ContributorMetric::DiskWrite, top_k, previous),
}
}
#[must_use]
pub const fn metric(&self, metric: ContributorMetric) -> &MetricContributors {
match metric {
ContributorMetric::Cpu => &self.cpu,
ContributorMetric::ResidentMemory => &self.memory,
ContributorMetric::DiskRead => &self.disk_read,
ContributorMetric::DiskWrite => &self.disk_write,
}
}
#[must_use]
pub fn retained_count(&self) -> usize {
ContributorMetric::ALL
.iter()
.map(|metric| self.metric(*metric).len())
.sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.retained_count() == 0
}
#[must_use]
pub fn heap_bytes(&self) -> usize {
ContributorMetric::ALL
.iter()
.map(|metric| self.metric(*metric).heap_bytes())
.sum()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Observed {
scalar: f64,
value: MeasuredValue,
}
#[derive(Clone, Copy, Debug)]
struct Ranked {
identity: ProcessIdentity,
index: usize,
observed: Observed,
}
fn observe(process: &ProcessSnapshot, metric: ContributorMetric) -> MetricState<Observed> {
match metric {
ContributorMetric::Cpu => process.cpu.map(|percent| Observed {
scalar: f64::from(percent.value()),
value: MeasuredValue::Percent(percent),
}),
ContributorMetric::ResidentMemory => process.memory.rss_bytes.map(|bytes| Observed {
scalar: bytes as f64,
value: MeasuredValue::Bytes(bytes),
}),
ContributorMetric::DiskRead => process.io.read.map(|rate| Observed {
scalar: rate.per_second(),
value: MeasuredValue::ByteRate(rate),
}),
ContributorMetric::DiskWrite => process.io.write.map(|rate| Observed {
scalar: rate.per_second(),
value: MeasuredValue::ByteRate(rate),
}),
}
}
fn trend_between(current: MeasuredValue, previous: MeasuredValue) -> Option<ContributorTrend> {
match (current, previous) {
(MeasuredValue::Percent(now), MeasuredValue::Percent(before)) => {
Some(ContributorTrend::Points(now.points_from(before)))
}
(MeasuredValue::Bytes(now), MeasuredValue::Bytes(before)) => {
let delta = i128::from(now) - i128::from(before);
i64::try_from(delta).ok().map(ContributorTrend::Bytes)
}
(MeasuredValue::ByteRate(now), MeasuredValue::ByteRate(before)) => {
Some(ContributorTrend::ByteRate(now.delta_from(before)))
}
_ => None,
}
}
fn previous_value(
previous: Option<&ContributorSet>,
metric: ContributorMetric,
identity: ProcessIdentity,
) -> Option<MeasuredValue> {
previous?
.metric(metric)
.entries()
.iter()
.find(|entry| entry.identity == identity)
.map(|entry| entry.value)
}
fn select(
processes: &[ProcessSnapshot],
metric: ContributorMetric,
top_k: usize,
previous: Option<&ContributorSet>,
) -> MetricContributors {
let mut ranked: Vec<Ranked> = Vec::new();
let mut observed_total = 0.0f64;
let mut fallback: Option<MetricState<Observed>> = None;
for (index, process) in processes.iter().enumerate() {
let state = observe(process, metric);
match state {
MetricState::Available(observed) => {
observed_total += observed.scalar;
ranked.push(Ranked {
identity: process.identity,
index,
observed,
});
}
other => fallback = Some(most_representative(fallback, other)),
}
}
ranked.sort_unstable_by(|left, right| {
right
.observed
.scalar
.total_cmp(&left.observed.scalar)
.then_with(|| left.identity.cmp(&right.identity))
});
let mut entries: Vec<Contributor> = Vec::with_capacity(top_k.min(ranked.len()));
let mut retained_total = 0.0f64;
for candidate in &ranked {
if entries.len() >= top_k {
break;
}
if entries
.iter()
.any(|entry| entry.identity == candidate.identity)
{
continue;
}
let Some(process) = processes.get(candidate.index) else {
continue;
};
let trend = previous_value(previous, metric, candidate.identity)
.and_then(|before| trend_between(candidate.observed.value, before))
.map_or(MetricState::WarmingUp, MetricState::Available);
retained_total += candidate.observed.scalar;
entries.push(Contributor {
identity: candidate.identity,
name: truncate_tail(&process.name, MAX_RETAINED_NAME_WIDTH, Ellipsis::Ascii)
.into_boxed_str(),
command: truncate_middle(
process.command_or_name(),
MAX_RETAINED_COMMAND_WIDTH,
Ellipsis::Ascii,
)
.into_boxed_str(),
value: candidate.observed.value,
trend,
});
}
let share = coverage(ranked.is_empty(), observed_total, retained_total, fallback);
MetricContributors {
metric,
entries,
coverage: share,
}
}
fn coverage(
nothing_observed: bool,
observed_total: f64,
retained_total: f64,
fallback: Option<MetricState<Observed>>,
) -> MetricState<Percent> {
if nothing_observed {
return fallback.map_or(MetricState::Unsupported, propagate_unavailable);
}
if observed_total <= 0.0 {
return MetricState::WarmingUp;
}
#[allow(clippy::cast_possible_truncation)]
let share = ((retained_total / observed_total) * 100.0) as f32;
Percent::new(share).map_or(MetricState::WarmingUp, |percent| {
MetricState::Available(percent.clamped_to_100())
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{ProcessIo, ProcessMemory, ProcessState, UnavailableReason};
use core::time::Duration;
fn process(pid: u32, start_key: u64) -> ProcessSnapshot {
ProcessSnapshot {
identity: ProcessIdentity::new(pid, start_key),
parent_pid: Some(1),
name: "proc".into(),
command: "proc".into(),
exe: None,
user: MetricState::Unsupported,
state: ProcessState::Running,
cpu: MetricState::WarmingUp,
memory: ProcessMemory::WARMING_UP,
io: ProcessIo::UNSUPPORTED,
threads: MetricState::Unsupported,
age: MetricState::Unsupported,
started_at: MetricState::Unsupported,
is_kernel_thread: false,
}
}
fn with_cpu(pid: u32, cpu: f32) -> ProcessSnapshot {
let mut process = process(pid, u64::from(pid) * 100);
process.cpu = MetricState::Available(Percent::new(cpu).expect("valid percent"));
process
}
fn with_rss(pid: u32, bytes: u64) -> ProcessSnapshot {
let mut process = process(pid, u64::from(pid) * 100);
process.memory = ProcessMemory {
rss_bytes: MetricState::Available(bytes),
virtual_bytes: MetricState::Unsupported,
share_of_total: MetricState::Unsupported,
};
process
}
fn with_io(pid: u32, read: f64, write: f64) -> ProcessSnapshot {
let mut process = process(pid, u64::from(pid) * 100);
process.io = ProcessIo {
read: MetricState::Available(Rate::new(read).expect("valid rate")),
write: MetricState::Available(Rate::new(write).expect("valid rate")),
read_total_bytes: MetricState::Unsupported,
write_total_bytes: MetricState::Unsupported,
};
process
}
#[test]
fn only_the_top_k_processes_are_retained_per_metric() {
let processes: Vec<ProcessSnapshot> = (1..=50u16)
.map(|pid| with_cpu(u32::from(pid), f32::from(pid)))
.collect();
let set = ContributorSet::from_processes(&processes, None, 3);
let cpu = set.metric(ContributorMetric::Cpu);
assert_eq!(cpu.len(), 3);
let pids: Vec<u32> = cpu.entries().iter().map(|e| e.identity.pid).collect();
assert_eq!(pids, vec![50, 49, 48], "highest CPU first");
}
#[test]
fn the_retained_count_is_bounded_by_k_times_the_metric_count() {
let processes: Vec<ProcessSnapshot> = (1..=500)
.map(|pid| {
let mut process = with_cpu(pid, 1.0);
process.memory = with_rss(pid, u64::from(pid)).memory;
process.io = with_io(pid, f64::from(pid), f64::from(pid)).io;
process
})
.collect();
let set = ContributorSet::from_processes(&processes, None, 10);
assert_eq!(set.retained_count(), ContributorSet::max_retained(10));
assert!(set.retained_count() <= 40);
}
#[test]
fn duplicate_identities_are_collapsed_to_their_largest_reading() {
let mut first = with_cpu(7, 10.0);
first.identity = ProcessIdentity::new(7, 700);
let mut duplicate = with_cpu(7, 90.0);
duplicate.identity = ProcessIdentity::new(7, 700);
let other = with_cpu(8, 50.0);
let set = ContributorSet::from_processes(&[first, duplicate, other], None, 10);
let cpu = set.metric(ContributorMetric::Cpu);
assert_eq!(cpu.len(), 2, "the duplicate must not take a second slot");
let entry = cpu.entries().first().expect("a contributor was retained");
assert_eq!(entry.identity, ProcessIdentity::new(7, 700));
assert_eq!(
entry.value,
MeasuredValue::Percent(Percent::new(90.0).expect("valid"))
);
}
#[test]
fn a_reused_pid_is_a_different_contributor() {
let original = with_cpu(31_842, 90.0);
let mut recycled = with_cpu(31_842, 10.0);
recycled.identity = ProcessIdentity::new(31_842, 999_999);
let set = ContributorSet::from_processes(&[original, recycled], None, 10);
assert_eq!(
set.metric(ContributorMetric::Cpu).len(),
2,
"the same PID with a different start key is a different process"
);
}
#[test]
fn coverage_is_the_share_of_the_observed_total() {
let processes = vec![
with_cpu(1, 90.0),
with_cpu(2, 10.0),
with_cpu(3, 50.0),
with_cpu(4, 50.0),
];
let set = ContributorSet::from_processes(&processes, None, 2);
let coverage = set
.metric(ContributorMetric::Cpu)
.coverage()
.fresh()
.copied()
.expect("coverage is available");
assert!((coverage.value() - 70.0).abs() < 0.01, "got {coverage}");
}
#[test]
fn full_coverage_is_reported_when_every_observed_process_is_retained() {
let processes = vec![with_cpu(1, 30.0), with_cpu(2, 70.0)];
let set = ContributorSet::from_processes(&processes, None, 10);
let coverage = set
.metric(ContributorMetric::Cpu)
.coverage()
.fresh()
.copied()
.expect("coverage is available");
assert!((coverage.value() - 100.0).abs() < 0.01, "got {coverage}");
}
#[test]
fn coverage_is_unavailable_when_no_total_was_observed() {
let processes = vec![process(1, 100), process(2, 200)];
let set = ContributorSet::from_processes(&processes, None, 10);
let io = set.metric(ContributorMetric::DiskRead);
assert!(io.is_empty());
assert_eq!(io.coverage(), MetricState::Unsupported);
assert!(io.coverage().fresh().is_none());
let cpu = set.metric(ContributorMetric::Cpu);
assert_eq!(cpu.coverage(), MetricState::WarmingUp);
}
#[test]
fn coverage_reports_permission_denied_rather_than_a_flattering_number() {
let mut denied = process(1, 100);
denied.io = ProcessIo {
read: MetricState::PermissionDenied,
write: MetricState::PermissionDenied,
read_total_bytes: MetricState::PermissionDenied,
write_total_bytes: MetricState::PermissionDenied,
};
let set = ContributorSet::from_processes(&[denied], None, 10);
assert_eq!(
set.metric(ContributorMetric::DiskRead).coverage(),
MetricState::PermissionDenied
);
}
#[test]
fn coverage_of_an_all_zero_total_is_warming_up_not_a_share_of_nothing() {
let processes = vec![with_cpu(1, 0.0), with_cpu(2, 0.0)];
let set = ContributorSet::from_processes(&processes, None, 10);
assert_eq!(
set.metric(ContributorMetric::Cpu).coverage(),
MetricState::WarmingUp
);
}
#[test]
fn coverage_of_an_empty_process_table_is_unavailable() {
let set = ContributorSet::from_processes(&[], None, 10);
assert!(set.is_empty());
for metric in ContributorMetric::ALL {
assert_eq!(set.metric(metric).coverage(), MetricState::Unsupported);
}
}
#[test]
fn a_first_appearance_has_a_warming_up_trend_not_a_zero_delta() {
let set = ContributorSet::from_processes(&[with_cpu(1, 42.0)], None, 10);
let entry = set
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
assert_eq!(entry.trend, MetricState::WarmingUp);
assert!(entry.trend.fresh().is_none());
}
#[test]
fn a_trend_is_the_change_against_the_previous_retained_value() {
let before = ContributorSet::from_processes(&[with_cpu(1, 141.0)], None, 10);
let after = ContributorSet::from_processes(&[with_cpu(1, 287.0)], Some(&before), 10);
let entry = after
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
match entry.trend.fresh().copied().expect("trend is available") {
ContributorTrend::Points(points) => {
assert!((points - 146.0).abs() < 0.01, "got {points}");
}
other => panic!("expected percentage points, got {other:?}"),
}
}
#[test]
fn a_reused_pid_does_not_inherit_the_previous_processes_trend() {
let before = ContributorSet::from_processes(&[with_cpu(31_842, 300.0)], None, 10);
let mut recycled = with_cpu(31_842, 1.0);
recycled.identity = ProcessIdentity::new(31_842, 999_999);
let after = ContributorSet::from_processes(&[recycled], Some(&before), 10);
let entry = after
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
assert_eq!(
entry.trend,
MetricState::WarmingUp,
"a delta across a PID reuse would be a fabricated -299 points"
);
}
#[test]
fn memory_trends_are_signed_byte_deltas_and_io_trends_are_rate_deltas() {
let before = ContributorSet::from_processes(
&[{
let mut p = with_rss(1, 8 * 1024 * 1024);
p.io = with_io(1, 3_000_000.0, 0.0).io;
p
}],
None,
10,
);
let after = ContributorSet::from_processes(
&[{
let mut p = with_rss(1, 4 * 1024 * 1024);
p.io = with_io(1, 42_000_000.0, 0.0).io;
p
}],
Some(&before),
10,
);
let memory = after
.metric(ContributorMetric::ResidentMemory)
.entries()
.first()
.expect("retained");
assert_eq!(
memory.trend.fresh().copied(),
Some(ContributorTrend::Bytes(-4 * 1024 * 1024))
);
let read = after
.metric(ContributorMetric::DiskRead)
.entries()
.first()
.expect("retained");
match read.trend.fresh().copied().expect("available") {
ContributorTrend::ByteRate(delta) => {
assert!((delta - 39_000_000.0).abs() < 1.0, "got {delta}");
}
other => panic!("expected a rate delta, got {other:?}"),
}
}
#[test]
fn a_process_outside_the_previous_top_k_has_no_trend_to_report() {
let before =
ContributorSet::from_processes(&[with_cpu(1, 90.0), with_cpu(2, 1.0)], None, 1);
let after = ContributorSet::from_processes(
&[with_cpu(1, 10.0), with_cpu(2, 80.0)],
Some(&before),
1,
);
let entry = after
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
assert_eq!(entry.identity.pid, 2);
assert_eq!(entry.trend, MetricState::WarmingUp);
}
#[test]
fn full_command_lines_are_never_retained() {
let mut long = with_cpu(1, 10.0);
long.command = format!("rustc {}", "--extremely-long-argument ".repeat(40)).into();
let set = ContributorSet::from_processes(&[long], None, 10);
let entry = set
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
assert!(
crate::units::display_width(&entry.command) <= MAX_RETAINED_COMMAND_WIDTH,
"retained {:?}",
entry.command
);
assert!(entry.command.contains("..."), "truncation must be visible");
}
#[test]
fn a_kernel_thread_with_no_command_line_retains_its_name() {
let mut kernel = with_cpu(9, 1.0);
kernel.name = "kworker/2:1".into();
kernel.command = "".into();
let set = ContributorSet::from_processes(&[kernel], None, 10);
let entry = set
.metric(ContributorMetric::Cpu)
.entries()
.first()
.expect("retained");
assert_eq!(&*entry.command, "kworker/2:1");
}
#[test]
fn top_k_of_zero_retains_nothing_but_still_reports_coverage() {
let set = ContributorSet::from_processes(&[with_cpu(1, 50.0)], None, 0);
let cpu = set.metric(ContributorMetric::Cpu);
assert!(cpu.is_empty());
let coverage = cpu.coverage().fresh().copied().expect("available");
assert!((coverage.value() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn trends_render_with_an_explicit_sign() {
assert_eq!(
ContributorTrend::Points(146.0).render(ByteUnits::Iec),
"+146%"
);
assert_eq!(
ContributorTrend::Points(-12.0).render(ByteUnits::Iec),
"-12%"
);
assert_eq!(
ContributorTrend::Bytes(-4 * 1024 * 1024).render(ByteUnits::Iec),
"-4.0 MiB"
);
assert_eq!(
ContributorTrend::ByteRate(39.0 * 1024.0 * 1024.0).render(ByteUnits::Iec),
"+39M/s"
);
}
#[test]
fn a_non_finite_rate_delta_renders_as_unavailable_rather_than_panicking() {
assert_eq!(
ContributorTrend::ByteRate(f64::NAN).render(ByteUnits::Iec),
"n/a"
);
}
#[test]
fn mismatched_measurement_kinds_produce_no_trend() {
assert!(
trend_between(
MeasuredValue::Bytes(1),
MeasuredValue::Percent(Percent::ZERO)
)
.is_none()
);
}
#[test]
fn heap_use_grows_with_the_retained_contributors_only() {
let processes: Vec<ProcessSnapshot> = (1..=200).map(|pid| with_cpu(pid, 1.0)).collect();
let small = ContributorSet::from_processes(&processes, None, 1);
let large = ContributorSet::from_processes(&processes, None, 10);
assert!(small.heap_bytes() < large.heap_bytes());
let per_contributor = size_of::<Contributor>() + MAX_RETAINED_TEXT_BYTES;
assert!(
large.heap_bytes() <= ContributorSet::max_retained(10) * per_contributor,
"heap {} exceeded the budgeted bound",
large.heap_bytes()
);
}
#[test]
fn a_warming_up_set_reports_nothing_measured() {
let set = ContributorSet::warming_up();
assert!(set.is_empty());
assert_eq!(set.heap_bytes(), 0);
for metric in ContributorMetric::ALL {
assert_eq!(set.metric(metric).coverage(), MetricState::WarmingUp);
assert_eq!(set.metric(metric).metric(), metric);
}
}
#[test]
fn a_counter_reset_keeps_a_process_out_of_the_retained_set() {
let mut resetting = process(1, 100);
resetting.io = ProcessIo {
read: MetricState::TemporarilyUnavailable(UnavailableReason::CounterReset),
write: MetricState::TemporarilyUnavailable(UnavailableReason::CounterReset),
read_total_bytes: MetricState::Available(0),
write_total_bytes: MetricState::Available(0),
};
let set = ContributorSet::from_processes(&[resetting], None, 10);
let read = set.metric(ContributorMetric::DiskRead);
assert!(read.is_empty());
assert_eq!(
read.coverage(),
MetricState::TemporarilyUnavailable(UnavailableReason::CounterReset)
);
}
#[test]
fn a_stale_reading_is_not_counted_towards_the_observed_total() {
let mut stale = process(1, 100);
stale.cpu = MetricState::Available(Percent::new(50.0).expect("valid"))
.into_stale(Duration::from_secs(2));
let set = ContributorSet::from_processes(&[stale], None, 10);
let cpu = set.metric(ContributorMetric::Cpu);
assert!(cpu.is_empty(), "a stale value must not feed a calculation");
assert_eq!(
cpu.coverage(),
MetricState::TemporarilyUnavailable(UnavailableReason::NeedsSecondSample)
);
}
}