Skip to main content

rustfs_madmin/
health.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Debug, Default, Serialize, Deserialize)]
20pub struct NodeCommon {
21    pub addr: String,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub error: Option<String>,
24}
25
26#[derive(Debug, Default, Serialize, Deserialize)]
27pub struct Cpu {
28    pub vendor_id: String,
29    pub family: String,
30    pub model: String,
31    pub stepping: i32,
32    pub physical_id: String,
33    pub model_name: String,
34    pub mhz: f64,
35    pub cache_size: i32,
36    pub flags: Vec<String>,
37    pub microcode: String,
38    pub cores: u64,
39}
40
41#[derive(Debug, Default, Serialize, Deserialize)]
42pub struct CpuFreqStats {
43    name: String,
44    cpuinfo_current_frequency: Option<u64>,
45    cpuinfo_minimum_frequency: Option<u64>,
46    cpuinfo_maximum_frequency: Option<u64>,
47    cpuinfo_transition_latency: Option<u64>,
48    scaling_current_frequency: Option<u64>,
49    scaling_minimum_frequency: Option<u64>,
50    scaling_maximum_frequency: Option<u64>,
51    available_governors: String,
52    driver: String,
53    governor: String,
54    related_cpus: String,
55    set_speed: String,
56}
57
58#[derive(Debug, Default, Serialize, Deserialize)]
59pub struct Cpus {
60    node_common: NodeCommon,
61    cpus: Vec<Cpu>,
62    cpu_freq_stats: Vec<CpuFreqStats>,
63}
64
65pub fn get_cpus() -> Cpus {
66    // todo
67    Cpus::default()
68}
69
70#[derive(Debug, Default, Serialize, Deserialize)]
71pub struct Partition {
72    pub error: String,
73    device: String,
74    model: String,
75    revision: String,
76    mountpoint: String,
77    fs_type: String,
78    mount_options: String,
79    space_total: u64,
80    space_free: u64,
81    inode_total: u64,
82    inode_free: u64,
83}
84
85#[derive(Debug, Default, Serialize, Deserialize)]
86pub struct Partitions {
87    node_common: NodeCommon,
88    partitions: Vec<Partition>,
89}
90
91pub fn get_partitions() -> Partitions {
92    Partitions::default()
93}
94
95#[derive(Debug, Default, Serialize, Deserialize)]
96pub struct OsInfo {
97    node_common: NodeCommon,
98}
99
100pub fn get_os_info() -> OsInfo {
101    OsInfo::default()
102}
103
104#[derive(Debug, Default, Serialize, Deserialize)]
105pub struct ProcInfo {
106    node_common: NodeCommon,
107    pid: i32,
108    is_background: bool,
109    cpu_percent: f64,
110    children_pids: Vec<i32>,
111    cmd_line: String,
112    num_connections: usize,
113    create_time: u64,
114    cwd: String,
115    exec_path: String,
116    gids: Vec<i32>,
117    // io_counters:
118    is_running: bool,
119    // mem_info:
120    // mem_maps:
121    mem_percent: f32,
122    name: String,
123    nice: i32,
124    //num_ctx_switches:
125    num_fds: i32,
126    num_threads: i32,
127    // page_faults:
128    ppid: i32,
129    status: String,
130    tgid: i32,
131    uids: Vec<i32>,
132    username: String,
133}
134
135pub fn get_proc_info(_addr: &str) -> ProcInfo {
136    ProcInfo::default()
137}
138
139#[derive(Debug, Default, Serialize, Deserialize)]
140pub struct SysService {
141    name: String,
142    status: String,
143}
144
145#[derive(Debug, Default, Serialize, Deserialize)]
146pub struct SysServices {
147    node_common: NodeCommon,
148    services: Vec<SysService>,
149}
150
151pub fn get_sys_services(_add: &str) -> SysServices {
152    SysServices::default()
153}
154
155#[derive(Debug, Default, Serialize, Deserialize)]
156pub struct SysConfig {
157    node_common: NodeCommon,
158    config: HashMap<String, String>,
159}
160
161pub fn get_sys_config(_addr: &str) -> SysConfig {
162    SysConfig::default()
163}
164
165#[derive(Debug, Default, Serialize, Deserialize)]
166pub struct SysErrors {
167    node_common: NodeCommon,
168    errors: Vec<String>,
169}
170
171pub fn get_sys_errors(_add: &str) -> SysErrors {
172    SysErrors::default()
173}
174
175#[derive(Clone, Debug, Default, Serialize, Deserialize)]
176pub struct MemInfo {
177    node_common: NodeCommon,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    total: Option<u64>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    used: Option<u64>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    free: Option<u64>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    available: Option<u64>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    shared: Option<u64>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    cache: Option<u64>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    buffers: Option<u64>,
192    #[serde(rename = "swap_space_total", skip_serializing_if = "Option::is_none")]
193    swap_space_total: Option<u64>,
194    #[serde(rename = "swap_space_free", skip_serializing_if = "Option::is_none")]
195    swap_space_free: Option<u64>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    limit: Option<u64>,
198}
199
200pub fn get_mem_info(_addr: &str) -> MemInfo {
201    MemInfo::default()
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use serde_json;
208
209    #[test]
210    fn test_node_common_creation() {
211        let node = NodeCommon::default();
212        assert!(node.addr.is_empty(), "Default addr should be empty");
213        assert!(node.error.is_none(), "Default error should be None");
214    }
215
216    #[test]
217    fn test_node_common_with_values() {
218        let node = NodeCommon {
219            addr: "127.0.0.1:9000".to_string(),
220            error: Some("Connection failed".to_string()),
221        };
222        assert_eq!(node.addr, "127.0.0.1:9000");
223        assert_eq!(node.error.unwrap(), "Connection failed");
224    }
225
226    #[test]
227    fn test_node_common_serialization() {
228        let node = NodeCommon {
229            addr: "localhost:8080".to_string(),
230            error: None,
231        };
232
233        let json = serde_json::to_string(&node).unwrap();
234        assert!(json.contains("localhost:8080"));
235        assert!(!json.contains("error"), "None error should be skipped in serialization");
236    }
237
238    #[test]
239    fn test_node_common_deserialization() {
240        let json = r#"{"addr":"test.example.com:9000","error":"Test error"}"#;
241        let node: NodeCommon = serde_json::from_str(json).unwrap();
242
243        assert_eq!(node.addr, "test.example.com:9000");
244        assert_eq!(node.error.unwrap(), "Test error");
245    }
246
247    #[test]
248    fn test_cpu_default() {
249        let cpu = Cpu::default();
250        assert!(cpu.vendor_id.is_empty());
251        assert!(cpu.family.is_empty());
252        assert!(cpu.model.is_empty());
253        assert_eq!(cpu.stepping, 0);
254        assert_eq!(cpu.mhz, 0.0);
255        assert_eq!(cpu.cache_size, 0);
256        assert!(cpu.flags.is_empty());
257        assert_eq!(cpu.cores, 0);
258    }
259
260    #[test]
261    fn test_cpu_with_values() {
262        let cpu = Cpu {
263            vendor_id: "GenuineIntel".to_string(),
264            family: "6".to_string(),
265            model: "142".to_string(),
266            stepping: 12,
267            physical_id: "0".to_string(),
268            model_name: "Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz".to_string(),
269            mhz: 1800.0,
270            cache_size: 8192,
271            flags: vec!["fpu".to_string(), "vme".to_string(), "de".to_string()],
272            microcode: "0xf0".to_string(),
273            cores: 4,
274        };
275
276        assert_eq!(cpu.vendor_id, "GenuineIntel");
277        assert_eq!(cpu.cores, 4);
278        assert_eq!(cpu.flags.len(), 3);
279        assert!(cpu.flags.contains(&"fpu".to_string()));
280    }
281
282    #[test]
283    fn test_cpu_serialization() {
284        let cpu = Cpu {
285            vendor_id: "AMD".to_string(),
286            model_name: "AMD Ryzen 7".to_string(),
287            cores: 8,
288            ..Default::default()
289        };
290
291        let json = serde_json::to_string(&cpu).unwrap();
292        assert!(json.contains("AMD"));
293        assert!(json.contains("AMD Ryzen 7"));
294        assert!(json.contains("8"));
295    }
296
297    #[test]
298    fn test_cpu_freq_stats_default() {
299        let stats = CpuFreqStats::default();
300        assert!(stats.name.is_empty());
301        assert!(stats.cpuinfo_current_frequency.is_none());
302        assert!(stats.available_governors.is_empty());
303        assert!(stats.driver.is_empty());
304    }
305
306    #[test]
307    fn test_cpus_structure() {
308        let cpus = Cpus {
309            node_common: NodeCommon {
310                addr: "node1".to_string(),
311                error: None,
312            },
313            cpus: vec![Cpu {
314                vendor_id: "Intel".to_string(),
315                cores: 4,
316                ..Default::default()
317            }],
318            cpu_freq_stats: vec![CpuFreqStats {
319                name: "cpu0".to_string(),
320                cpuinfo_current_frequency: Some(2400),
321                ..Default::default()
322            }],
323        };
324
325        assert_eq!(cpus.node_common.addr, "node1");
326        assert_eq!(cpus.cpus.len(), 1);
327        assert_eq!(cpus.cpu_freq_stats.len(), 1);
328        assert_eq!(cpus.cpus[0].cores, 4);
329    }
330
331    #[test]
332    fn test_get_cpus_function() {
333        let cpus = get_cpus();
334        assert!(cpus.node_common.addr.is_empty());
335        assert!(cpus.cpus.is_empty());
336        assert!(cpus.cpu_freq_stats.is_empty());
337    }
338
339    #[test]
340    fn test_partition_default() {
341        let partition = Partition::default();
342        assert!(partition.error.is_empty());
343        assert!(partition.device.is_empty());
344        assert_eq!(partition.space_total, 0);
345        assert_eq!(partition.space_free, 0);
346        assert_eq!(partition.inode_total, 0);
347        assert_eq!(partition.inode_free, 0);
348    }
349
350    #[test]
351    fn test_partition_with_values() {
352        let partition = Partition {
353            error: "".to_string(),
354            device: "/dev/sda1".to_string(),
355            model: "Samsung SSD".to_string(),
356            revision: "1.0".to_string(),
357            mountpoint: "/".to_string(),
358            fs_type: "ext4".to_string(),
359            mount_options: "rw,relatime".to_string(),
360            space_total: 1000000000,
361            space_free: 500000000,
362            inode_total: 1000000,
363            inode_free: 800000,
364        };
365
366        assert_eq!(partition.device, "/dev/sda1");
367        assert_eq!(partition.fs_type, "ext4");
368        assert_eq!(partition.space_total, 1000000000);
369        assert_eq!(partition.space_free, 500000000);
370    }
371
372    #[test]
373    fn test_partitions_structure() {
374        let partitions = Partitions {
375            node_common: NodeCommon {
376                addr: "storage-node".to_string(),
377                error: None,
378            },
379            partitions: vec![
380                Partition {
381                    device: "/dev/sda1".to_string(),
382                    mountpoint: "/".to_string(),
383                    space_total: 1000000,
384                    space_free: 500000,
385                    ..Default::default()
386                },
387                Partition {
388                    device: "/dev/sdb1".to_string(),
389                    mountpoint: "/data".to_string(),
390                    space_total: 2000000,
391                    space_free: 1500000,
392                    ..Default::default()
393                },
394            ],
395        };
396
397        assert_eq!(partitions.partitions.len(), 2);
398        assert_eq!(partitions.partitions[0].device, "/dev/sda1");
399        assert_eq!(partitions.partitions[1].mountpoint, "/data");
400    }
401
402    #[test]
403    fn test_get_partitions_function() {
404        let partitions = get_partitions();
405        assert!(partitions.node_common.addr.is_empty());
406        assert!(partitions.partitions.is_empty());
407    }
408
409    #[test]
410    fn test_os_info_default() {
411        let os_info = OsInfo::default();
412        assert!(os_info.node_common.addr.is_empty());
413        assert!(os_info.node_common.error.is_none());
414    }
415
416    #[test]
417    fn test_get_os_info_function() {
418        let os_info = get_os_info();
419        assert!(os_info.node_common.addr.is_empty());
420    }
421
422    #[test]
423    fn test_proc_info_default() {
424        let proc_info = ProcInfo::default();
425        assert_eq!(proc_info.pid, 0);
426        assert!(!proc_info.is_background);
427        assert_eq!(proc_info.cpu_percent, 0.0);
428        assert!(proc_info.children_pids.is_empty());
429        assert!(proc_info.cmd_line.is_empty());
430        assert_eq!(proc_info.num_connections, 0);
431        assert!(!proc_info.is_running);
432        assert_eq!(proc_info.mem_percent, 0.0);
433        assert!(proc_info.name.is_empty());
434        assert_eq!(proc_info.nice, 0);
435        assert_eq!(proc_info.num_fds, 0);
436        assert_eq!(proc_info.num_threads, 0);
437        assert_eq!(proc_info.ppid, 0);
438        assert!(proc_info.status.is_empty());
439        assert_eq!(proc_info.tgid, 0);
440        assert!(proc_info.uids.is_empty());
441        assert!(proc_info.username.is_empty());
442    }
443
444    #[test]
445    fn test_proc_info_with_values() {
446        let proc_info = ProcInfo {
447            node_common: NodeCommon {
448                addr: "worker-node".to_string(),
449                error: None,
450            },
451            pid: 1234,
452            is_background: true,
453            cpu_percent: 15.5,
454            children_pids: vec![1235, 1236],
455            cmd_line: "rustfs --config /etc/rustfs.conf".to_string(),
456            num_connections: 10,
457            create_time: 1640995200,
458            cwd: "/opt/rustfs".to_string(),
459            exec_path: "/usr/bin/rustfs".to_string(),
460            gids: vec![1000, 1001],
461            is_running: true,
462            mem_percent: 8.2,
463            name: "rustfs".to_string(),
464            nice: 0,
465            num_fds: 25,
466            num_threads: 4,
467            ppid: 1,
468            status: "running".to_string(),
469            tgid: 1234,
470            uids: vec![1000],
471            username: "rustfs".to_string(),
472        };
473
474        assert_eq!(proc_info.pid, 1234);
475        assert!(proc_info.is_background);
476        assert_eq!(proc_info.cpu_percent, 15.5);
477        assert_eq!(proc_info.children_pids.len(), 2);
478        assert_eq!(proc_info.name, "rustfs");
479        assert!(proc_info.is_running);
480    }
481
482    #[test]
483    fn test_get_proc_info_function() {
484        let proc_info = get_proc_info("127.0.0.1:9000");
485        assert_eq!(proc_info.pid, 0);
486        assert!(!proc_info.is_running);
487    }
488
489    #[test]
490    fn test_sys_service_default() {
491        let service = SysService::default();
492        assert!(service.name.is_empty());
493        assert!(service.status.is_empty());
494    }
495
496    #[test]
497    fn test_sys_service_with_values() {
498        let service = SysService {
499            name: "rustfs".to_string(),
500            status: "active".to_string(),
501        };
502
503        assert_eq!(service.name, "rustfs");
504        assert_eq!(service.status, "active");
505    }
506
507    #[test]
508    fn test_sys_services_structure() {
509        let services = SysServices {
510            node_common: NodeCommon {
511                addr: "service-node".to_string(),
512                error: None,
513            },
514            services: vec![
515                SysService {
516                    name: "rustfs".to_string(),
517                    status: "active".to_string(),
518                },
519                SysService {
520                    name: "nginx".to_string(),
521                    status: "inactive".to_string(),
522                },
523            ],
524        };
525
526        assert_eq!(services.services.len(), 2);
527        assert_eq!(services.services[0].name, "rustfs");
528        assert_eq!(services.services[1].status, "inactive");
529    }
530
531    #[test]
532    fn test_get_sys_services_function() {
533        let services = get_sys_services("localhost");
534        assert!(services.node_common.addr.is_empty());
535        assert!(services.services.is_empty());
536    }
537
538    #[test]
539    fn test_sys_config_default() {
540        let config = SysConfig::default();
541        assert!(config.node_common.addr.is_empty());
542        assert!(config.config.is_empty());
543    }
544
545    #[test]
546    fn test_sys_config_with_values() {
547        let mut config_map = HashMap::new();
548        config_map.insert("max_connections".to_string(), "1000".to_string());
549        config_map.insert("timeout".to_string(), "30".to_string());
550
551        let config = SysConfig {
552            node_common: NodeCommon {
553                addr: "config-node".to_string(),
554                error: None,
555            },
556            config: config_map,
557        };
558
559        assert_eq!(config.config.len(), 2);
560        assert_eq!(config.config.get("max_connections").unwrap(), "1000");
561        assert_eq!(config.config.get("timeout").unwrap(), "30");
562    }
563
564    #[test]
565    fn test_get_sys_config_function() {
566        let config = get_sys_config("192.168.1.100");
567        assert!(config.node_common.addr.is_empty());
568        assert!(config.config.is_empty());
569    }
570
571    #[test]
572    fn test_sys_errors_default() {
573        let errors = SysErrors::default();
574        assert!(errors.node_common.addr.is_empty());
575        assert!(errors.errors.is_empty());
576    }
577
578    #[test]
579    fn test_sys_errors_with_values() {
580        let errors = SysErrors {
581            node_common: NodeCommon {
582                addr: "error-node".to_string(),
583                error: None,
584            },
585            errors: vec![
586                "Connection timeout".to_string(),
587                "Memory allocation failed".to_string(),
588                "Disk full".to_string(),
589            ],
590        };
591
592        assert_eq!(errors.errors.len(), 3);
593        assert!(errors.errors.contains(&"Connection timeout".to_string()));
594        assert!(errors.errors.contains(&"Disk full".to_string()));
595    }
596
597    #[test]
598    fn test_get_sys_errors_function() {
599        let errors = get_sys_errors("test-node");
600        assert!(errors.node_common.addr.is_empty());
601        assert!(errors.errors.is_empty());
602    }
603
604    #[test]
605    fn test_mem_info_default() {
606        let mem_info = MemInfo::default();
607        assert!(mem_info.node_common.addr.is_empty());
608        assert!(mem_info.total.is_none());
609        assert!(mem_info.used.is_none());
610        assert!(mem_info.free.is_none());
611        assert!(mem_info.available.is_none());
612        assert!(mem_info.shared.is_none());
613        assert!(mem_info.cache.is_none());
614        assert!(mem_info.buffers.is_none());
615        assert!(mem_info.swap_space_total.is_none());
616        assert!(mem_info.swap_space_free.is_none());
617        assert!(mem_info.limit.is_none());
618    }
619
620    #[test]
621    fn test_mem_info_with_values() {
622        let mem_info = MemInfo {
623            node_common: NodeCommon {
624                addr: "memory-node".to_string(),
625                error: None,
626            },
627            total: Some(16777216000),
628            used: Some(8388608000),
629            free: Some(4194304000),
630            available: Some(12582912000),
631            shared: Some(1048576000),
632            cache: Some(2097152000),
633            buffers: Some(524288000),
634            swap_space_total: Some(4294967296),
635            swap_space_free: Some(2147483648),
636            limit: Some(16777216000),
637        };
638
639        assert_eq!(mem_info.total.unwrap(), 16777216000);
640        assert_eq!(mem_info.used.unwrap(), 8388608000);
641        assert_eq!(mem_info.free.unwrap(), 4194304000);
642        assert_eq!(mem_info.swap_space_total.unwrap(), 4294967296);
643    }
644
645    #[test]
646    fn test_mem_info_serialization() {
647        let mem_info = MemInfo {
648            node_common: NodeCommon {
649                addr: "test-node".to_string(),
650                error: None,
651            },
652            total: Some(8000000000),
653            used: Some(4000000000),
654            free: None,
655            available: Some(6000000000),
656            ..Default::default()
657        };
658
659        let json = serde_json::to_string(&mem_info).unwrap();
660        assert!(json.contains("8000000000"));
661        assert!(json.contains("4000000000"));
662        assert!(json.contains("6000000000"));
663        assert!(!json.contains("free"), "None values should be skipped");
664    }
665
666    #[test]
667    fn test_get_mem_info_function() {
668        let mem_info = get_mem_info("memory-server");
669        assert!(mem_info.node_common.addr.is_empty());
670        assert!(mem_info.total.is_none());
671        assert!(mem_info.used.is_none());
672    }
673
674    #[test]
675    fn test_all_structures_debug_format() {
676        let node = NodeCommon::default();
677        let cpu = Cpu::default();
678        let partition = Partition::default();
679        let proc_info = ProcInfo::default();
680        let service = SysService::default();
681        let mem_info = MemInfo::default();
682
683        // Test that all structures can be formatted with Debug
684        assert!(!format!("{node:?}").is_empty());
685        assert!(!format!("{cpu:?}").is_empty());
686        assert!(!format!("{partition:?}").is_empty());
687        assert!(!format!("{proc_info:?}").is_empty());
688        assert!(!format!("{service:?}").is_empty());
689        assert!(!format!("{mem_info:?}").is_empty());
690    }
691
692    #[test]
693    fn test_memory_efficiency() {
694        // Test that structures don't use excessive memory
695        assert!(std::mem::size_of::<NodeCommon>() < 1000);
696        assert!(std::mem::size_of::<Cpu>() < 2000);
697        assert!(std::mem::size_of::<Partition>() < 2000);
698        assert!(std::mem::size_of::<MemInfo>() < 1000);
699    }
700}