1use crate::api::generated::machine::{
8 CpUsInfo as ProtoCpUsInfo, CpuInfo as ProtoCpuInfo, CpuInfoResponse as ProtoCpuInfoResponse,
9 DiskStat as ProtoDiskStat, DiskStats as ProtoDiskStats,
10 DiskStatsResponse as ProtoDiskStatsResponse, LoadAvg as ProtoLoadAvg,
11 LoadAvgResponse as ProtoLoadAvgResponse, Memory as ProtoMemory,
12 MemoryResponse as ProtoMemoryResponse, MountStat as ProtoMountStat,
13 MountsResponse as ProtoMountsResponse, NetDev as ProtoNetDev,
14 NetworkDeviceStats as ProtoNetworkDeviceStats,
15 NetworkDeviceStatsResponse as ProtoNetworkDeviceStatsResponse, Process as ProtoProcess,
16 ProcessInfo as ProtoProcessInfo, ProcessesResponse as ProtoProcessesResponse,
17};
18
19#[derive(Debug, Clone)]
25pub struct LoadAvgResult {
26 pub node: Option<String>,
28 pub load1: f64,
30 pub load5: f64,
32 pub load15: f64,
34}
35
36impl From<ProtoLoadAvg> for LoadAvgResult {
37 fn from(proto: ProtoLoadAvg) -> Self {
38 Self {
39 node: proto.metadata.map(|m| m.hostname),
40 load1: proto.load1,
41 load5: proto.load5,
42 load15: proto.load15,
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct LoadAvgResponse {
50 pub results: Vec<LoadAvgResult>,
52}
53
54impl From<ProtoLoadAvgResponse> for LoadAvgResponse {
55 fn from(proto: ProtoLoadAvgResponse) -> Self {
56 Self {
57 results: proto
58 .messages
59 .into_iter()
60 .map(LoadAvgResult::from)
61 .collect(),
62 }
63 }
64}
65
66impl LoadAvgResponse {
67 #[must_use]
69 pub fn first(&self) -> Option<&LoadAvgResult> {
70 self.results.first()
71 }
72}
73
74#[derive(Debug, Clone)]
80pub struct MemoryResult {
81 pub node: Option<String>,
83 pub mem_total: u64,
85 pub mem_free: u64,
87 pub mem_available: u64,
89 pub buffers: u64,
91 pub cached: u64,
93 pub swap_total: u64,
95 pub swap_free: u64,
97}
98
99impl From<ProtoMemory> for MemoryResult {
100 fn from(proto: ProtoMemory) -> Self {
101 let meminfo = proto.meminfo.unwrap_or_default();
102 Self {
103 node: proto.metadata.map(|m| m.hostname),
104 mem_total: meminfo.memtotal,
105 mem_free: meminfo.memfree,
106 mem_available: meminfo.memavailable,
107 buffers: meminfo.buffers,
108 cached: meminfo.cached,
109 swap_total: meminfo.swaptotal,
110 swap_free: meminfo.swapfree,
111 }
112 }
113}
114
115impl MemoryResult {
116 #[must_use]
118 pub fn total(&self) -> u64 {
119 self.mem_total
120 }
121
122 #[must_use]
124 pub fn free(&self) -> u64 {
125 self.mem_free
126 }
127
128 #[must_use]
130 pub fn available(&self) -> u64 {
131 self.mem_available
132 }
133
134 #[must_use]
136 pub fn used(&self) -> u64 {
137 self.mem_total.saturating_sub(self.mem_available)
138 }
139
140 #[must_use]
142 pub fn usage_percent(&self) -> f64 {
143 if self.mem_total == 0 {
144 0.0
145 } else {
146 (self.used() as f64 / self.mem_total as f64) * 100.0
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct MemoryResponse {
154 pub results: Vec<MemoryResult>,
156}
157
158impl From<ProtoMemoryResponse> for MemoryResponse {
159 fn from(proto: ProtoMemoryResponse) -> Self {
160 Self {
161 results: proto.messages.into_iter().map(MemoryResult::from).collect(),
162 }
163 }
164}
165
166impl MemoryResponse {
167 #[must_use]
169 pub fn first(&self) -> Option<&MemoryResult> {
170 self.results.first()
171 }
172}
173
174#[derive(Debug, Clone)]
180pub struct CpuInfo {
181 pub processor: u32,
183 pub vendor_id: String,
185 pub model_name: String,
187 pub cpu_mhz: f64,
189 pub cpu_cores: u32,
191 pub flags: Vec<String>,
193}
194
195impl From<ProtoCpuInfo> for CpuInfo {
196 fn from(proto: ProtoCpuInfo) -> Self {
197 Self {
198 processor: proto.processor,
199 vendor_id: proto.vendor_id,
200 model_name: proto.model_name,
201 cpu_mhz: proto.cpu_mhz,
202 cpu_cores: proto.cpu_cores,
203 flags: proto.flags,
204 }
205 }
206}
207
208#[derive(Debug, Clone)]
210pub struct CpuInfoResult {
211 pub node: Option<String>,
213 pub cpus: Vec<CpuInfo>,
215}
216
217impl From<ProtoCpUsInfo> for CpuInfoResult {
218 fn from(proto: ProtoCpUsInfo) -> Self {
219 Self {
220 node: proto.metadata.map(|m| m.hostname),
221 cpus: proto.cpu_info.into_iter().map(CpuInfo::from).collect(),
222 }
223 }
224}
225
226#[derive(Debug, Clone)]
228pub struct CpuInfoResponse {
229 pub results: Vec<CpuInfoResult>,
231}
232
233impl From<ProtoCpuInfoResponse> for CpuInfoResponse {
234 fn from(proto: ProtoCpuInfoResponse) -> Self {
235 Self {
236 results: proto
237 .messages
238 .into_iter()
239 .map(CpuInfoResult::from)
240 .collect(),
241 }
242 }
243}
244
245impl CpuInfoResponse {
246 #[must_use]
248 pub fn first(&self) -> Option<&CpuInfoResult> {
249 self.results.first()
250 }
251
252 #[must_use]
254 pub fn total_cpus(&self) -> usize {
255 self.results.iter().map(|r| r.cpus.len()).sum()
256 }
257}
258
259#[derive(Debug, Clone)]
265pub struct DiskStat {
266 pub name: String,
268 pub read_completed: u64,
270 pub read_sectors: u64,
272 pub read_time_ms: u64,
274 pub write_completed: u64,
276 pub write_sectors: u64,
278 pub write_time_ms: u64,
280 pub io_in_progress: u64,
282 pub io_time_ms: u64,
284}
285
286impl From<ProtoDiskStat> for DiskStat {
287 fn from(proto: ProtoDiskStat) -> Self {
288 Self {
289 name: proto.name,
290 read_completed: proto.read_completed,
291 read_sectors: proto.read_sectors,
292 read_time_ms: proto.read_time_ms,
293 write_completed: proto.write_completed,
294 write_sectors: proto.write_sectors,
295 write_time_ms: proto.write_time_ms,
296 io_in_progress: proto.io_in_progress,
297 io_time_ms: proto.io_time_ms,
298 }
299 }
300}
301
302#[derive(Debug, Clone)]
304pub struct DiskStatsResult {
305 pub node: Option<String>,
307 pub total: Option<DiskStat>,
309 pub devices: Vec<DiskStat>,
311}
312
313impl From<ProtoDiskStats> for DiskStatsResult {
314 fn from(proto: ProtoDiskStats) -> Self {
315 Self {
316 node: proto.metadata.map(|m| m.hostname),
317 total: proto.total.map(DiskStat::from),
318 devices: proto.devices.into_iter().map(DiskStat::from).collect(),
319 }
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct DiskStatsResponse {
326 pub results: Vec<DiskStatsResult>,
328}
329
330impl From<ProtoDiskStatsResponse> for DiskStatsResponse {
331 fn from(proto: ProtoDiskStatsResponse) -> Self {
332 Self {
333 results: proto
334 .messages
335 .into_iter()
336 .map(DiskStatsResult::from)
337 .collect(),
338 }
339 }
340}
341
342impl DiskStatsResponse {
343 #[must_use]
345 pub fn first(&self) -> Option<&DiskStatsResult> {
346 self.results.first()
347 }
348}
349
350#[derive(Debug, Clone)]
356pub struct NetDevStat {
357 pub name: String,
359 pub rx_bytes: u64,
361 pub rx_packets: u64,
363 pub rx_errors: u64,
365 pub tx_bytes: u64,
367 pub tx_packets: u64,
369 pub tx_errors: u64,
371}
372
373impl From<ProtoNetDev> for NetDevStat {
374 fn from(proto: ProtoNetDev) -> Self {
375 Self {
376 name: proto.name,
377 rx_bytes: proto.rx_bytes,
378 rx_packets: proto.rx_packets,
379 rx_errors: proto.rx_errors,
380 tx_bytes: proto.tx_bytes,
381 tx_packets: proto.tx_packets,
382 tx_errors: proto.tx_errors,
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
389pub struct NetworkDeviceStatsResult {
390 pub node: Option<String>,
392 pub total: Option<NetDevStat>,
394 pub devices: Vec<NetDevStat>,
396}
397
398impl From<ProtoNetworkDeviceStats> for NetworkDeviceStatsResult {
399 fn from(proto: ProtoNetworkDeviceStats) -> Self {
400 Self {
401 node: proto.metadata.map(|m| m.hostname),
402 total: proto.total.map(NetDevStat::from),
403 devices: proto.devices.into_iter().map(NetDevStat::from).collect(),
404 }
405 }
406}
407
408#[derive(Debug, Clone)]
410pub struct NetworkDeviceStatsResponse {
411 pub results: Vec<NetworkDeviceStatsResult>,
413}
414
415impl From<ProtoNetworkDeviceStatsResponse> for NetworkDeviceStatsResponse {
416 fn from(proto: ProtoNetworkDeviceStatsResponse) -> Self {
417 Self {
418 results: proto
419 .messages
420 .into_iter()
421 .map(NetworkDeviceStatsResult::from)
422 .collect(),
423 }
424 }
425}
426
427impl NetworkDeviceStatsResponse {
428 #[must_use]
430 pub fn first(&self) -> Option<&NetworkDeviceStatsResult> {
431 self.results.first()
432 }
433}
434
435#[derive(Debug, Clone)]
441pub struct MountStat {
442 pub filesystem: String,
444 pub size: u64,
446 pub available: u64,
448 pub mounted_on: String,
450}
451
452impl From<ProtoMountStat> for MountStat {
453 fn from(proto: ProtoMountStat) -> Self {
454 Self {
455 filesystem: proto.filesystem,
456 size: proto.size,
457 available: proto.available,
458 mounted_on: proto.mounted_on,
459 }
460 }
461}
462
463impl MountStat {
464 #[must_use]
466 pub fn used(&self) -> u64 {
467 self.size.saturating_sub(self.available)
468 }
469
470 #[must_use]
472 pub fn usage_percent(&self) -> f64 {
473 if self.size == 0 {
474 0.0
475 } else {
476 (self.used() as f64 / self.size as f64) * 100.0
477 }
478 }
479}
480
481#[derive(Debug, Clone)]
483pub struct MountsResult {
484 pub node: Option<String>,
486 pub stats: Vec<MountStat>,
488}
489
490#[derive(Debug, Clone)]
492pub struct MountsResponse {
493 pub results: Vec<MountsResult>,
495}
496
497impl From<ProtoMountsResponse> for MountsResponse {
498 fn from(proto: ProtoMountsResponse) -> Self {
499 Self {
500 results: proto
501 .messages
502 .into_iter()
503 .map(|m| MountsResult {
504 node: m.metadata.map(|meta| meta.hostname),
505 stats: m.stats.into_iter().map(MountStat::from).collect(),
506 })
507 .collect(),
508 }
509 }
510}
511
512impl MountsResponse {
513 #[must_use]
515 pub fn first(&self) -> Option<&MountsResult> {
516 self.results.first()
517 }
518}
519
520#[derive(Debug, Clone)]
526pub struct ProcessInfo {
527 pub pid: i32,
529 pub ppid: i32,
531 pub state: String,
533 pub threads: i32,
535 pub cpu_time: f64,
537 pub virtual_memory: u64,
539 pub resident_memory: u64,
541 pub command: String,
543 pub executable: String,
545 pub args: String,
547}
548
549impl From<ProtoProcessInfo> for ProcessInfo {
550 fn from(proto: ProtoProcessInfo) -> Self {
551 Self {
552 pid: proto.pid,
553 ppid: proto.ppid,
554 state: proto.state,
555 threads: proto.threads,
556 cpu_time: proto.cpu_time,
557 virtual_memory: proto.virtual_memory,
558 resident_memory: proto.resident_memory,
559 command: proto.command,
560 executable: proto.executable,
561 args: proto.args,
562 }
563 }
564}
565
566#[derive(Debug, Clone)]
568pub struct ProcessesResult {
569 pub node: Option<String>,
571 pub processes: Vec<ProcessInfo>,
573}
574
575impl From<ProtoProcess> for ProcessesResult {
576 fn from(proto: ProtoProcess) -> Self {
577 Self {
578 node: proto.metadata.map(|m| m.hostname),
579 processes: proto.processes.into_iter().map(ProcessInfo::from).collect(),
580 }
581 }
582}
583
584#[derive(Debug, Clone)]
586pub struct ProcessesResponse {
587 pub results: Vec<ProcessesResult>,
589}
590
591impl From<ProtoProcessesResponse> for ProcessesResponse {
592 fn from(proto: ProtoProcessesResponse) -> Self {
593 Self {
594 results: proto
595 .messages
596 .into_iter()
597 .map(ProcessesResult::from)
598 .collect(),
599 }
600 }
601}
602
603impl ProcessesResponse {
604 #[must_use]
606 pub fn first(&self) -> Option<&ProcessesResult> {
607 self.results.first()
608 }
609
610 #[must_use]
612 pub fn total_processes(&self) -> usize {
613 self.results.iter().map(|r| r.processes.len()).sum()
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn test_load_avg_result() {
623 let result = LoadAvgResult {
624 node: Some("node1".to_string()),
625 load1: 0.5,
626 load5: 0.7,
627 load15: 0.9,
628 };
629 assert_eq!(result.load1, 0.5);
630 }
631
632 #[test]
633 fn test_memory_result() {
634 let result = MemoryResult {
635 node: Some("node1".to_string()),
636 mem_total: 16_000_000_000,
637 mem_free: 4_000_000_000,
638 mem_available: 8_000_000_000,
639 buffers: 100_000_000,
640 cached: 2_000_000_000,
641 swap_total: 1_000_000_000,
642 swap_free: 500_000_000,
643 };
644
645 assert_eq!(result.total(), 16_000_000_000);
646 assert_eq!(result.available(), 8_000_000_000);
647 assert_eq!(result.used(), 8_000_000_000);
648 assert!((result.usage_percent() - 50.0).abs() < 0.01);
649 }
650
651 #[test]
652 fn test_mount_stat() {
653 let stat = MountStat {
654 filesystem: "ext4".to_string(),
655 size: 100_000_000_000,
656 available: 40_000_000_000,
657 mounted_on: "/".to_string(),
658 };
659
660 assert_eq!(stat.used(), 60_000_000_000);
661 assert!((stat.usage_percent() - 60.0).abs() < 0.01);
662 }
663
664 #[test]
665 fn test_cpu_info() {
666 let cpu = CpuInfo {
667 processor: 0,
668 vendor_id: "GenuineIntel".to_string(),
669 model_name: "Intel Core i7".to_string(),
670 cpu_mhz: 3200.0,
671 cpu_cores: 4,
672 flags: vec!["avx".to_string(), "sse".to_string()],
673 };
674
675 assert_eq!(cpu.processor, 0);
676 assert_eq!(cpu.cpu_cores, 4);
677 }
678
679 #[test]
680 fn test_disk_stat() {
681 let stat = DiskStat {
682 name: "sda".to_string(),
683 read_completed: 1000,
684 read_sectors: 50000,
685 read_time_ms: 500,
686 write_completed: 500,
687 write_sectors: 25000,
688 write_time_ms: 250,
689 io_in_progress: 2,
690 io_time_ms: 750,
691 };
692
693 assert_eq!(stat.name, "sda");
694 assert_eq!(stat.read_completed, 1000);
695 }
696
697 #[test]
698 fn test_net_dev_stat() {
699 let stat = NetDevStat {
700 name: "eth0".to_string(),
701 rx_bytes: 1_000_000,
702 rx_packets: 1000,
703 rx_errors: 0,
704 tx_bytes: 500_000,
705 tx_packets: 500,
706 tx_errors: 0,
707 };
708
709 assert_eq!(stat.name, "eth0");
710 assert_eq!(stat.rx_bytes, 1_000_000);
711 }
712
713 #[test]
714 fn test_process_info() {
715 let proc = ProcessInfo {
716 pid: 1,
717 ppid: 0,
718 state: "S".to_string(),
719 threads: 1,
720 cpu_time: 10.5,
721 virtual_memory: 1_000_000,
722 resident_memory: 500_000,
723 command: "init".to_string(),
724 executable: "/sbin/init".to_string(),
725 args: "".to_string(),
726 };
727
728 assert_eq!(proc.pid, 1);
729 assert_eq!(proc.command, "init");
730 }
731}