1use crate::{
7 snapshot::{
8 CpuMetrics, LoadAverage, MemoryMetrics, MetricCapabilities, StatusSnapshot, SwapMetrics,
9 SystemIdentity,
10 },
11 v2::{
12 CommitMetrics, CpuMetricsV2, DriveMetrics, MetricCapabilitiesV2, StatusPayloadV2,
13 StatusSnapshotV2, SCHEMA_VERSION_V2,
14 },
15 SCHEMA_VERSION_V1,
16};
17
18#[derive(Debug, Clone)]
20pub struct IdentityFixture {
21 pub name: &'static str,
22 pub hostname: &'static str,
23 pub os_name: &'static str,
24 pub os_version: &'static str,
25 pub kernel_name: &'static str,
26 pub kernel_release: &'static str,
27 pub architecture: &'static str,
28}
29
30impl IdentityFixture {
31 pub const fn linux() -> Self {
33 Self {
34 name: "deadpool",
35 hostname: "deadpool.local",
36 os_name: "linux",
37 os_version: "Ubuntu 24.04",
38 kernel_name: "Linux",
39 kernel_release: "6.8.0-31-generic",
40 architecture: "x86_64",
41 }
42 }
43
44 pub const fn macos() -> Self {
46 Self {
47 name: "mac-mini",
48 hostname: "mac-mini.local",
49 os_name: "macos",
50 os_version: "15.0",
51 kernel_name: "Darwin",
52 kernel_release: "24.0.0",
53 architecture: "arm64",
54 }
55 }
56
57 pub const fn windows() -> Self {
59 Self {
60 name: "win-pc",
61 hostname: "win-pc.local",
62 os_name: "windows",
63 os_version: "10.0",
64 kernel_name: "Windows",
65 kernel_release: "10.0.19045",
66 architecture: "x86_64",
67 }
68 }
69
70 fn into_identity(self) -> SystemIdentity {
71 SystemIdentity {
72 name: self.name.into(),
73 hostname: self.hostname.into(),
74 os_name: self.os_name.into(),
75 os_version: self.os_version.into(),
76 kernel_name: self.kernel_name.into(),
77 kernel_release: self.kernel_release.into(),
78 architecture: self.architecture.into(),
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct LinuxSnapshotBuilder {
86 identity: IdentityFixture,
87 logical_cores: u32,
88 usage_pct: f32,
89 iowait_pct: f32,
90 load: LoadAverage,
91 used_bytes: u64,
92 total_bytes: u64,
93 swap_used_bytes: u64,
94 swap_total_bytes: u64,
95 sample_interval_ms: u64,
96 observed_at_unix_ms: u64,
97}
98
99impl Default for LinuxSnapshotBuilder {
100 fn default() -> Self {
101 Self {
102 identity: IdentityFixture::linux(),
103 logical_cores: 8,
104 usage_pct: 25.2,
105 iowait_pct: 0.4,
106 load: LoadAverage {
107 one: 1.32,
108 five: 0.91,
109 fifteen: 0.62,
110 },
111 used_bytes: 5_900_000_000,
112 total_bytes: 15_600_000_000,
113 swap_used_bytes: 0,
114 swap_total_bytes: 4_000_000_000,
115 sample_interval_ms: 1000,
116 observed_at_unix_ms: 1_716_460_800_000,
117 }
118 }
119}
120
121impl LinuxSnapshotBuilder {
122 #[must_use]
124 pub const fn logical_cores(mut self, cores: u32) -> Self {
125 self.logical_cores = cores;
126 self
127 }
128
129 #[must_use]
131 pub const fn usage_pct(mut self, pct: f32) -> Self {
132 self.usage_pct = pct;
133 self
134 }
135
136 #[must_use]
138 pub const fn iowait_pct(mut self, pct: f32) -> Self {
139 self.iowait_pct = pct;
140 self
141 }
142
143 #[must_use]
145 pub const fn load(mut self, one: f32, five: f32, fifteen: f32) -> Self {
146 self.load = LoadAverage { one, five, fifteen };
147 self
148 }
149
150 #[must_use]
152 pub const fn memory(mut self, used_bytes: u64, total_bytes: u64) -> Self {
153 self.used_bytes = used_bytes;
154 self.total_bytes = total_bytes;
155 self
156 }
157
158 #[must_use]
160 pub const fn swap(mut self, used_bytes: u64, total_bytes: u64) -> Self {
161 self.swap_used_bytes = used_bytes;
162 self.swap_total_bytes = total_bytes;
163 self
164 }
165
166 #[must_use]
168 pub const fn sample_interval_ms(mut self, ms: u64) -> Self {
169 self.sample_interval_ms = ms;
170 self
171 }
172
173 #[must_use]
175 pub const fn observed_at_unix_ms(mut self, ms: u64) -> Self {
176 self.observed_at_unix_ms = ms;
177 self
178 }
179
180 #[must_use]
188 pub fn build(self) -> StatusSnapshot {
189 let snap = StatusSnapshot {
190 schema_version: SCHEMA_VERSION_V1,
191 observed_at_unix_ms: self.observed_at_unix_ms,
192 sample_interval_ms: self.sample_interval_ms,
193 capabilities: MetricCapabilities { cpu_iowait: true },
194 system: self.identity.into_identity(),
195 cpu: CpuMetrics {
196 logical_cores: self.logical_cores,
197 usage_pct: self.usage_pct,
198 iowait_pct: Some(self.iowait_pct),
199 },
200 load: self.load,
201 memory: MemoryMetrics {
202 used_bytes: self.used_bytes,
203 total_bytes: self.total_bytes,
204 usage_pct: percent(self.used_bytes, self.total_bytes),
205 },
206 swap: SwapMetrics {
207 used_bytes: self.swap_used_bytes,
208 total_bytes: self.swap_total_bytes,
209 usage_pct: percent(self.swap_used_bytes, self.swap_total_bytes),
210 },
211 };
212 snap.validate().expect("linux snapshot validates");
213 snap
214 }
215}
216
217#[derive(Debug, Clone)]
220pub struct MacosSnapshotBuilder {
221 identity: IdentityFixture,
222 logical_cores: u32,
223 usage_pct: f32,
224 load: LoadAverage,
225 used_bytes: u64,
226 total_bytes: u64,
227 swap_used_bytes: u64,
228 swap_total_bytes: u64,
229 sample_interval_ms: u64,
230 observed_at_unix_ms: u64,
231}
232
233impl Default for MacosSnapshotBuilder {
234 fn default() -> Self {
235 Self {
236 identity: IdentityFixture::macos(),
237 logical_cores: 8,
238 usage_pct: 18.7,
239 load: LoadAverage {
240 one: 2.10,
241 five: 1.85,
242 fifteen: 1.40,
243 },
244 used_bytes: 9_000_000_000,
245 total_bytes: 16_000_000_000,
246 swap_used_bytes: 0,
247 swap_total_bytes: 0,
248 sample_interval_ms: 1000,
249 observed_at_unix_ms: 1_716_460_800_000,
250 }
251 }
252}
253
254impl MacosSnapshotBuilder {
255 #[must_use]
257 pub const fn logical_cores(mut self, cores: u32) -> Self {
258 self.logical_cores = cores;
259 self
260 }
261
262 #[must_use]
264 pub const fn usage_pct(mut self, pct: f32) -> Self {
265 self.usage_pct = pct;
266 self
267 }
268
269 #[must_use]
271 pub const fn load(mut self, one: f32, five: f32, fifteen: f32) -> Self {
272 self.load = LoadAverage { one, five, fifteen };
273 self
274 }
275
276 #[must_use]
278 pub const fn memory(mut self, used_bytes: u64, total_bytes: u64) -> Self {
279 self.used_bytes = used_bytes;
280 self.total_bytes = total_bytes;
281 self
282 }
283
284 #[must_use]
286 pub const fn swap(mut self, used_bytes: u64, total_bytes: u64) -> Self {
287 self.swap_used_bytes = used_bytes;
288 self.swap_total_bytes = total_bytes;
289 self
290 }
291
292 #[must_use]
294 pub const fn sample_interval_ms(mut self, ms: u64) -> Self {
295 self.sample_interval_ms = ms;
296 self
297 }
298
299 #[must_use]
301 pub const fn observed_at_unix_ms(mut self, ms: u64) -> Self {
302 self.observed_at_unix_ms = ms;
303 self
304 }
305
306 #[must_use]
314 pub fn build(self) -> StatusSnapshot {
315 let snap = StatusSnapshot {
316 schema_version: SCHEMA_VERSION_V1,
317 observed_at_unix_ms: self.observed_at_unix_ms,
318 sample_interval_ms: self.sample_interval_ms,
319 capabilities: MetricCapabilities { cpu_iowait: false },
320 system: self.identity.into_identity(),
321 cpu: CpuMetrics {
322 logical_cores: self.logical_cores,
323 usage_pct: self.usage_pct,
324 iowait_pct: None,
325 },
326 load: self.load,
327 memory: MemoryMetrics {
328 used_bytes: self.used_bytes,
329 total_bytes: self.total_bytes,
330 usage_pct: percent(self.used_bytes, self.total_bytes),
331 },
332 swap: SwapMetrics {
333 used_bytes: self.swap_used_bytes,
334 total_bytes: self.swap_total_bytes,
335 usage_pct: percent(self.swap_used_bytes, self.swap_total_bytes),
336 },
337 };
338 snap.validate().expect("macos snapshot validates");
339 snap
340 }
341}
342
343#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
344fn percent(used: u64, total: u64) -> f32 {
345 if total == 0 {
346 0.0
347 } else {
348 let pct = (used as f64) * 100.0 / (total as f64);
349 (pct as f32).clamp(0.0, 100.0)
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct LinuxSnapshotV2Builder {
356 identity: IdentityFixture,
357 logical_cores: u32,
358 usage_pct: f32,
359 iowait_pct: f32,
360 load: LoadAverage,
361 used_bytes: u64,
362 total_bytes: u64,
363 swap_used_bytes: u64,
364 swap_total_bytes: u64,
365 sample_interval_ms: u64,
366 observed_at_unix_ms: u64,
367 drives: Option<Vec<DriveMetrics>>,
368}
369
370impl Default for LinuxSnapshotV2Builder {
371 fn default() -> Self {
372 Self {
373 identity: IdentityFixture::linux(),
374 logical_cores: 8,
375 usage_pct: 25.2,
376 iowait_pct: 0.4,
377 load: LoadAverage {
378 one: 1.32,
379 five: 0.91,
380 fifteen: 0.62,
381 },
382 used_bytes: 5_900_000_000,
383 total_bytes: 15_600_000_000,
384 swap_used_bytes: 0,
385 swap_total_bytes: 4_000_000_000,
386 sample_interval_ms: 1000,
387 observed_at_unix_ms: 1_716_460_800_000,
388 drives: None,
389 }
390 }
391}
392
393impl LinuxSnapshotV2Builder {
394 #[must_use]
395 pub const fn logical_cores(mut self, cores: u32) -> Self {
396 self.logical_cores = cores;
397 self
398 }
399
400 #[must_use]
401 pub const fn usage_pct(mut self, pct: f32) -> Self {
402 self.usage_pct = pct;
403 self
404 }
405
406 #[must_use]
407 pub const fn iowait_pct(mut self, pct: f32) -> Self {
408 self.iowait_pct = pct;
409 self
410 }
411
412 #[must_use]
413 pub const fn load(mut self, one: f32, five: f32, fifteen: f32) -> Self {
414 self.load = LoadAverage { one, five, fifteen };
415 self
416 }
417
418 #[must_use]
419 pub const fn memory(mut self, used_bytes: u64, total_bytes: u64) -> Self {
420 self.used_bytes = used_bytes;
421 self.total_bytes = total_bytes;
422 self
423 }
424
425 #[must_use]
426 pub const fn swap(mut self, used_bytes: u64, total_bytes: u64) -> Self {
427 self.swap_used_bytes = used_bytes;
428 self.swap_total_bytes = total_bytes;
429 self
430 }
431
432 #[must_use]
433 pub fn drives(mut self, drives: Option<Vec<DriveMetrics>>) -> Self {
434 self.drives = drives;
435 self
436 }
437
438 #[must_use]
439 pub fn build_payload(self) -> StatusPayloadV2 {
440 let drives = self.drives.clone();
441 let snapshot = self.build();
442 let payload = StatusPayloadV2 { snapshot, drives };
443 payload.validate().expect("linux v2 payload validates");
444 payload
445 }
446
447 #[must_use]
448 pub fn build(self) -> StatusSnapshotV2 {
449 let snap = StatusSnapshotV2 {
450 schema_version: SCHEMA_VERSION_V2,
451 observed_at_unix_ms: self.observed_at_unix_ms,
452 sample_interval_ms: self.sample_interval_ms,
453 capabilities: MetricCapabilitiesV2 {
454 cpu_iowait: true,
455 load_average: true,
456 swap: true,
457 memory_commit: false,
458 },
459 system: self.identity.into_identity(),
460 cpu: CpuMetricsV2 {
461 logical_cores: self.logical_cores,
462 usage_pct: self.usage_pct,
463 iowait_pct: Some(self.iowait_pct),
464 },
465 load: Some(self.load),
466 memory: MemoryMetrics {
467 used_bytes: self.used_bytes,
468 total_bytes: self.total_bytes,
469 usage_pct: percent(self.used_bytes, self.total_bytes),
470 },
471 swap: Some(crate::v2::SwapMetrics {
472 used_bytes: self.swap_used_bytes,
473 total_bytes: self.swap_total_bytes,
474 usage_pct: percent(self.swap_used_bytes, self.swap_total_bytes),
475 }),
476 commit: None,
477 };
478 crate::validate_v2::validate_v2(&snap).expect("linux v2 snapshot validates");
479 snap
480 }
481}
482
483#[derive(Debug, Clone)]
485pub struct WindowsSnapshotV2Builder {
486 identity: IdentityFixture,
487 logical_cores: u32,
488 usage_pct: f32,
489 memory_used: u64,
490 memory_total: u64,
491 commit_used: u64,
492 commit_limit: u64,
493 sample_interval_ms: u64,
494 observed_at_unix_ms: u64,
495 drives: Option<Vec<DriveMetrics>>,
496}
497
498impl Default for WindowsSnapshotV2Builder {
499 fn default() -> Self {
500 Self {
501 identity: IdentityFixture::windows(),
502 logical_cores: 4,
503 usage_pct: 12.5,
504 memory_used: 2_000_000_000,
505 memory_total: 8_000_000_000,
506 commit_used: 3_000_000_000,
507 commit_limit: 8_000_000_000,
508 sample_interval_ms: 1000,
509 observed_at_unix_ms: 1_716_460_800_000,
510 drives: None,
511 }
512 }
513}
514
515impl WindowsSnapshotV2Builder {
516 #[must_use]
517 pub const fn logical_cores(mut self, cores: u32) -> Self {
518 self.logical_cores = cores;
519 self
520 }
521
522 #[must_use]
523 pub const fn usage_pct(mut self, pct: f32) -> Self {
524 self.usage_pct = pct;
525 self
526 }
527
528 #[must_use]
529 pub const fn memory(mut self, used_bytes: u64, total_bytes: u64) -> Self {
530 self.memory_used = used_bytes;
531 self.memory_total = total_bytes;
532 self
533 }
534
535 #[must_use]
536 pub const fn commit(mut self, used_bytes: u64, limit_bytes: u64) -> Self {
537 self.commit_used = used_bytes;
538 self.commit_limit = limit_bytes;
539 self
540 }
541
542 #[must_use]
543 pub fn drives(mut self, drives: Option<Vec<DriveMetrics>>) -> Self {
544 self.drives = drives;
545 self
546 }
547
548 #[must_use]
549 pub fn build_payload(self) -> StatusPayloadV2 {
550 let drives = self.drives.clone();
551 let snapshot = self.build();
552 let payload = StatusPayloadV2 { snapshot, drives };
553 payload.validate().expect("windows v2 payload validates");
554 payload
555 }
556
557 #[must_use]
558 pub fn build(self) -> StatusSnapshotV2 {
559 let snap = StatusSnapshotV2 {
560 schema_version: SCHEMA_VERSION_V2,
561 observed_at_unix_ms: self.observed_at_unix_ms,
562 sample_interval_ms: self.sample_interval_ms,
563 capabilities: MetricCapabilitiesV2 {
564 cpu_iowait: false,
565 load_average: false,
566 swap: false,
567 memory_commit: true,
568 },
569 system: self.identity.into_identity(),
570 cpu: CpuMetricsV2 {
571 logical_cores: self.logical_cores,
572 usage_pct: self.usage_pct,
573 iowait_pct: None,
574 },
575 load: None,
576 memory: MemoryMetrics {
577 used_bytes: self.memory_used,
578 total_bytes: self.memory_total,
579 usage_pct: percent(self.memory_used, self.memory_total),
580 },
581 swap: None,
582 commit: Some(CommitMetrics {
583 used_bytes: self.commit_used,
584 limit_bytes: self.commit_limit,
585 usage_pct: percent(self.commit_used, self.commit_limit),
586 }),
587 };
588 crate::validate_v2::validate_v2(&snap).expect("windows v2 snapshot validates");
589 snap
590 }
591}