kaspa_utils/
sysinfo.rs

1use crate::fd_budget;
2use crate::git;
3use crate::hex::ToHex;
4use sha2::{Digest, Sha256};
5use std::fs::{read_to_string, File};
6use std::io::Read;
7use std::path::PathBuf;
8// use std::fs::read_to_string;
9use std::sync::OnceLock;
10
11static SYSTEM_INFO: OnceLock<SystemInfo> = OnceLock::new();
12
13#[derive(Clone)]
14pub struct SystemInfo {
15    /// unique system (machine) identifier
16    pub system_id: Option<Vec<u8>>,
17    /// full git commit hash
18    pub git_hash: Option<Vec<u8>>,
19    /// short git commit hash
20    pub git_short_hash: Option<Vec<u8>>,
21    /// crate (workspace) version
22    pub version: String,
23    /// number of physical CPU cores
24    pub cpu_physical_cores: u16,
25    /// total system memory in bytes
26    pub total_memory: u64,
27    /// file descriptor limit of the current process
28    pub fd_limit: u32,
29    /// maximum number of sockets per CPU core
30    pub proxy_socket_limit_per_cpu_core: Option<u32>,
31}
32
33// provide hex encoding for system_id, git_hash, and git_short_hash
34impl std::fmt::Debug for SystemInfo {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.debug_struct("SystemInfo")
37            .field("system_id", &self.system_id.as_ref().map(|id| id.to_hex()))
38            .field("git_hash", &self.git_hash.as_ref().map(|hash| hash.to_hex()))
39            .field("git_short_hash", &self.git_short_hash.as_ref().map(|hash| hash.to_hex()))
40            .field("version", &self.version)
41            .field("cpu_physical_cores", &self.cpu_physical_cores)
42            .field("total_memory", &self.total_memory)
43            .field("fd_limit", &self.fd_limit)
44            .field("proxy_socket_limit_per_cpu_core", &self.proxy_socket_limit_per_cpu_core)
45            .finish()
46    }
47}
48
49impl Default for SystemInfo {
50    fn default() -> Self {
51        let system_info = SYSTEM_INFO.get_or_init(|| {
52            let mut system = sysinfo::System::new();
53            system.refresh_memory();
54            let cpu_physical_cores = num_cpus::get() as u16;
55            let total_memory = system.total_memory();
56            let fd_limit = fd_budget::limit() as u32;
57            let system_id = Self::try_system_id();
58            let git_hash = git::hash();
59            let git_short_hash = git::short_hash();
60            let version = git::version();
61            let proxy_socket_limit_per_cpu_core = Self::try_proxy_socket_limit_per_cpu_core();
62
63            SystemInfo {
64                system_id,
65                git_hash,
66                git_short_hash,
67                version,
68                cpu_physical_cores,
69                total_memory,
70                fd_limit,
71                proxy_socket_limit_per_cpu_core,
72            }
73        });
74        (*system_info).clone()
75    }
76}
77
78impl SystemInfo {
79    /// Obtain a unique system (machine) identifier.
80    fn try_system_id() -> Option<Vec<u8>> {
81        let some_id = if let Ok(mut file) = File::open("/etc/machine-id") {
82            // fetch the system id from /etc/machine-id
83            let mut machine_id = String::new();
84            file.read_to_string(&mut machine_id).ok();
85            machine_id.trim().to_string()
86        } else if let Ok(Some(mac)) = mac_address::get_mac_address() {
87            // fallback on the mac address
88            mac.to_string().trim().to_string()
89        } else {
90            // 🤷
91            return None;
92        };
93        let mut sha256 = Sha256::default();
94        sha256.update(some_id.as_bytes());
95        Some(sha256.finalize().to_vec())
96    }
97
98    fn try_proxy_socket_limit_per_cpu_core() -> Option<u32> {
99        let nginx_config_path = PathBuf::from("/etc/nginx/nginx.conf");
100        if nginx_config_path.exists() {
101            read_to_string(nginx_config_path)
102                .ok()
103                .and_then(|content| content.lines().find(|line| line.trim().starts_with("worker_connections")).map(String::from))
104                .and_then(|line| line.split_whitespace().nth(1).map(|v| v.replace(";", "")))
105                .and_then(|value| value.parse::<u32>().ok())
106        } else {
107            None
108        }
109    }
110}
111
112impl AsRef<SystemInfo> for SystemInfo {
113    fn as_ref(&self) -> &SystemInfo {
114        self
115    }
116}
117
118// #[cfg(test)]
119// mod tests {
120//     use super::*;
121
122//     #[test]
123//     fn test_system_info() {
124//         let system_info = SystemInfo::default();
125//         println!("{:#?}", system_info);
126//     }
127// }