monitrs_core/model/
measurement.rs1use core::time::Duration;
10
11use crate::units::{ByteUnits, Percent, Rate, format_age, format_byte_rate, format_bytes};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
17pub enum MeasuredValue {
18 Bytes(u64),
20 ByteRate(Rate),
22 EventRate(Rate),
24 Percent(Percent),
26 Count(u64),
28 Duration(Duration),
30 Load(f32),
32}
33
34impl MeasuredValue {
35 #[must_use]
37 pub fn render(self, units: ByteUnits) -> String {
38 match self {
39 Self::Bytes(bytes) => format_bytes(bytes, units),
40 Self::ByteRate(rate) => format_byte_rate(rate, units),
41 Self::EventRate(rate) => format!("{:.0}/s", rate.per_second()),
42 Self::Percent(percent) => percent.to_string(),
43 Self::Count(count) => count.to_string(),
44 Self::Duration(duration) => format_age(duration),
45 Self::Load(load) => format!("{load:.2}"),
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53pub struct Measurement {
54 pub label: &'static str,
56 pub value: MeasuredValue,
58}
59
60impl Measurement {
61 #[must_use]
63 pub const fn new(label: &'static str, value: MeasuredValue) -> Self {
64 Self { label, value }
65 }
66
67 #[must_use]
69 pub fn render(&self, units: ByteUnits) -> String {
70 format!("{} {}", self.label, self.value.render(units))
71 }
72}
73
74#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
78pub enum Severity {
79 Info,
81 Watch,
83 Critical,
85}
86
87impl Severity {
88 #[must_use]
90 pub const fn symbol(self) -> char {
91 match self {
92 Self::Info => '.',
93 Self::Watch => '!',
94 Self::Critical => 'X',
95 }
96 }
97
98 #[must_use]
100 pub const fn label(self) -> &'static str {
101 match self {
102 Self::Info => "info",
103 Self::Watch => "watch",
104 Self::Critical => "critical",
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize))]
115#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
116pub enum Confidence {
117 Low,
119 Medium,
121 High,
123}
124
125impl Confidence {
126 #[must_use]
128 pub const fn label(self) -> &'static str {
129 match self {
130 Self::Low => "low",
131 Self::Medium => "medium",
132 Self::High => "high",
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn measurements_render_with_their_unit_family() {
143 let m = Measurement::new("available", MeasuredValue::Bytes(4 * 1024 * 1024 * 1024));
144 assert_eq!(m.render(ByteUnits::Iec), "available 4.0 GiB");
145 assert_eq!(m.render(ByteUnits::Si), "available 4.3 GB");
146 }
147
148 #[test]
149 fn load_renders_as_a_queue_length_not_a_percentage() {
150 let m = Measurement::new("load1", MeasuredValue::Load(11.4));
151 assert_eq!(m.render(ByteUnits::Iec), "load1 11.40");
152 }
153
154 #[test]
155 fn event_rates_are_distinct_from_byte_rates() {
156 let rate = Rate::new(1024.0).expect("valid");
157 assert_eq!(
158 MeasuredValue::EventRate(rate).render(ByteUnits::Iec),
159 "1024/s"
160 );
161 assert_eq!(
162 MeasuredValue::ByteRate(rate).render(ByteUnits::Iec),
163 "1.0K/s"
164 );
165 }
166
167 #[test]
168 fn severity_symbols_match_the_specified_ascii_cues() {
169 assert_eq!(Severity::Info.symbol(), '.');
170 assert_eq!(Severity::Watch.symbol(), '!');
171 assert_eq!(Severity::Critical.symbol(), 'X');
172 }
173
174 #[test]
175 fn severity_and_confidence_order_from_least_to_most() {
176 assert!(Severity::Info < Severity::Watch);
177 assert!(Severity::Watch < Severity::Critical);
178 assert!(Confidence::Low < Confidence::Medium);
179 assert!(Confidence::Medium < Confidence::High);
180 }
181}