hardware_query/
gpu.rs

1use crate::Result;
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "nvidia")]
5use nvml_wrapper::Nvml;
6
7// ROCm detection will be done via system calls
8#[cfg(target_os = "linux")]
9use std::process::Command;
10
11#[cfg(target_os = "windows")]
12use wmi::{COMLibrary, WMIConnection};
13
14/// GPU vendor information
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub enum GPUVendor {
17    NVIDIA,
18    AMD,
19    Intel,
20    Apple,
21    ARM,
22    Qualcomm,
23    Unknown(String),
24}
25
26impl std::fmt::Display for GPUVendor {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            GPUVendor::NVIDIA => write!(f, "NVIDIA"),
30            GPUVendor::AMD => write!(f, "AMD"),
31            GPUVendor::Intel => write!(f, "Intel"),
32            GPUVendor::Apple => write!(f, "Apple"),
33            GPUVendor::ARM => write!(f, "ARM"),
34            GPUVendor::Qualcomm => write!(f, "Qualcomm"),
35            GPUVendor::Unknown(name) => write!(f, "{name}"),
36        }
37    }
38}
39
40/// GPU type classification
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum GPUType {
43    /// Consumer discrete GPU
44    Discrete,
45    /// Integrated GPU
46    Integrated,
47    /// Workstation GPU (Quadro, RadeonPro, etc.)
48    Workstation,
49    /// Datacenter GPU (Tesla, Instinct, etc.)
50    Datacenter,
51    /// Virtual GPU
52    Virtual,
53    /// Unknown type
54    Unknown,
55}
56
57impl std::fmt::Display for GPUType {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            GPUType::Discrete => write!(f, "Discrete"),
61            GPUType::Integrated => write!(f, "Integrated"),
62            GPUType::Workstation => write!(f, "Workstation"),
63            GPUType::Datacenter => write!(f, "Datacenter"),
64            GPUType::Virtual => write!(f, "Virtual"),
65            GPUType::Unknown => write!(f, "Unknown"),
66        }
67    }
68}
69
70/// GPU compute capabilities
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ComputeCapabilities {
73    /// CUDA support and compute capability
74    pub cuda: Option<String>,
75    /// ROCm support
76    pub rocm: bool,
77    /// DirectML support (Windows)
78    pub directml: bool,
79    /// OpenCL support
80    pub opencl: bool,
81    /// Vulkan support
82    pub vulkan: bool,
83    /// Metal support (macOS)
84    pub metal: bool,
85    /// Maximum compute units/cores
86    pub compute_units: Option<u32>,
87    /// Maximum workgroup size
88    pub max_workgroup_size: Option<u32>,
89}
90
91/// GPU information and specifications
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct GPUInfo {
94    /// GPU vendor
95    pub vendor: GPUVendor,
96    /// GPU model name
97    pub model_name: String,
98    /// GPU type (discrete, integrated, etc.)
99    pub gpu_type: GPUType,
100    /// GPU memory in MB
101    pub memory_mb: u64,
102    /// GPU memory type (GDDR6, HBM2, etc.)
103    pub memory_type: Option<String>,
104    /// GPU memory bandwidth in GB/s
105    pub memory_bandwidth: Option<f32>,
106    /// GPU base clock in MHz
107    pub base_clock: Option<u32>,
108    /// GPU boost clock in MHz
109    pub boost_clock: Option<u32>,
110    /// GPU memory clock in MHz
111    pub memory_clock: Option<u32>,
112    /// Number of shader units/cores
113    pub shader_units: Option<u32>,
114    /// Number of RT cores (NVIDIA)
115    pub rt_cores: Option<u32>,
116    /// Number of tensor cores (NVIDIA)
117    pub tensor_cores: Option<u32>,
118    /// Compute capabilities
119    pub compute_capabilities: ComputeCapabilities,
120    /// Current GPU usage percentage
121    pub usage_percent: Option<f32>,
122    /// Current GPU temperature in Celsius
123    pub temperature: Option<f32>,
124    /// Current power consumption in watts
125    pub power_consumption: Option<f32>,
126    /// Maximum power limit in watts
127    pub power_limit: Option<f32>,
128    /// Driver version
129    pub driver_version: Option<String>,
130    /// VBIOS version
131    pub vbios_version: Option<String>,
132    /// PCI device ID
133    pub pci_device_id: Option<String>,
134    /// PCI subsystem ID
135    pub pci_subsystem_id: Option<String>,
136}
137
138impl GPUInfo {
139    /// Query all GPU information from the system
140    pub fn query_all() -> Result<Vec<Self>> {
141        let mut gpus = Vec::new();
142
143        // Always try WMI detection first to get all GPUs
144        if let Ok(wmi_gpus) = Self::query_generic_gpus() {
145            gpus.extend(wmi_gpus);
146        }
147
148        // Try to enhance with vendor-specific information
149        if let Ok(nvidia_gpus) = Self::query_nvidia_gpus() {
150            // Merge NVIDIA-specific details with WMI results
151            for nvidia_gpu in nvidia_gpus {
152                // Check if we already have this GPU from WMI
153                if let Some(existing) = gpus.iter_mut().find(|g| 
154                    g.vendor == GPUVendor::NVIDIA && 
155                    g.model_name.contains("RTX") == nvidia_gpu.model_name.contains("RTX")
156                ) {
157                    // Update with more detailed NVIDIA information
158                    existing.compute_capabilities.cuda = nvidia_gpu.compute_capabilities.cuda;
159                    existing.usage_percent = nvidia_gpu.usage_percent;
160                    existing.temperature = nvidia_gpu.temperature;
161                    existing.power_consumption = nvidia_gpu.power_consumption;
162                    existing.shader_units = nvidia_gpu.shader_units;
163                    existing.rt_cores = nvidia_gpu.rt_cores;
164                    existing.tensor_cores = nvidia_gpu.tensor_cores;
165                } else {
166                    // Add as new GPU if not found in WMI results
167                    gpus.push(nvidia_gpu);
168                }
169            }
170        }
171
172        if let Ok(amd_gpus) = Self::query_amd_gpus() {
173            // Similar merge logic for AMD GPUs
174            for amd_gpu in amd_gpus {
175                if !gpus.iter().any(|g| g.vendor == GPUVendor::AMD && g.model_name == amd_gpu.model_name) {
176                    gpus.push(amd_gpu);
177                }
178            }
179        }
180
181        if let Ok(intel_gpus) = Self::query_intel_gpus() {
182            // Similar merge logic for Intel GPUs
183            for intel_gpu in intel_gpus {
184                if !gpus.iter().any(|g| g.vendor == GPUVendor::Intel && g.model_name == intel_gpu.model_name) {
185                    gpus.push(intel_gpu);
186                }
187            }
188        }
189
190        // If still no GPUs found, return a placeholder
191        if gpus.is_empty() {
192            gpus.push(Self::default_gpu());
193        }
194
195        Ok(gpus)
196    }
197
198    /// Get GPU vendor
199    pub fn vendor(&self) -> &GPUVendor {
200        &self.vendor
201    }
202
203    /// Get GPU model name
204    pub fn model_name(&self) -> &str {
205        &self.model_name
206    }
207
208    /// Get GPU type
209    pub fn gpu_type(&self) -> &GPUType {
210        &self.gpu_type
211    }
212
213    /// Get GPU memory in GB (rounded to 1 decimal place)
214    pub fn memory_gb(&self) -> f64 {
215        (self.memory_mb as f64 / 1024.0 * 10.0).round() / 10.0
216    }
217
218    /// Get GPU memory in MB
219    pub fn memory_mb(&self) -> u64 {
220        self.memory_mb
221    }
222
223    /// Check if GPU supports CUDA
224    pub fn supports_cuda(&self) -> bool {
225        self.compute_capabilities.cuda.is_some()
226    }
227
228    /// Get CUDA compute capability
229    pub fn cuda_capability(&self) -> Option<&str> {
230        self.compute_capabilities.cuda.as_deref()
231    }
232
233    /// Check if GPU supports ROCm
234    pub fn supports_rocm(&self) -> bool {
235        self.compute_capabilities.rocm
236    }
237
238    /// Check if GPU supports DirectML
239    pub fn supports_directml(&self) -> bool {
240        self.compute_capabilities.directml
241    }
242
243    /// Check if GPU supports OpenCL
244    pub fn supports_opencl(&self) -> bool {
245        self.compute_capabilities.opencl
246    }
247
248    /// Check if GPU supports Vulkan
249    pub fn supports_vulkan(&self) -> bool {
250        self.compute_capabilities.vulkan
251    }
252
253    /// Check if GPU supports Metal
254    pub fn supports_metal(&self) -> bool {
255        self.compute_capabilities.metal
256    }
257
258    /// Get current GPU usage percentage
259    pub fn usage_percent(&self) -> Option<f32> {
260        self.usage_percent
261    }
262
263    /// Get current GPU temperature
264    pub fn temperature(&self) -> Option<f32> {
265        self.temperature
266    }
267
268    /// Create a default/fallback GPU for systems where no GPUs are detected
269    fn default_gpu() -> Self {
270        Self {
271            vendor: GPUVendor::Unknown("Generic".to_string()),
272            model_name: "Unknown GPU".to_string(),
273            gpu_type: GPUType::Unknown,
274            memory_mb: 1024, // 1GB default
275            memory_type: None,
276            memory_bandwidth: None,
277            base_clock: None,
278            boost_clock: None,
279            memory_clock: None,
280            shader_units: None,
281            rt_cores: None,
282            tensor_cores: None,
283            compute_capabilities: ComputeCapabilities {
284                cuda: None,
285                rocm: false,
286                directml: cfg!(target_os = "windows"),
287                opencl: false,
288                vulkan: false,
289                metal: cfg!(target_os = "macos"),
290                compute_units: None,
291                max_workgroup_size: None,
292            },
293            usage_percent: None,
294            temperature: None,
295            power_consumption: None,
296            power_limit: None,
297            driver_version: None,
298            vbios_version: None,
299            pci_device_id: None,
300            pci_subsystem_id: None,
301        }
302    }
303
304    fn query_nvidia_gpus() -> Result<Vec<Self>> {
305        #[cfg(feature = "nvidia")]
306        {
307            let nvml = match Nvml::init() {
308                Ok(nvml) => nvml,
309                Err(_) => return Ok(vec![]),
310            };
311
312            let mut gpus = Vec::new();
313            let device_count = nvml.device_count().unwrap_or(0);
314
315            for i in 0..device_count {
316                if let Ok(device) = nvml.device_by_index(i) {
317                    let name = device.name().unwrap_or_default();
318                    let memory_info = device.memory_info().ok();
319                    let cuda_capability = device.cuda_compute_capability().ok();
320                    let driver_version = nvml.sys_driver_version().unwrap_or_default();
321
322                    let gpu = Self {
323                        vendor: GPUVendor::NVIDIA,
324                        model_name: name,
325                        gpu_type: GPUType::Discrete,
326                        memory_mb: memory_info.map(|m| m.total / 1024 / 1024).unwrap_or(0),
327                        memory_type: Some("GDDR6".to_string()),
328                        memory_bandwidth: None,
329                        base_clock: None,
330                        boost_clock: None,
331                        memory_clock: None,
332                        shader_units: None,
333                        rt_cores: None,
334                        tensor_cores: None,
335                        compute_capabilities: ComputeCapabilities {
336                            cuda: cuda_capability.map(|c| format!("{}.{}", c.major, c.minor)),
337                            rocm: false,
338                            directml: cfg!(target_os = "windows"),
339                            opencl: true,
340                            vulkan: true,
341                            metal: cfg!(target_os = "macos"),
342                            compute_units: None,
343                            max_workgroup_size: None,
344                        },
345                        usage_percent: device.utilization_rates().ok().map(|u| u.gpu as f32),
346                        temperature: device
347                            .temperature(
348                                nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu,
349                            )
350                            .ok()
351                            .map(|t| t as f32),
352                        power_consumption: device.power_usage().ok().map(|p| p as f32 / 1000.0),
353                        power_limit: device
354                            .power_management_limit_default()
355                            .ok()
356                            .map(|p| p as f32 / 1000.0),
357                        driver_version: Some(driver_version),
358                        vbios_version: device.vbios_version().ok(),
359                        pci_device_id: None,
360                        pci_subsystem_id: None,
361                    };
362
363                    gpus.push(gpu);
364                }
365            }
366
367            Ok(gpus)
368        }
369        #[cfg(not(feature = "nvidia"))]
370        {
371            Ok(vec![])
372        }
373    }
374
375    fn query_amd_gpus() -> Result<Vec<Self>> {
376        #[cfg(feature = "amd")]
377        {
378            // ROCm/AMD GPU detection would require additional dependencies
379            // For now, return empty vector as AMD feature is not implemented
380            Ok(vec![])
381        }
382        #[cfg(not(feature = "amd"))]
383        {
384            Ok(vec![])
385        }
386    }
387
388    fn query_intel_gpus() -> Result<Vec<Self>> {
389        #[cfg(target_os = "windows")]
390        {
391            // Use WMI to query Intel GPUs
392            match WMIConnection::new(COMLibrary::new()?) {
393                Ok(wmi_con) => {
394                    let results: Vec<std::collections::HashMap<String, wmi::Variant>> = wmi_con
395                        .raw_query("SELECT Name, AdapterRAM FROM Win32_VideoController WHERE Name LIKE '%Intel%'")
396                        .unwrap_or_default();
397
398                    let mut gpus = Vec::new();
399                    for result in results {
400                        if let (Some(wmi::Variant::String(name)), Some(wmi::Variant::UI4(ram))) =
401                            (result.get("Name"), result.get("AdapterRAM"))
402                        {
403                            let gpu = Self {
404                                vendor: GPUVendor::Intel,
405                                model_name: name.clone(),
406                                gpu_type: GPUType::Integrated,
407                                memory_mb: *ram as u64 / 1024 / 1024,
408                                memory_type: Some("System".to_string()),
409                                memory_bandwidth: None,
410                                base_clock: None,
411                                boost_clock: None,
412                                memory_clock: None,
413                                shader_units: None,
414                                rt_cores: None,
415                                tensor_cores: None,
416                                compute_capabilities: ComputeCapabilities {
417                                    cuda: None,
418                                    rocm: false,
419                                    directml: true,
420                                    opencl: true,
421                                    vulkan: true,
422                                    metal: false,
423                                    compute_units: None,
424                                    max_workgroup_size: None,
425                                },
426                                usage_percent: None,
427                                temperature: None,
428                                power_consumption: None,
429                                power_limit: None,
430                                driver_version: None,
431                                vbios_version: None,
432                                pci_device_id: None,
433                                pci_subsystem_id: None,
434                            };
435
436                            gpus.push(gpu);
437                        }
438                    }
439
440                    Ok(gpus)
441                }
442                Err(_) => Ok(vec![]),
443            }
444        }
445        #[cfg(not(target_os = "windows"))]
446        {
447            Ok(vec![])
448        }
449    }
450
451    fn query_generic_gpus() -> Result<Vec<Self>> {
452        // Generic GPU detection using WMI on Windows
453        #[cfg(target_os = "windows")]
454        {
455            use std::collections::HashMap;
456            use wmi::{COMLibrary, WMIConnection, Variant};
457
458            let com_con = COMLibrary::new()?;
459            let wmi_con = WMIConnection::new(com_con)?;
460
461            let results: Vec<HashMap<String, Variant>> = wmi_con
462                .raw_query("SELECT * FROM Win32_VideoController WHERE PNPDeviceID IS NOT NULL")?;
463
464            let mut gpus = Vec::new();
465
466            for gpu in results {
467                let name = gpu.get("Name")
468                    .and_then(|v| match v {
469                        Variant::String(s) => Some(s.clone()),
470                        _ => None,
471                    })
472                    .unwrap_or_else(|| "Unknown GPU".to_string());
473
474                let adapter_ram = gpu.get("AdapterRAM")
475                    .and_then(|v| match v {
476                        Variant::UI4(val) => Some(*val as u64),
477                        Variant::UI8(val) => Some(*val),
478                        _ => None,
479                    })
480                    .unwrap_or(0);
481
482                let device_id = gpu.get("PNPDeviceID")
483                    .and_then(|v| match v {
484                        Variant::String(s) => Some(s.clone()),
485                        _ => None,
486                    })
487                    .unwrap_or_else(|| "".to_string());
488
489                let driver_version = gpu.get("DriverVersion")
490                    .and_then(|v| match v {
491                        Variant::String(s) => Some(s.clone()),
492                        _ => None,
493                    });
494
495                // Determine vendor from name or device ID
496                let vendor = if name.to_lowercase().contains("nvidia") || device_id.contains("VEN_10DE") {
497                    GPUVendor::NVIDIA
498                } else if name.to_lowercase().contains("amd") || name.to_lowercase().contains("radeon") || device_id.contains("VEN_1002") {
499                    GPUVendor::AMD
500                } else if name.to_lowercase().contains("intel") || device_id.contains("VEN_8086") {
501                    GPUVendor::Intel
502                } else {
503                    GPUVendor::Unknown("Generic".to_string())
504                };
505
506                // Determine GPU type using comprehensive classification
507                let gpu_type = Self::classify_gpu_type(&name, &vendor, adapter_ram);
508
509                // Convert memory from bytes to MB
510                let memory_mb = if adapter_ram > 0 {
511                    adapter_ram / (1024 * 1024)
512                } else {
513                    // Fallback estimates based on GPU type and vendor
514                    match (&vendor, &gpu_type) {
515                        (GPUVendor::NVIDIA, GPUType::Datacenter) => 32768,    // 32GB for datacenter (A100, H100)
516                        (GPUVendor::NVIDIA, GPUType::Workstation) => 16384,   // 16GB for workstation (RTX A6000)
517                        (GPUVendor::NVIDIA, GPUType::Discrete) => 8192,       // 8GB for consumer RTX
518                        (GPUVendor::AMD, GPUType::Datacenter) => 32768,       // 32GB for Instinct
519                        (GPUVendor::AMD, GPUType::Workstation) => 16384,      // 16GB for Radeon Pro
520                        (GPUVendor::AMD, GPUType::Discrete) => 8192,          // 8GB for discrete AMD
521                        (_, GPUType::Integrated) => 512,                      // 512MB for integrated
522                        _ => 4096,                                            // Default 4GB
523                    }
524                };
525
526                // Set compute capabilities based on vendor
527                let compute_capabilities = ComputeCapabilities {
528                    cuda: if vendor == GPUVendor::NVIDIA { Some("Unknown".to_string()) } else { None },
529                    rocm: vendor == GPUVendor::AMD && gpu_type == GPUType::Discrete,
530                    directml: true, // DirectML is available on Windows for most modern GPUs
531                    opencl: true,   // Most modern GPUs support OpenCL
532                    vulkan: true,   // Most modern GPUs support Vulkan
533                    metal: false,   // Metal is macOS only
534                    compute_units: None,
535                    max_workgroup_size: None,
536                };
537
538                gpus.push(Self {
539                    vendor,
540                    model_name: name,
541                    gpu_type,
542                    memory_mb,
543                    memory_type: None,
544                    memory_bandwidth: None,
545                    base_clock: None,
546                    boost_clock: None,
547                    memory_clock: None,
548                    shader_units: None,
549                    rt_cores: None,
550                    tensor_cores: None,
551                    compute_capabilities,
552                    usage_percent: None,
553                    temperature: None,
554                    power_consumption: None,
555                    power_limit: None,
556                    driver_version,
557                    vbios_version: None,
558                    pci_device_id: Some(device_id),
559                    pci_subsystem_id: None,
560                });
561            }
562
563            Ok(gpus)
564        }
565        #[cfg(not(target_os = "windows"))]
566        {
567            // For non-Windows platforms, use system-specific detection
568            Ok(vec![])
569        }
570    }
571
572    /// Classify GPU type based on model name and characteristics
573    fn classify_gpu_type(name: &str, vendor: &GPUVendor, adapter_ram: u64) -> GPUType {
574        let name_lower = name.to_lowercase();
575        
576        // Check for datacenter GPUs first
577        if Self::is_datacenter_gpu(&name_lower, vendor) {
578            return GPUType::Datacenter;
579        }
580        
581        // Check for workstation GPUs
582        if Self::is_workstation_gpu(&name_lower, vendor) {
583            return GPUType::Workstation;
584        }
585        
586        // Check for integrated GPUs
587        if Self::is_integrated_gpu(&name_lower, vendor, adapter_ram) {
588            return GPUType::Integrated;
589        }
590        
591        // Default to discrete for remaining GPUs
592        GPUType::Discrete
593    }
594    
595    /// Check if GPU is a datacenter model
596    fn is_datacenter_gpu(name: &str, vendor: &GPUVendor) -> bool {
597        match vendor {
598            GPUVendor::NVIDIA => {
599                name.contains("tesla") ||
600                name.contains("a100") ||
601                name.contains("h100") ||
602                name.contains("h200") ||
603                name.contains("v100") ||
604                name.contains("p100") ||
605                name.contains("k80") ||
606                name.contains("k40") ||
607                name.contains("l40") ||
608                name.contains("l4") ||
609                name.contains("data center") ||
610                name.contains("datacenter") ||
611                name.contains("dgx") ||
612                name.contains("hgx")
613            },
614            GPUVendor::AMD => {
615                name.contains("instinct") ||
616                name.contains("mi50") ||
617                name.contains("mi100") ||
618                name.contains("mi200") ||
619                name.contains("mi250") ||
620                name.contains("mi300") ||
621                name.contains("cdna") ||
622                name.contains("datacenter") ||
623                name.contains("server")
624            },
625            GPUVendor::Intel => {
626                name.contains("ponte vecchio") ||
627                name.contains("data center") ||
628                name.contains("max") && (name.contains("1100") || name.contains("1550"))
629            },
630            _ => false,
631        }
632    }
633    
634    /// Check if GPU is a workstation model
635    fn is_workstation_gpu(name: &str, vendor: &GPUVendor) -> bool {
636        match vendor {
637            GPUVendor::NVIDIA => {
638                name.contains("quadro") ||
639                name.contains("rtx a") ||        // RTX A series (A4000, A5000, A6000)
640                name.contains("rtx 4000") ||     // Quadro RTX 4000
641                name.contains("rtx 5000") ||     // Quadro RTX 5000, etc.
642                name.contains("rtx 6000") ||
643                name.contains("rtx 8000") ||
644                name.contains("titan") ||        // Titan series
645                name.contains("nvs") ||          // NVS series
646                name.contains("t1000") ||        // T-series workstation
647                name.contains("t400") ||
648                name.contains("t600") ||
649                (name.contains("professional") && !name.contains("geforce"))
650            },
651            GPUVendor::AMD => {
652                name.contains("radeon pro") ||
653                name.contains("firepro") ||
654                name.contains("wx ") ||          // WX series
655                name.contains("w6") ||           // W6000 series  
656                name.contains("w7") ||           // W7000 series
657                name.contains("workstation") ||
658                name.contains("professional")
659            },
660            GPUVendor::Intel => {
661                name.contains("pro") ||
662                name.contains("workstation") ||
663                name.contains("professional")
664            },
665            _ => false,
666        }
667    }
668    
669    /// Check if GPU is integrated
670    fn is_integrated_gpu(name: &str, vendor: &GPUVendor, adapter_ram: u64) -> bool {
671        // Standard integrated GPU indicators
672        let integrated_keywords = name.contains("integrated") ||
673                                 name.contains("uhd") ||
674                                 name.contains("iris") ||
675                                 name.contains("vega") && name.contains("graphics") ||
676                                 name.contains("radeon graphics") ||
677                                 name.contains("apu") ||
678                                 name.contains("mobile") && !name.contains("rtx") ||
679                                 name.contains("embedded");
680        
681        // Memory-based heuristic (integrated GPUs typically have < 2GB dedicated VRAM)
682        let low_memory = vendor == &GPUVendor::AMD && adapter_ram < 2_000_000_000;
683        
684        integrated_keywords || low_memory
685    }
686}