monitrs_core/model/
cpu.rs1use crate::model::MetricState;
8use crate::units::Percent;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13pub struct CpuUsage {
14 pub busy: Percent,
16 pub breakdown: MetricState<CpuBreakdown>,
18}
19
20impl CpuUsage {
21 #[must_use]
23 pub const fn plain(busy: Percent) -> Self {
24 Self {
25 busy,
26 breakdown: MetricState::Unsupported,
27 }
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct CpuBreakdown {
38 pub user: Percent,
40 pub system: Percent,
42 pub nice: Percent,
44 pub idle: Percent,
46 pub iowait: MetricState<Percent>,
48 pub irq: MetricState<Percent>,
50 pub softirq: MetricState<Percent>,
52 pub steal: MetricState<Percent>,
55}
56
57#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
61pub enum CpuNormalization {
62 #[default]
66 Core,
67 Machine,
69}
70
71impl CpuNormalization {
72 #[must_use]
74 pub const fn description(self) -> &'static str {
75 match self {
76 Self::Core => "one core = 100%, so a multi-threaded process may exceed 100%",
77 Self::Machine => "the whole machine = 100%, so no process exceeds 100%",
78 }
79 }
80
81 #[must_use]
86 pub fn apply(self, core_normalized: Percent, logical_cpus: u16) -> Option<Percent> {
87 match self {
88 Self::Core => Some(core_normalized),
89 Self::Machine => {
90 if logical_cpus == 0 {
91 return None;
92 }
93 Percent::new(core_normalized.value() / f32::from(logical_cpus))
94 }
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize))]
102pub struct CpuSnapshot {
103 pub logical_count: u16,
105 pub physical_count: MetricState<u16>,
107 pub total: MetricState<CpuUsage>,
109 pub per_core: MetricState<Vec<CpuUsage>>,
111 pub frequency_mhz: MetricState<u64>,
113}
114
115impl CpuSnapshot {
116 #[must_use]
118 pub const fn warming_up(logical_count: u16) -> Self {
119 Self {
120 logical_count,
121 physical_count: MetricState::WarmingUp,
122 total: MetricState::WarmingUp,
123 per_core: MetricState::WarmingUp,
124 frequency_mhz: MetricState::WarmingUp,
125 }
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct LoadSnapshot {
133 pub one: f32,
135 pub five: f32,
137 pub fifteen: f32,
139}
140
141impl LoadSnapshot {
142 #[must_use]
148 pub fn per_cpu(&self, logical_cpus: u16) -> Option<f32> {
149 if logical_cpus == 0 {
150 return None;
151 }
152 let value = self.one / f32::from(logical_cpus);
153 value.is_finite().then_some(value)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn core_normalization_is_the_identity() {
163 let cpu = Percent::new(287.0).expect("valid");
164 let out = CpuNormalization::Core.apply(cpu, 8).expect("valid");
165 assert!((out.value() - 287.0).abs() < f32::EPSILON);
166 }
167
168 #[test]
169 fn machine_normalization_divides_by_the_logical_cpu_count() {
170 let cpu = Percent::new(400.0).expect("valid");
171 let out = CpuNormalization::Machine.apply(cpu, 8).expect("valid");
172 assert!((out.value() - 50.0).abs() < f32::EPSILON);
173 }
174
175 #[test]
176 fn machine_normalization_is_undefined_without_a_cpu_count() {
177 let cpu = Percent::new(400.0).expect("valid");
178 assert!(CpuNormalization::Machine.apply(cpu, 0).is_none());
179 }
180
181 #[test]
182 fn the_default_convention_is_documented_as_per_core() {
183 assert_eq!(CpuNormalization::default(), CpuNormalization::Core);
184 assert!(
185 CpuNormalization::default()
186 .description()
187 .contains("exceed 100%")
188 );
189 }
190
191 #[test]
192 fn load_per_cpu_is_undefined_without_a_cpu_count() {
193 let load = LoadSnapshot {
194 one: 11.4,
195 five: 8.0,
196 fifteen: 4.0,
197 };
198 assert!(load.per_cpu(0).is_none());
199 let per_cpu = load.per_cpu(8).expect("valid");
200 assert!((per_cpu - 1.425).abs() < 0.001);
201 }
202
203 #[test]
204 fn a_warming_up_cpu_snapshot_reports_no_utilization() {
205 let cpu = CpuSnapshot::warming_up(8);
206 assert_eq!(cpu.logical_count, 8);
207 assert!(cpu.total.fresh().is_none());
208 assert!(cpu.total.is_warming_up());
209 }
210}