use core::time::Duration;
use crate::units::{ByteUnits, Percent, Rate, format_age, format_byte_rate, format_bytes};
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum MeasuredValue {
Bytes(u64),
ByteRate(Rate),
EventRate(Rate),
Percent(Percent),
Count(u64),
Duration(Duration),
Load(f32),
}
impl MeasuredValue {
#[must_use]
pub fn render(self, units: ByteUnits) -> String {
match self {
Self::Bytes(bytes) => format_bytes(bytes, units),
Self::ByteRate(rate) => format_byte_rate(rate, units),
Self::EventRate(rate) => format!("{:.0}/s", rate.per_second()),
Self::Percent(percent) => percent.to_string(),
Self::Count(count) => count.to_string(),
Self::Duration(duration) => format_age(duration),
Self::Load(load) => format!("{load:.2}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Measurement {
pub label: &'static str,
pub value: MeasuredValue,
}
impl Measurement {
#[must_use]
pub const fn new(label: &'static str, value: MeasuredValue) -> Self {
Self { label, value }
}
#[must_use]
pub fn render(&self, units: ByteUnits) -> String {
format!("{} {}", self.label, self.value.render(units))
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Severity {
Info,
Watch,
Critical,
}
impl Severity {
#[must_use]
pub const fn symbol(self) -> char {
match self {
Self::Info => '.',
Self::Watch => '!',
Self::Critical => 'X',
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Info => "info",
Self::Watch => "watch",
Self::Critical => "critical",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Confidence {
Low,
Medium,
High,
}
impl Confidence {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn measurements_render_with_their_unit_family() {
let m = Measurement::new("available", MeasuredValue::Bytes(4 * 1024 * 1024 * 1024));
assert_eq!(m.render(ByteUnits::Iec), "available 4.0 GiB");
assert_eq!(m.render(ByteUnits::Si), "available 4.3 GB");
}
#[test]
fn load_renders_as_a_queue_length_not_a_percentage() {
let m = Measurement::new("load1", MeasuredValue::Load(11.4));
assert_eq!(m.render(ByteUnits::Iec), "load1 11.40");
}
#[test]
fn event_rates_are_distinct_from_byte_rates() {
let rate = Rate::new(1024.0).expect("valid");
assert_eq!(
MeasuredValue::EventRate(rate).render(ByteUnits::Iec),
"1024/s"
);
assert_eq!(
MeasuredValue::ByteRate(rate).render(ByteUnits::Iec),
"1.0K/s"
);
}
#[test]
fn severity_symbols_match_the_specified_ascii_cues() {
assert_eq!(Severity::Info.symbol(), '.');
assert_eq!(Severity::Watch.symbol(), '!');
assert_eq!(Severity::Critical.symbol(), 'X');
}
#[test]
fn severity_and_confidence_order_from_least_to_most() {
assert!(Severity::Info < Severity::Watch);
assert!(Severity::Watch < Severity::Critical);
assert!(Confidence::Low < Confidence::Medium);
assert!(Confidence::Medium < Confidence::High);
}
}