1use crate::error::{CoreError, CoreResult};
7#[cfg(target_os = "linux")]
8use std::fs;
9
10#[derive(Debug, Clone)]
12pub struct CpuInfo {
13 pub model: String,
15 pub vendor: String,
17 pub physical_cores: usize,
19 pub logical_cores: usize,
21 pub base_frequency_ghz: f64,
23 pub max_frequency_ghz: f64,
25 pub cache_l1_kb: usize,
27 pub cache_l2_kb: usize,
29 pub cache_l3_kb: usize,
31 pub simd_capabilities: SimdCapabilities,
33 pub architecture: CpuArchitecture,
35 pub features: Vec<String>,
37}
38
39impl Default for CpuInfo {
40 fn default() -> Self {
41 Self {
42 model: "Unknown CPU".to_string(),
43 vendor: "Unknown".to_string(),
44 physical_cores: 1,
45 logical_cores: 1,
46 base_frequency_ghz: 1.0,
47 max_frequency_ghz: 1.0,
48 cache_l1_kb: 32,
49 cache_l2_kb: 256,
50 cache_l3_kb: 1024,
51 simd_capabilities: SimdCapabilities::default(),
52 architecture: CpuArchitecture::Unknown,
53 features: Vec::new(),
54 }
55 }
56}
57
58impl CpuInfo {
59 pub fn detect() -> CoreResult<Self> {
61 #[cfg(target_os = "linux")]
62 return Self::detect_linux();
63
64 #[cfg(target_os = "windows")]
65 return Self::detect_windows();
66
67 #[cfg(target_os = "macos")]
68 return Self::detect_macos();
69
70 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
71 return Ok(Self::default());
72
73 fn parse_cache_size(sizestr: &str) -> Option<u64> {
74 let parts: Vec<&str> = sizestr.split_whitespace().collect();
75 if parts.is_empty() {
76 return None;
77 }
78
79 let number = parts[0].parse::<u64>().ok()?;
80
81 if parts.len() > 1 {
83 match parts[1] {
84 "K" | "KB" => Some(number * 1024),
85 "M" | "MB" => Some(number * 1024 * 1024),
86 "G" | "GB" => Some(number * 1024 * 1024 * 1024),
87 _ => Some(number),
88 }
89 } else {
90 Some(number)
91 }
92 }
93 }
94
95 #[cfg(target_os = "linux")]
97 fn detect_linux() -> CoreResult<Self> {
98 let cpuinfo = fs::read_to_string("/proc/cpuinfo").map_err(|e| {
99 CoreError::IoError(crate::error::ErrorContext::new(format!(
100 "Failed to read /proc/cpuinfo: {e}"
101 )))
102 })?;
103
104 let mut model = "Unknown CPU".to_string();
105 let mut vendor = "Unknown".to_string();
106 let mut logical_cores = 0;
107 let mut cache_l1_kb = 32;
108 let mut cache_l2_kb = 256;
109 let mut cache_l3_kb = 1024;
110 let mut flags = Vec::new();
111
112 for line in cpuinfo.lines() {
113 if line.starts_with("model name") {
114 if let Some(value) = line.split(':').nth(1) {
115 model = value.trim().to_string();
116 }
117 } else if line.starts_with("vendor_id") {
118 if let Some(value) = line.split(':').nth(1) {
119 vendor = value.trim().to_string();
120 }
121 } else if line.starts_with("processor") {
122 logical_cores += 1;
123 } else if line.starts_with("flags") {
124 if let Some(value) = line.split(':').nth(1) {
125 flags = value.split_whitespace().map(|s| s.to_string()).collect();
126 }
127 }
128 }
129
130 if let Ok(cache_info) = Self::read_cache_info_linux() {
132 cache_l1_kb = cache_info.0;
133 cache_l2_kb = cache_info.1;
134 cache_l3_kb = cache_info.2;
135 }
136
137 let physical_cores = Self::get_physical_cores_linux().unwrap_or(logical_cores);
139
140 let (base_freq, max_freq) = Self::get_frequency_info_linux().unwrap_or((2.0, 2.0));
142
143 let simd_capabilities = SimdCapabilities::from_flags(&flags);
145
146 let architecture = CpuArchitecture::detect();
148
149 Ok(Self {
150 model,
151 vendor,
152 physical_cores,
153 logical_cores,
154 base_frequency_ghz: base_freq,
155 max_frequency_ghz: max_freq,
156 cache_l1_kb,
157 cache_l2_kb,
158 cache_l3_kb,
159 simd_capabilities,
160 architecture,
161 features: flags,
162 })
163 }
164
165 #[cfg(target_os = "linux")]
167 fn read_cache_info_linux() -> CoreResult<(usize, usize, usize)> {
168 let mut l1_kb = 32;
169 let mut l2_kb = 256;
170 let mut l3_kb = 1024;
171
172 if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index0/size") {
174 if let Ok(size) = Self::parse_cache_size(content.trim()) {
175 l1_kb = size;
176 }
177 }
178
179 if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index2/size") {
180 if let Ok(size) = Self::parse_cache_size(content.trim()) {
181 l2_kb = size;
182 }
183 }
184
185 if let Ok(content) = fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index3/size") {
186 if let Ok(size) = Self::parse_cache_size(content.trim()) {
187 l3_kb = size;
188 }
189 }
190
191 Ok((l1_kb, l2_kb, l3_kb))
192 }
193
194 #[allow(dead_code)]
196 fn parse_cache_size(sizestr: &str) -> CoreResult<usize> {
197 if sizestr.ends_with('K') || sizestr.ends_with('k') {
198 let num_str = &sizestr[..sizestr.len() - 1];
199 let size = num_str.parse::<usize>().map_err(|e| {
200 CoreError::ValidationError(crate::error::ErrorContext::new(format!(
201 "Failed to parse cache size: {e}"
202 )))
203 })?;
204 Ok(size)
205 } else if sizestr.ends_with('M') || sizestr.ends_with('m') {
206 let num_str = &sizestr[..sizestr.len() - 1];
207 let size = num_str.parse::<usize>().map_err(|e| {
208 CoreError::ValidationError(crate::error::ErrorContext::new(format!(
209 "Failed to parse cache size: {e}"
210 )))
211 })? * 1024;
212 Ok(size)
213 } else {
214 let size = sizestr.parse::<usize>().map_err(|e| {
215 CoreError::ValidationError(crate::error::ErrorContext::new(format!(
216 "Failed to parse cache size: {e}"
217 )))
218 })?;
219 Ok(size)
220 }
221 }
222
223 #[cfg(target_os = "linux")]
225 fn get_physical_cores_linux() -> CoreResult<usize> {
226 if let Ok(content) = fs::read_to_string("/proc/cpuinfo") {
227 let mut core_ids = std::collections::HashSet::new();
228 for line in content.lines() {
229 if line.starts_with("core id") {
230 if let Some(value) = line.split(':').nth(1) {
231 if let Ok(core_id) = value.trim().parse::<usize>() {
232 core_ids.insert(core_id);
233 }
234 }
235 }
236 }
237 if !core_ids.is_empty() {
238 return Ok(core_ids.len());
239 }
240 }
241
242 Ok(std::thread::available_parallelism()
244 .map(|n| n.get())
245 .unwrap_or(1))
246 }
247
248 #[cfg(target_os = "linux")]
250 fn get_frequency_info_linux() -> CoreResult<(f64, f64)> {
251 let mut base_freq = 2.0;
252 let mut max_freq = 2.0;
253
254 if let Ok(content) =
256 fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency")
257 {
258 if let Ok(freq_khz) = content.trim().parse::<f64>() {
259 base_freq = freq_khz / 1_000_000.0; }
261 }
262
263 if let Ok(content) =
264 fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")
265 {
266 if let Ok(freq_khz) = content.trim().parse::<f64>() {
267 max_freq = freq_khz / 1_000_000.0; }
269 }
270
271 Ok((base_freq, max_freq))
272 }
273
274 #[cfg(target_os = "windows")]
276 fn detect_windows() -> CoreResult<Self> {
277 let logical_cores = std::thread::available_parallelism()
280 .map(|n| n.get())
281 .unwrap_or(1);
282
283 let physical_cores = logical_cores / 2; Ok(Self {
286 model: "Windows CPU".to_string(),
287 vendor: "Unknown".to_string(),
288 physical_cores,
289 logical_cores,
290 base_frequency_ghz: 2.5,
291 max_frequency_ghz: 3.0,
292 cache_l1_kb: 32,
293 cache_l2_kb: 256,
294 cache_l3_kb: 1024,
295 simd_capabilities: SimdCapabilities::detect(),
296 architecture: CpuArchitecture::detect(),
297 features: Vec::new(),
298 })
299 }
300
301 #[cfg(target_os = "macos")]
303 fn detect_macos() -> CoreResult<Self> {
304 let logical_cores = std::thread::available_parallelism()
307 .map(|n| n.get())
308 .unwrap_or(1);
309
310 let physical_cores = logical_cores; Ok(Self {
313 model: "macOS CPU".to_string(),
314 vendor: "Apple".to_string(),
315 physical_cores,
316 logical_cores,
317 base_frequency_ghz: 2.5,
318 max_frequency_ghz: 3.0,
319 cache_l1_kb: 128,
320 cache_l2_kb: 4096,
321 cache_l3_kb: 0, simd_capabilities: SimdCapabilities::detect(),
323 architecture: CpuArchitecture::detect(),
324 features: Vec::new(),
325 })
326 }
327
328 pub fn performance_score(&self) -> f64 {
330 let core_score = (self.physical_cores as f64 / 16.0).min(1.0); let freq_score = (self.max_frequency_ghz / 4.0).min(1.0); let cache_score = (self.cache_l3_kb as f64 / 32768.0).min(1.0); let simd_score = if self.simd_capabilities.avx512 {
334 1.0
335 } else if self.simd_capabilities.avx2 {
336 0.8
337 } else if self.simd_capabilities.sse4_2 {
338 0.6
339 } else {
340 0.3
341 };
342
343 (core_score + freq_score + cache_score + simd_score) / 4.0
344 }
345
346 pub fn optimal_thread_count(&self) -> usize {
348 let base_threads = self.physical_cores;
351 let io_threads = (self.logical_cores - self.physical_cores).min(2);
352 base_threads + io_threads
353 }
354
355 pub fn optimal_chunk_size(&self) -> usize {
357 let l2_bytes = self.cache_l2_kb * 1024;
359 (l2_bytes * 3 / 4).max(4096) }
361
362 pub fn supports_instruction_set(&self, instructionset: &str) -> bool {
364 self.features
365 .iter()
366 .any(|f| f.eq_ignore_ascii_case(instructionset))
367 }
368}
369
370#[derive(Debug, Clone, Default)]
372pub struct SimdCapabilities {
373 pub sse4_2: bool,
375 pub avx2: bool,
377 pub avx512: bool,
379 pub neon: bool,
381}
382
383impl SimdCapabilities {
384 pub fn from_flags(flags: &[String]) -> Self {
386 let mut capabilities = Self::default();
387
388 for flag in flags {
389 match flag.as_str() {
390 "sse4_2" => capabilities.sse4_2 = true,
391 "avx2" => capabilities.avx2 = true,
392 "avx512f" | "avx512" => capabilities.avx512 = true,
393 "neon" => capabilities.neon = true,
394 _ => {}
395 }
396 }
397
398 capabilities
399 }
400
401 pub fn detect() -> Self {
403 let mut capabilities = Self::default();
404
405 #[cfg(target_arch = "x86_64")]
406 {
407 if is_x86_feature_detected!("sse4.2") {
408 capabilities.sse4_2 = true;
409 }
410 if is_x86_feature_detected!("avx2") {
411 capabilities.avx2 = true;
412 }
413 if is_x86_feature_detected!("avx512f") {
414 capabilities.avx512 = true;
415 }
416 }
417
418 #[cfg(target_arch = "aarch64")]
419 {
420 capabilities.neon = true;
422 }
423
424 capabilities
425 }
426
427 pub fn best_available(&self) -> SimdInstructionSet {
429 if self.avx512 {
430 SimdInstructionSet::Avx512
431 } else if self.avx2 {
432 SimdInstructionSet::Avx2
433 } else if self.sse4_2 {
434 SimdInstructionSet::Sse42
435 } else if self.neon {
436 SimdInstructionSet::Neon
437 } else {
438 SimdInstructionSet::None
439 }
440 }
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445pub enum SimdInstructionSet {
446 None,
448 Sse42,
450 Avx2,
452 Avx512,
454 Neon,
456}
457
458impl SimdInstructionSet {
459 pub fn vector_width_bytes(&self) -> usize {
461 match self {
462 SimdInstructionSet::None => 8,
463 SimdInstructionSet::Sse42 => 16,
464 SimdInstructionSet::Avx2 => 32,
465 SimdInstructionSet::Avx512 => 64,
466 SimdInstructionSet::Neon => 16,
467 }
468 }
469
470 pub fn vector_width_f32(&self) -> usize {
472 self.vector_width_bytes() / 4
473 }
474
475 pub fn vector_width_f64(&self) -> usize {
477 self.vector_width_bytes() / 8
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
483pub enum CpuArchitecture {
484 X86_64,
486 AArch64,
488 X86,
490 Arm,
492 Unknown,
494}
495
496impl CpuArchitecture {
497 pub fn detect() -> Self {
499 #[cfg(target_arch = "x86_64")]
500 return CpuArchitecture::X86_64;
501
502 #[cfg(target_arch = "aarch64")]
503 return CpuArchitecture::AArch64;
504
505 #[cfg(target_arch = "x86")]
506 return CpuArchitecture::X86;
507
508 #[cfg(target_arch = "arm")]
509 return CpuArchitecture::Arm;
510
511 #[cfg(not(any(
512 target_arch = "x86_64",
513 target_arch = "aarch64",
514 target_arch = "x86",
515 target_arch = "arm"
516 )))]
517 return CpuArchitecture::Unknown;
518 }
519
520 pub fn supports_64bit(&self) -> bool {
522 matches!(self, CpuArchitecture::X86_64 | CpuArchitecture::AArch64)
523 }
524
525 pub fn pointer_size(&self) -> usize {
527 match self {
528 CpuArchitecture::X86_64 | CpuArchitecture::AArch64 => 8,
529 CpuArchitecture::X86 | CpuArchitecture::Arm => 4,
530 CpuArchitecture::Unknown => std::mem::size_of::<usize>(),
531 }
532 }
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538
539 #[test]
540 fn test_cpu_detection() {
541 let cpuinfo = CpuInfo::detect();
542 assert!(cpuinfo.is_ok());
543
544 let cpu = cpuinfo.unwrap();
545 assert!(cpu.logical_cores > 0);
546 assert!(cpu.physical_cores > 0);
547 assert!(cpu.physical_cores <= cpu.logical_cores);
548 }
549
550 #[test]
551 fn test_simd_detection() {
552 let simd = SimdCapabilities::detect();
553 let best = simd.best_available();
554
555 assert_ne!(best, SimdInstructionSet::None);
557 }
558
559 #[test]
560 fn test_architecture_detection() {
561 let arch = CpuArchitecture::detect();
562 assert_ne!(arch, CpuArchitecture::Unknown);
563
564 assert!(arch.pointer_size() > 0);
566 assert_eq!(arch.pointer_size(), std::mem::size_of::<usize>());
567 }
568
569 #[test]
570 fn test_performance_score() {
571 let cpu = CpuInfo::default();
572 let score = cpu.performance_score();
573 assert!((0.0..=1.0).contains(&score));
574 }
575
576 #[test]
577 fn test_optimal_parameters() {
578 let cpu = CpuInfo::default();
579
580 let thread_count = cpu.optimal_thread_count();
581 assert!(thread_count > 0);
582
583 let chunk_size = cpu.optimal_chunk_size();
584 assert!(chunk_size >= 4096);
585 }
586
587 #[test]
588 fn test_vector_widths() {
589 let avx2 = SimdInstructionSet::Avx2;
590 assert_eq!(avx2.vector_width_bytes(), 32);
591 assert_eq!(avx2.vector_width_f32(), 8);
592 assert_eq!(avx2.vector_width_f64(), 4);
593 }
594
595 #[test]
596 fn test_cache_size_parsing() {
597 assert_eq!(CpuInfo::parse_cache_size("32K").unwrap(), 32);
598 assert_eq!(CpuInfo::parse_cache_size("256k").unwrap(), 256);
599 assert_eq!(CpuInfo::parse_cache_size("8M").unwrap(), 8192);
600 assert_eq!(CpuInfo::parse_cache_size("1024").unwrap(), 1024);
601 }
602}