Skip to main content

offline_intelligence/engine_management/
analyzer.rs

1//! Hardware Analyzer
2//!
3//! Enhanced hardware detection and profiling for engine compatibility analysis.
4//! Extends the existing platform detection with detailed capability assessment.
5
6use crate::model_runtime::platform_detector::{HardwareCapabilities, Platform, HardwareArchitecture};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::OnceLock;
10use tracing::{debug, info};
11
12/// Detailed hardware profile for engine compatibility analysis
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct HardwareProfile {
15    pub platform: Platform,
16    pub architecture: HardwareArchitecture,
17    pub cpu_cores: u32,
18    pub total_memory_gb: f32,
19    pub available_memory_gb: f32,
20    pub gpu_info: Option<GPUInfo>,
21    pub acceleration_support: HashMap<String, bool>,
22    pub system_info: SystemInfo,
23}
24
25// Static cache for hardware profiles
26static PROFILE_CACHE: OnceLock<HardwareProfile> = OnceLock::new();
27
28impl HardwareProfile {
29    /// Analyze hardware capabilities to create a detailed profile (cached)
30    pub async fn analyze(capabilities: &HardwareCapabilities) -> Result<Self, Box<dyn std::error::Error>> {
31        // Return cached result if available
32        if let Some(cached) = PROFILE_CACHE.get() {
33            return Ok(cached.clone());
34        }
35        
36        let analyzer = HardwareAnalyzer::new(capabilities.clone());
37        let profile = analyzer.get_hardware_profile();
38        
39        // Cache the result
40        let _ = PROFILE_CACHE.set(profile.clone());
41        
42        Ok(profile)
43    }
44}
45
46/// Information about available GPUs
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct GPUInfo {
49    pub vendor: String,
50    pub model: String,
51    pub memory_gb: f32,
52    pub compute_capability: Option<String>,
53    pub driver_version: Option<String>,
54}
55
56/// General system information
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SystemInfo {
59    pub os_name: String,
60    pub os_version: String,
61    pub kernel_version: Option<String>,
62}
63
64/// Analyzes hardware capabilities for engine compatibility
65pub struct HardwareAnalyzer {
66    capabilities: HardwareCapabilities,
67}
68
69impl HardwareAnalyzer {
70    pub fn new(capabilities: HardwareCapabilities) -> Self {
71        Self { capabilities }
72    }
73
74    /// Get detailed hardware profile
75    pub fn get_hardware_profile(&self) -> HardwareProfile {
76        let system_info = self.detect_system_info();
77        let gpu_info = self.detect_gpu_info();
78        let memory_info = self.detect_memory_info();
79        let acceleration_support = self.detect_acceleration_support(&gpu_info);
80
81        HardwareProfile {
82            platform: self.capabilities.platform.clone(),
83            architecture: self.capabilities.architecture.clone(),
84            cpu_cores: self.detect_cpu_cores(),
85            total_memory_gb: memory_info.total_gb,
86            available_memory_gb: memory_info.available_gb,
87            gpu_info,
88            acceleration_support,
89            system_info,
90        }
91    }
92
93    /// Detect CPU core count
94    fn detect_cpu_cores(&self) -> u32 {
95        let logical_cores = num_cpus::get() as u32;
96        debug!("Detected {} logical CPU cores", logical_cores);
97        logical_cores
98    }
99
100    /// Detect memory information
101    fn detect_memory_info(&self) -> MemoryInfo {
102        let mut system = sysinfo::System::new_all();
103        system.refresh_memory();
104        
105        let total_bytes = system.total_memory();
106        let available_bytes = system.available_memory();
107        
108        let total_gb = total_bytes as f32 / (1024.0 * 1024.0 * 1024.0);
109        let available_gb = available_bytes as f32 / (1024.0 * 1024.0 * 1024.0);
110        
111        debug!("Memory: {:.1}GB total, {:.1}GB available", total_gb, available_gb);
112        
113        MemoryInfo {
114            total_gb,
115            available_gb,
116        }
117    }
118
119    /// Detect GPU information
120    fn detect_gpu_info(&self) -> Option<GPUInfo> {
121        match &self.capabilities.platform {
122            Platform::Windows => self.detect_windows_gpu(),
123            Platform::Linux => self.detect_linux_gpu(),
124            Platform::MacOS => self.detect_macos_gpu(),
125        }
126    }
127
128    /// Detect Windows GPU information
129    fn detect_windows_gpu(&self) -> Option<GPUInfo> {
130        #[cfg(target_os = "windows")]
131        {
132            use std::process::{Command, Stdio};
133            use std::os::windows::process::CommandExt;
134
135            // Use PowerShell instead of wmic (deprecated/removed in Windows 11)
136            // with a timeout to prevent hangs
137            // CREATE_NO_WINDOW flag prevents console window from appearing
138            let child = Command::new("powershell")
139                .args([
140                    "-NoProfile", "-NonInteractive", "-Command",
141                    "Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name"
142                ])
143                .stdout(Stdio::piped())
144                .stderr(Stdio::null())
145                .creation_flags(0x08000000) // CREATE_NO_WINDOW
146                .spawn();
147
148            if let Ok(mut process) = child {
149                let start = std::time::Instant::now();
150                loop {
151                    match process.try_wait() {
152                        Ok(Some(status)) => {
153                            if status.success() {
154                                if let Ok(output) = process.wait_with_output() {
155                                    let stdout = String::from_utf8_lossy(&output.stdout);
156                                    for line in stdout.lines() {
157                                        let trimmed = line.trim();
158                                        if trimmed.contains("NVIDIA") {
159                                            return Some(GPUInfo {
160                                                vendor: "NVIDIA".to_string(),
161                                                model: trimmed.to_string(),
162                                                memory_gb: 0.0,
163                                                compute_capability: None,
164                                                driver_version: None,
165                                            });
166                                        }
167                                    }
168                                }
169                            }
170                            break;
171                        }
172                        Ok(None) => {
173                            if start.elapsed() > std::time::Duration::from_secs(10) {
174                                let _ = process.kill();
175                                let _ = process.wait();
176                                debug!("GPU detection via PowerShell timed out");
177                                break;
178                            }
179                            std::thread::sleep(std::time::Duration::from_millis(100));
180                        }
181                        Err(_) => break,
182                    }
183                }
184            }
185        }
186
187        // Fallback: Check for CUDA availability (already detected without wmic)
188        if self.capabilities.has_cuda {
189            Some(GPUInfo {
190                vendor: "NVIDIA".to_string(),
191                model: "Unknown CUDA GPU".to_string(),
192                memory_gb: 0.0,
193                compute_capability: None,
194                driver_version: None,
195            })
196        } else {
197            None
198        }
199    }
200
201    /// Detect Linux GPU information
202    fn detect_linux_gpu(&self) -> Option<GPUInfo> {
203        use std::process::{Command, Stdio};
204
205        // Try lspci with timeout to prevent hangs
206        let child = Command::new("lspci")
207            .stdout(Stdio::piped())
208            .stderr(Stdio::null())
209            .spawn();
210
211        if let Ok(mut process) = child {
212            let start = std::time::Instant::now();
213            loop {
214                match process.try_wait() {
215                    Ok(Some(_)) => {
216                        if let Ok(output) = process.wait_with_output() {
217                            let stdout = String::from_utf8_lossy(&output.stdout);
218                            for line in stdout.lines() {
219                                if line.contains("VGA compatible controller") || line.contains("3D controller") {
220                                    if line.contains("NVIDIA") {
221                                        return Some(GPUInfo {
222                                            vendor: "NVIDIA".to_string(),
223                                            model: line.split(": ").nth(1).unwrap_or("Unknown").to_string(),
224                                            memory_gb: 0.0,
225                                            compute_capability: None,
226                                            driver_version: None,
227                                        });
228                                    } else if line.contains("AMD") || line.contains("ATI") {
229                                        return Some(GPUInfo {
230                                            vendor: "AMD".to_string(),
231                                            model: line.split(": ").nth(1).unwrap_or("Unknown").to_string(),
232                                            memory_gb: 0.0,
233                                            compute_capability: None,
234                                            driver_version: None,
235                                        });
236                                    }
237                                }
238                            }
239                        }
240                        break;
241                    }
242                    Ok(None) => {
243                        if start.elapsed() > std::time::Duration::from_secs(5) {
244                            let _ = process.kill();
245                            let _ = process.wait();
246                            debug!("lspci GPU detection timed out");
247                            break;
248                        }
249                        std::thread::sleep(std::time::Duration::from_millis(50));
250                    }
251                    Err(_) => break,
252                }
253            }
254        }
255
256        None
257    }
258
259    /// Detect macOS GPU information
260    fn detect_macos_gpu(&self) -> Option<GPUInfo> {
261        #[cfg(target_os = "macos")]
262        {
263            // On macOS, Metal support is the key indicator - skip slow system_profiler call
264            // and use the already-detected has_metal flag from HardwareCapabilities
265            if self.capabilities.has_metal {
266                return Some(GPUInfo {
267                    vendor: "Apple".to_string(),
268                    model: "Integrated GPU".to_string(),
269                    memory_gb: 0.0,
270                    compute_capability: Some("Metal".to_string()),
271                    driver_version: None,
272                });
273            }
274        }
275        
276        None
277    }
278
279    /// Detect acceleration support capabilities
280    fn detect_acceleration_support(&self, _gpu_info: &Option<GPUInfo>) -> HashMap<String, bool> {
281        let mut support = HashMap::new();
282        
283        // CPU support is always available
284        support.insert("cpu".to_string(), true);
285        
286        // Platform-specific acceleration
287        match &self.capabilities.platform {
288            Platform::Windows => {
289                support.insert("cuda".to_string(), self.capabilities.has_cuda);
290                support.insert("directml".to_string(), true); // DirectML is available on Windows
291            }
292            Platform::MacOS => {
293                support.insert("metal".to_string(), self.capabilities.has_metal);
294            }
295            Platform::Linux => {
296                support.insert("cuda".to_string(), self.capabilities.has_cuda);
297                support.insert("vulkan".to_string(), self.capabilities.has_vulkan);
298                // Check for ROCm support on Linux
299                support.insert("rocm".to_string(), self.detect_rocm_support());
300            }
301        }
302        
303        // Check for OpenCL support
304        support.insert("opencl".to_string(), self.detect_opencl_support());
305        
306        debug!("Acceleration support: {:?}", support);
307        support
308    }
309
310    /// Detect ROCm support (AMD GPU acceleration on Linux)
311    fn detect_rocm_support(&self) -> bool {
312        #[cfg(target_os = "linux")]
313        {
314            use std::path::Path;
315            // Check for ROCm installation
316            Path::new("/opt/rocm").exists() || Path::new("/usr/lib/rocm").exists()
317        }
318        #[cfg(not(target_os = "linux"))]
319        {
320            false
321        }
322    }
323
324    /// Detect OpenCL support
325    fn detect_opencl_support(&self) -> bool {
326        // Simplified check - in practice you'd want to enumerate OpenCL platforms
327        match &self.capabilities.platform {
328            Platform::Windows => {
329                // Check for OpenCL ICD loader
330                std::path::Path::new("C:\\Windows\\System32\\OpenCL.dll").exists()
331            }
332            Platform::Linux => {
333                std::path::Path::new("/usr/lib/libOpenCL.so").exists() ||
334                std::path::Path::new("/usr/lib/x86_64-linux-gnu/libOpenCL.so").exists()
335            }
336            Platform::MacOS => {
337                // OpenCL is deprecated on macOS but still available
338                true
339            }
340        }
341    }
342
343    /// Detect system information
344    fn detect_system_info(&self) -> SystemInfo {
345        SystemInfo {
346            os_name: std::env::consts::OS.to_string(),
347            os_version: "Unknown".to_string(),
348            kernel_version: None,
349        }
350    }
351
352    /// Get compatibility recommendations for engines
353    pub fn get_engine_recommendations(&self) -> Vec<EngineRecommendation> {
354        let profile = self.get_hardware_profile();
355        let mut recommendations = Vec::new();
356        
357        // CPU recommendation (always available)
358        recommendations.push(EngineRecommendation {
359            engine_type: "CPU".to_string(),
360            priority: 1,
361            reason: "Universal compatibility".to_string(),
362            estimated_performance: self.estimate_cpu_performance(&profile),
363        });
364        
365        // GPU recommendations based on detected hardware
366        if profile.acceleration_support.get("cuda").copied().unwrap_or(false) {
367            recommendations.push(EngineRecommendation {
368                engine_type: "CUDA".to_string(),
369                priority: 2,
370                reason: "NVIDIA GPU acceleration available".to_string(),
371                estimated_performance: self.estimate_cuda_performance(&profile),
372            });
373        }
374        
375        if profile.acceleration_support.get("metal").copied().unwrap_or(false) {
376            recommendations.push(EngineRecommendation {
377                engine_type: "Metal".to_string(),
378                priority: 2,
379                reason: "Apple Silicon GPU acceleration".to_string(),
380                estimated_performance: self.estimate_metal_performance(&profile),
381            });
382        }
383        
384        if profile.acceleration_support.get("vulkan").copied().unwrap_or(false) {
385            recommendations.push(EngineRecommendation {
386                engine_type: "Vulkan".to_string(),
387                priority: 3,
388                reason: "Cross-platform GPU acceleration".to_string(),
389                estimated_performance: self.estimate_vulkan_performance(&profile),
390            });
391        }
392        
393        // Sort by priority
394        recommendations.sort_by(|a, b| a.priority.cmp(&b.priority));
395        recommendations
396    }
397
398    /// Estimate CPU performance
399    fn estimate_cpu_performance(&self, profile: &HardwareProfile) -> PerformanceEstimate {
400        let core_multiplier = profile.cpu_cores as f32;
401        let memory_multiplier = (profile.available_memory_gb / 8.0).min(4.0);
402        let base_score = 50.0;
403        
404        PerformanceEstimate {
405            inference_speed: base_score * core_multiplier * memory_multiplier * 0.1,
406            memory_efficiency: (profile.available_memory_gb / profile.total_memory_gb) * 100.0,
407            power_efficiency: 70.0, // CPUs are generally less power efficient than GPUs
408        }
409    }
410
411    /// Estimate CUDA performance
412    fn estimate_cuda_performance(&self, profile: &HardwareProfile) -> PerformanceEstimate {
413        if let Some(gpu) = &profile.gpu_info {
414            let memory_score = gpu.memory_gb * 10.0; // Rough estimation
415            let base_score = 80.0;
416            
417            PerformanceEstimate {
418                inference_speed: base_score + memory_score,
419                memory_efficiency: 90.0, // GPUs excel at memory efficiency
420                power_efficiency: 85.0, // Modern GPUs are quite power efficient
421            }
422        } else {
423            PerformanceEstimate {
424                inference_speed: 0.0,
425                memory_efficiency: 0.0,
426                power_efficiency: 0.0,
427            }
428        }
429    }
430
431    /// Estimate Metal performance
432    fn estimate_metal_performance(&self, _profile: &HardwareProfile) -> PerformanceEstimate {
433        // Apple Silicon typically has excellent unified memory performance
434        PerformanceEstimate {
435            inference_speed: 75.0,
436            memory_efficiency: 95.0, // Unified memory architecture
437            power_efficiency: 90.0, // Apple Silicon is very power efficient
438        }
439    }
440
441    /// Estimate Vulkan performance
442    fn estimate_vulkan_performance(&self, _profile: &HardwareProfile) -> PerformanceEstimate {
443        // Vulkan performance varies significantly by GPU
444        PerformanceEstimate {
445            inference_speed: 70.0,
446            memory_efficiency: 85.0,
447            power_efficiency: 80.0,
448        }
449    }
450}
451
452/// Memory information structure
453#[derive(Debug)]
454struct MemoryInfo {
455    total_gb: f32,
456    available_gb: f32,
457}
458
459/// Engine recommendation with priority
460#[derive(Debug, serde::Serialize, serde::Deserialize)]
461pub struct EngineRecommendation {
462    pub engine_type: String,
463    pub priority: u32,
464    pub reason: String,
465    pub estimated_performance: PerformanceEstimate,
466}
467
468/// Performance estimates for different engine types
469#[derive(Debug, serde::Serialize, serde::Deserialize)]
470pub struct PerformanceEstimate {
471    pub inference_speed: f32,      // Relative speed score (higher is better)
472    pub memory_efficiency: f32,    // Percentage (0-100)
473    pub power_efficiency: f32,     // Percentage (0-100)
474}