1use crate::{HardwareQueryError, Result};
3use std::collections::HashMap;
4use std::fs;
5use std::process::Command;
6
7#[derive(Debug, Clone)]
9pub struct LinuxCPUInfo {
10 pub vendor_id: String,
11 pub model_name: String,
12 pub cpu_family: Option<u32>,
13 pub model: Option<u32>,
14 pub stepping: Option<u32>,
15 pub microcode: Option<String>,
16 pub cpu_cores: u32,
17 pub siblings: u32,
18 pub core_id: Vec<u32>,
19 pub apicid: Vec<u32>,
20 pub initial_apicid: Vec<u32>,
21 pub cpu_mhz: f32,
22 pub cache_size: u32,
23 pub physical_id: Vec<u32>,
24 pub flags: Vec<String>,
25 pub bogomips: f32,
26 pub clflush_size: Option<u32>,
27 pub cache_alignment: Option<u32>,
28 pub address_sizes: Option<String>,
29 pub power_management: Option<String>,
30 pub vulnerabilities: Vec<String>,
31}
32
33impl LinuxCPUInfo {
34 pub fn query() -> Result<Self> {
36 let cpuinfo_content = fs::read_to_string("/proc/cpuinfo").map_err(|e| {
37 HardwareQueryError::system_info_unavailable(format!("Cannot read /proc/cpuinfo: {}", e))
38 })?;
39
40 let mut cpu_info = LinuxCPUInfo {
41 vendor_id: String::new(),
42 model_name: String::new(),
43 cpu_family: None,
44 model: None,
45 stepping: None,
46 microcode: None,
47 cpu_cores: 0,
48 siblings: 0,
49 core_id: Vec::new(),
50 apicid: Vec::new(),
51 initial_apicid: Vec::new(),
52 cpu_mhz: 0.0,
53 cache_size: 0,
54 physical_id: Vec::new(),
55 flags: Vec::new(),
56 bogomips: 0.0,
57 clflush_size: None,
58 cache_alignment: None,
59 address_sizes: None,
60 power_management: None,
61 vulnerabilities: Vec::new(),
62 };
63
64 for line in cpuinfo_content.lines() {
66 if let Some((key, value)) = line.split_once(':') {
67 let key = key.trim();
68 let value = value.trim();
69
70 match key {
71 "vendor_id" => cpu_info.vendor_id = value.to_string(),
72 "model name" => cpu_info.model_name = value.to_string(),
73 "cpu family" => cpu_info.cpu_family = value.parse().ok(),
74 "model" => cpu_info.model = value.parse().ok(),
75 "stepping" => cpu_info.stepping = value.parse().ok(),
76 "microcode" => cpu_info.microcode = Some(value.to_string()),
77 "cpu cores" => cpu_info.cpu_cores = value.parse().unwrap_or(0),
78 "siblings" => cpu_info.siblings = value.parse().unwrap_or(0),
79 "core id" => cpu_info.core_id.push(value.parse().unwrap_or(0)),
80 "apicid" => cpu_info.apicid.push(value.parse().unwrap_or(0)),
81 "initial apicid" => cpu_info.initial_apicid.push(value.parse().unwrap_or(0)),
82 "cpu MHz" => cpu_info.cpu_mhz = value.parse().unwrap_or(0.0),
83 "cache size" => {
84 if let Some(size_str) = value.split_whitespace().next() {
86 cpu_info.cache_size = size_str.parse().unwrap_or(0);
87 }
88 }
89 "physical id" => cpu_info.physical_id.push(value.parse().unwrap_or(0)),
90 "flags" => {
91 cpu_info.flags = value.split_whitespace().map(|s| s.to_string()).collect();
92 }
93 "bogomips" => cpu_info.bogomips = value.parse().unwrap_or(0.0),
94 "clflush size" => cpu_info.clflush_size = value.parse().ok(),
95 "cache_alignment" => cpu_info.cache_alignment = value.parse().ok(),
96 "address sizes" => cpu_info.address_sizes = Some(value.to_string()),
97 "power management" => cpu_info.power_management = Some(value.to_string()),
98 _ => {}
99 }
100 }
101 }
102
103 cpu_info.vulnerabilities = Self::get_vulnerabilities()?;
105
106 Ok(cpu_info)
107 }
108
109 pub fn get_vulnerabilities() -> Result<Vec<String>> {
111 let mut vulnerabilities = Vec::new();
112
113 if let Ok(entries) = fs::read_dir("/sys/devices/system/cpu/vulnerabilities") {
114 for entry in entries.flatten() {
115 if let Ok(file_type) = entry.file_type() {
116 if file_type.is_file() {
117 if let Some(vuln_name) = entry.file_name().to_str() {
118 if let Ok(status) = fs::read_to_string(entry.path()) {
119 let status = status.trim();
120 if !status.starts_with("Not affected")
121 && !status.starts_with("Mitigation")
122 {
123 vulnerabilities.push(format!("{}: {}", vuln_name, status));
124 }
125 }
126 }
127 }
128 }
129 }
130 }
131
132 Ok(vulnerabilities)
133 }
134
135 pub fn get_cache_info() -> Result<HashMap<String, u32>> {
137 let mut cache_info = HashMap::new();
138
139 for level in 1..=3 {
140 for cache_type in &["data", "instruction", "unified"] {
141 let cache_path = format!("/sys/devices/system/cpu/cpu0/cache/index{}/", level);
142
143 if let Ok(type_content) = fs::read_to_string(format!("{}type", cache_path)) {
145 if type_content.trim() == *cache_type || type_content.trim() == "Unified" {
146 if let Ok(size_content) = fs::read_to_string(format!("{}size", cache_path))
147 {
148 let size_str = size_content.trim().replace("K", "");
149 if let Ok(size) = size_str.parse::<u32>() {
150 let key = format!("L{} {}", level, cache_type);
151 cache_info.insert(key, size);
152 }
153 }
154 }
155 }
156 }
157 }
158
159 Ok(cache_info)
160 }
161
162 pub fn get_frequency_info() -> Result<HashMap<String, u32>> {
164 let mut freq_info = HashMap::new();
165
166 if let Ok(min_freq) =
168 fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq")
169 {
170 if let Ok(freq) = min_freq.trim().parse::<u32>() {
171 freq_info.insert("min_frequency_khz".to_string(), freq);
172 }
173 }
174
175 if let Ok(max_freq) =
176 fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
177 {
178 if let Ok(freq) = max_freq.trim().parse::<u32>() {
179 freq_info.insert("max_frequency_khz".to_string(), freq);
180 }
181 }
182
183 if let Ok(cur_freq) =
184 fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")
185 {
186 if let Ok(freq) = cur_freq.trim().parse::<u32>() {
187 freq_info.insert("current_frequency_khz".to_string(), freq);
188 }
189 }
190
191 Ok(freq_info)
192 }
193
194 pub fn get_temperature() -> Result<Option<f32>> {
196 let thermal_paths = [
198 "/sys/class/thermal/thermal_zone0/temp",
199 "/sys/class/hwmon/hwmon0/temp1_input",
200 "/sys/class/hwmon/hwmon1/temp1_input",
201 ];
202
203 for path in &thermal_paths {
204 if let Ok(temp_str) = fs::read_to_string(path) {
205 if let Ok(temp) = temp_str.trim().parse::<i32>() {
206 return Ok(Some(temp as f32 / 1000.0));
208 }
209 }
210 }
211
212 if let Ok(output) = Command::new("sensors").arg("-A").arg("-u").output() {
214 let output_str = String::from_utf8_lossy(&output.stdout);
215 for line in output_str.lines() {
216 if line.contains("temp1_input") || line.contains("Core 0") {
217 if let Some(temp_str) = line.split(':').nth(1) {
218 if let Ok(temp) = temp_str.trim().parse::<f32>() {
219 return Ok(Some(temp));
220 }
221 }
222 }
223 }
224 }
225
226 Ok(None)
227 }
228}
229
230#[derive(Debug, Clone)]
232pub struct LinuxGPUInfo {
233 pub device_name: String,
234 pub vendor_name: String,
235 pub driver: String,
236 pub pci_id: String,
237 pub memory_info: Option<String>,
238 pub driver_version: Option<String>,
239}
240
241impl LinuxGPUInfo {
242 pub fn query_all() -> Result<Vec<Self>> {
244 let mut gpus = Vec::new();
245
246 if let Ok(output) = Command::new("lspci")
248 .args(["-v", "-s", "$(lspci | grep VGA | cut -d' ' -f1)"])
249 .output()
250 {
251 let output_str = String::from_utf8_lossy(&output.stdout);
252 }
255
256 if let Ok(nvidia_gpus) = Self::query_nvidia_gpus() {
258 gpus.extend(nvidia_gpus);
259 }
260
261 if let Ok(amd_gpus) = Self::query_amd_gpus() {
263 gpus.extend(amd_gpus);
264 }
265
266 Ok(gpus)
267 }
268
269 fn query_nvidia_gpus() -> Result<Vec<Self>> {
271 let mut gpus = Vec::new();
272
273 if let Ok(output) = Command::new("nvidia-smi")
274 .args([
275 "--query-gpu=name,driver_version,memory.total,pci.bus_id",
276 "--format=csv,noheader,nounits",
277 ])
278 .output()
279 {
280 let output_str = String::from_utf8_lossy(&output.stdout);
281 for line in output_str.lines() {
282 let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
283 if parts.len() >= 4 {
284 gpus.push(Self {
285 device_name: parts[0].to_string(),
286 vendor_name: "NVIDIA".to_string(),
287 driver: "nvidia".to_string(),
288 pci_id: parts[3].to_string(),
289 memory_info: Some(format!("{} MB", parts[2])),
290 driver_version: Some(parts[1].to_string()),
291 });
292 }
293 }
294 }
295
296 Ok(gpus)
297 }
298
299 fn query_amd_gpus() -> Result<Vec<Self>> {
301 let mut gpus = Vec::new();
302
303 if let Ok(output) = Command::new("rocm-smi")
304 .args(["--showproductname", "--showdriverversion"])
305 .output()
306 {
307 let output_str = String::from_utf8_lossy(&output.stdout);
308 for line in output_str.lines() {
310 if line.contains("Card series") {
311 }
314 }
315 }
316
317 Ok(gpus)
318 }
319}
320
321#[derive(Debug, Clone)]
323pub struct LinuxMemoryInfo {
324 pub mem_total_kb: u64,
325 pub mem_free_kb: u64,
326 pub mem_available_kb: u64,
327 pub buffers_kb: u64,
328 pub cached_kb: u64,
329 pub swap_total_kb: u64,
330 pub swap_free_kb: u64,
331 pub modules: Vec<LinuxMemoryModule>,
332}
333
334#[derive(Debug, Clone)]
335pub struct LinuxMemoryModule {
336 pub size_mb: u64,
337 pub speed_mhz: Option<u32>,
338 pub memory_type: String,
339 pub locator: String,
340}
341
342impl LinuxMemoryInfo {
343 pub fn query() -> Result<Self> {
345 let meminfo_content = fs::read_to_string("/proc/meminfo").map_err(|e| {
346 HardwareQueryError::system_info_unavailable(format!("Cannot read /proc/meminfo: {}", e))
347 })?;
348
349 let mut mem_info = LinuxMemoryInfo {
350 mem_total_kb: 0,
351 mem_free_kb: 0,
352 mem_available_kb: 0,
353 buffers_kb: 0,
354 cached_kb: 0,
355 swap_total_kb: 0,
356 swap_free_kb: 0,
357 modules: Vec::new(),
358 };
359
360 for line in meminfo_content.lines() {
362 if let Some((key, value)) = line.split_once(':') {
363 let key = key.trim();
364 let value = value.trim().replace(" kB", "");
365
366 if let Ok(val) = value.parse::<u64>() {
367 match key {
368 "MemTotal" => mem_info.mem_total_kb = val,
369 "MemFree" => mem_info.mem_free_kb = val,
370 "MemAvailable" => mem_info.mem_available_kb = val,
371 "Buffers" => mem_info.buffers_kb = val,
372 "Cached" => mem_info.cached_kb = val,
373 "SwapTotal" => mem_info.swap_total_kb = val,
374 "SwapFree" => mem_info.swap_free_kb = val,
375 _ => {}
376 }
377 }
378 }
379 }
380
381 mem_info.modules = Self::get_memory_modules()?;
383
384 Ok(mem_info)
385 }
386
387 fn get_memory_modules() -> Result<Vec<LinuxMemoryModule>> {
389 let mut modules = Vec::new();
390
391 if let Ok(output) = Command::new("dmidecode").args(["-t", "memory"]).output() {
392 let output_str = String::from_utf8_lossy(&output.stdout);
393 let mut current_module: Option<LinuxMemoryModule> = None;
394
395 for line in output_str.lines() {
396 let line = line.trim();
397
398 if line.starts_with("Memory Device") {
399 if let Some(module) = current_module.take() {
400 modules.push(module);
401 }
402 current_module = Some(LinuxMemoryModule {
403 size_mb: 0,
404 speed_mhz: None,
405 memory_type: String::new(),
406 locator: String::new(),
407 });
408 } else if let Some(ref mut module) = current_module {
409 if let Some((key, value)) = line.split_once(':') {
410 let key = key.trim();
411 let value = value.trim();
412
413 match key {
414 "Size" => {
415 if value.contains("MB") {
416 let size_str = value.replace(" MB", "");
417 module.size_mb = size_str.parse().unwrap_or(0);
418 } else if value.contains("GB") {
419 let size_str = value.replace(" GB", "");
420 if let Ok(size_gb) = size_str.parse::<u64>() {
421 module.size_mb = size_gb * 1024;
422 }
423 }
424 }
425 "Speed" => {
426 let speed_str = value.replace(" MHz", "");
427 module.speed_mhz = speed_str.parse().ok();
428 }
429 "Type" => module.memory_type = value.to_string(),
430 "Locator" => module.locator = value.to_string(),
431 _ => {}
432 }
433 }
434 }
435 }
436
437 if let Some(module) = current_module {
438 modules.push(module);
439 }
440 }
441
442 modules.retain(|m| m.size_mb > 0);
444
445 Ok(modules)
446 }
447}