#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum CapabilityState {
Available,
Unsupported,
PermissionDenied,
#[default]
Unknown,
}
impl CapabilityState {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Available => "available",
Self::Unsupported => "unsupported",
Self::PermissionDenied => "permission denied",
Self::Unknown => "not probed",
}
}
#[must_use]
pub const fn symbol(self) -> char {
match self {
Self::Available => '+',
Self::Unsupported => '-',
Self::PermissionDenied => '!',
Self::Unknown => '?',
}
}
#[must_use]
pub const fn privileges_might_help(self) -> bool {
matches!(self, Self::PermissionDenied)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CapabilitySnapshot {
pub per_process_io: CapabilityState,
pub per_process_threads: CapabilityState,
pub per_process_open_files: CapabilityState,
pub per_process_sockets: CapabilityState,
pub per_process_working_directory: CapabilityState,
pub per_core_cpu: CapabilityState,
pub cpu_breakdown: CapabilityState,
pub load_average: CapabilityState,
pub swap_activity: CapabilityState,
pub disk_io: CapabilityState,
pub disk_busy: CapabilityState,
pub filesystem_capacity: CapabilityState,
pub network_counters: CapabilityState,
pub network_link_speed: CapabilityState,
pub network_errors: CapabilityState,
pub temperatures: CapabilityState,
pub battery: CapabilityState,
pub linux_psi: CapabilityState,
pub cgroup_limits: CapabilityState,
pub kernel_threads: CapabilityState,
pub process_signals: CapabilityState,
pub renice: CapabilityState,
}
impl CapabilitySnapshot {
pub const COUNT: usize = 22;
#[must_use]
pub fn entries(&self) -> [(&'static str, CapabilityState); Self::COUNT] {
[
("process I/O", self.per_process_io),
("process threads", self.per_process_threads),
("process open files", self.per_process_open_files),
("process sockets", self.per_process_sockets),
(
"process working directory",
self.per_process_working_directory,
),
("per-core CPU", self.per_core_cpu),
("CPU time breakdown", self.cpu_breakdown),
("load average", self.load_average),
("swap activity", self.swap_activity),
("disk I/O", self.disk_io),
("disk busy", self.disk_busy),
("filesystem capacity", self.filesystem_capacity),
("network counters", self.network_counters),
("network link speed", self.network_link_speed),
("network errors", self.network_errors),
("temperatures", self.temperatures),
("battery", self.battery),
("Linux PSI", self.linux_psi),
("cgroup limits", self.cgroup_limits),
("kernel threads", self.kernel_threads),
("process signals", self.process_signals),
("renice", self.renice),
]
}
#[must_use]
pub fn unavailable(&self) -> Vec<(&'static str, CapabilityState)> {
self.entries()
.into_iter()
.filter(|(_, state)| *state != CapabilityState::Available)
.collect()
}
#[must_use]
pub fn any_permission_denied(&self) -> bool {
self.entries()
.into_iter()
.any(|(_, state)| state.privileges_might_help())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entries_covers_every_declared_capability() {
let capabilities = CapabilitySnapshot::default();
assert_eq!(capabilities.entries().len(), CapabilitySnapshot::COUNT);
}
#[test]
fn entry_labels_are_unique_and_ascii() {
let entries = CapabilitySnapshot::default().entries();
let mut labels: Vec<&str> = entries.iter().map(|(label, _)| *label).collect();
assert!(labels.iter().all(|label| label.is_ascii()));
labels.sort_unstable();
labels.dedup();
assert_eq!(
labels.len(),
CapabilitySnapshot::COUNT,
"duplicate capability label"
);
}
#[test]
fn an_unprobed_snapshot_reports_everything_as_unavailable() {
let capabilities = CapabilitySnapshot::default();
assert_eq!(capabilities.unavailable().len(), CapabilitySnapshot::COUNT);
assert!(
!capabilities.any_permission_denied(),
"unknown is not denied"
);
}
#[test]
fn permission_denied_is_distinct_from_unsupported() {
let capabilities = CapabilitySnapshot {
per_process_io: CapabilityState::PermissionDenied,
linux_psi: CapabilityState::Unsupported,
..CapabilitySnapshot::default()
};
assert!(capabilities.any_permission_denied());
assert!(CapabilityState::PermissionDenied.privileges_might_help());
assert!(
!CapabilityState::Unsupported.privileges_might_help(),
"root cannot conjure a kernel feature that does not exist"
);
}
#[test]
fn available_capabilities_drop_out_of_the_unavailable_list() {
let capabilities = CapabilitySnapshot {
per_process_io: CapabilityState::Available,
..CapabilitySnapshot::default()
};
let unavailable = capabilities.unavailable();
assert_eq!(unavailable.len(), CapabilitySnapshot::COUNT - 1);
assert!(!unavailable.iter().any(|(label, _)| *label == "process I/O"));
}
#[test]
fn state_symbols_are_distinguishable_without_color() {
let mut symbols: Vec<char> = [
CapabilityState::Available,
CapabilityState::Unsupported,
CapabilityState::PermissionDenied,
CapabilityState::Unknown,
]
.iter()
.map(|state| state.symbol())
.collect();
symbols.sort_unstable();
symbols.dedup();
assert_eq!(symbols.len(), 4);
}
}