Skip to main content

trueno/monitor/
backends.rs

1//! wgpu and CUDA backend implementations for GPU monitoring
2
3#[cfg(feature = "cuda-monitor")]
4use super::GpuMemoryMetrics;
5#[cfg(any(all(feature = "gpu", not(target_arch = "wasm32")), feature = "cuda-monitor"))]
6use super::MonitorError;
7#[cfg(any(all(feature = "gpu", not(target_arch = "wasm32")), feature = "cuda-monitor"))]
8use super::{GpuBackend, GpuDeviceInfo, GpuVendor};
9
10// ============================================================================
11// wgpu Backend Implementation
12// ============================================================================
13
14/// Query device info from wgpu adapter
15#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
16pub(crate) fn query_wgpu_device_info(device_index: u32) -> Result<GpuDeviceInfo, MonitorError> {
17    use crate::backends::gpu::runtime;
18
19    runtime::block_on(async {
20        // PMAT-778: reuse the process-global instance (single freedreno enumeration).
21        let instance = crate::backends::gpu::shared_instance();
22
23        // Get all adapters (wgpu 27+ returns Vec directly).
24        // PMAT-925: exclude GLES (SIGABRT-in-Drop on Linux/AMD-RADV).
25        let adapters = instance.enumerate_adapters(crate::backends::gpu::gpu_backends());
26
27        if adapters.is_empty() {
28            return Err(MonitorError::NoDevice);
29        }
30
31        let adapter =
32            adapters.get(device_index as usize).ok_or(MonitorError::InvalidDevice(device_index))?;
33
34        let info = adapter.get_info();
35
36        // Map wgpu backend to our backend enum
37        let backend = match info.backend {
38            wgpu::Backend::Vulkan => GpuBackend::Vulkan,
39            wgpu::Backend::Metal => GpuBackend::Metal,
40            wgpu::Backend::Dx12 => GpuBackend::Dx12,
41            wgpu::Backend::Gl => GpuBackend::OpenGl,
42            wgpu::Backend::BrowserWebGpu => GpuBackend::WebGpu,
43            wgpu::Backend::Noop => GpuBackend::Cpu,
44        };
45
46        // Map vendor ID
47        let vendor = GpuVendor::from_vendor_id(info.vendor);
48
49        // Get memory limits (rough estimate from adapter limits)
50        let limits = adapter.limits();
51        // Use max buffer size as a proxy for VRAM (not exact but gives an idea)
52        let vram_estimate = limits.max_buffer_size;
53
54        Ok(GpuDeviceInfo::new(device_index, info.name, vendor, backend)
55            .with_vram(vram_estimate)
56            .with_driver_version(format!("{:?}", info.driver_info)))
57    })
58}
59
60/// Enumerate all wgpu devices
61#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
62pub(crate) fn enumerate_wgpu_devices() -> Result<Vec<GpuDeviceInfo>, MonitorError> {
63    use crate::backends::gpu::runtime;
64
65    runtime::block_on(async {
66        // PMAT-778: reuse the process-global instance (single freedreno enumeration).
67        let instance = crate::backends::gpu::shared_instance();
68        // PMAT-925: exclude GLES (SIGABRT-in-Drop on Linux/AMD-RADV).
69        let adapters = instance.enumerate_adapters(crate::backends::gpu::gpu_backends());
70
71        if adapters.is_empty() {
72            return Err(MonitorError::NoDevice);
73        }
74
75        let mut devices = Vec::with_capacity(adapters.len());
76
77        for (idx, adapter) in adapters.iter().enumerate() {
78            let info: wgpu::AdapterInfo = adapter.get_info();
79
80            let backend = match info.backend {
81                wgpu::Backend::Vulkan => GpuBackend::Vulkan,
82                wgpu::Backend::Metal => GpuBackend::Metal,
83                wgpu::Backend::Dx12 => GpuBackend::Dx12,
84                wgpu::Backend::Gl => GpuBackend::OpenGl,
85                wgpu::Backend::BrowserWebGpu => GpuBackend::WebGpu,
86                wgpu::Backend::Noop => GpuBackend::Cpu,
87            };
88
89            let vendor = GpuVendor::from_vendor_id(info.vendor);
90            let limits = adapter.limits();
91
92            devices.push(
93                GpuDeviceInfo::new(idx as u32, info.name, vendor, backend)
94                    .with_vram(limits.max_buffer_size)
95                    .with_driver_version(format!("{:?}", info.driver_info)),
96            );
97        }
98
99        Ok(devices)
100    })
101}
102
103// ============================================================================
104// CUDA Backend Implementation (TRUENO-SPEC-010 Section 8)
105// ============================================================================
106
107/// Query device info from native CUDA via trueno-gpu
108///
109/// This provides more accurate information than wgpu including:
110/// - Actual device name (e.g., "NVIDIA GeForce RTX 4090")
111/// - Accurate VRAM total from cuDeviceTotalMem
112#[cfg(feature = "cuda-monitor")]
113pub fn query_cuda_device_info(device_index: u32) -> Result<GpuDeviceInfo, MonitorError> {
114    use trueno_gpu::CudaDeviceInfo;
115
116    let cuda_info = CudaDeviceInfo::query(device_index)
117        .map_err(|e| MonitorError::BackendInit(format!("CUDA query failed: {}", e)))?;
118
119    Ok(GpuDeviceInfo::new(
120        cuda_info.index,
121        cuda_info.name,
122        GpuVendor::Nvidia, // CUDA is NVIDIA-only
123        GpuBackend::Cuda,
124    )
125    .with_vram(cuda_info.total_memory))
126}
127
128/// Enumerate all CUDA devices via trueno-gpu
129#[cfg(feature = "cuda-monitor")]
130pub fn enumerate_cuda_devices() -> Result<Vec<GpuDeviceInfo>, MonitorError> {
131    use trueno_gpu::CudaDeviceInfo;
132
133    let cuda_devices = CudaDeviceInfo::enumerate()
134        .map_err(|e| MonitorError::BackendInit(format!("CUDA enumerate failed: {}", e)))?;
135
136    Ok(cuda_devices
137        .into_iter()
138        .map(|cuda_info| {
139            GpuDeviceInfo::new(cuda_info.index, cuda_info.name, GpuVendor::Nvidia, GpuBackend::Cuda)
140                .with_vram(cuda_info.total_memory)
141        })
142        .collect())
143}
144
145/// Query real-time CUDA memory metrics
146///
147/// Returns current free/used VRAM from cuMemGetInfo.
148#[cfg(feature = "cuda-monitor")]
149pub fn query_cuda_memory(device_index: u32) -> Result<GpuMemoryMetrics, MonitorError> {
150    use trueno_gpu::driver::CudaContext;
151    use trueno_gpu::CudaMemoryInfo;
152
153    let ctx = CudaContext::new(device_index as i32)
154        .map_err(|e| MonitorError::BackendInit(format!("CUDA context failed: {}", e)))?;
155
156    let mem = CudaMemoryInfo::query(&ctx)
157        .map_err(|e| MonitorError::QueryFailed(format!("CUDA memory query failed: {}", e)))?;
158
159    Ok(GpuMemoryMetrics::new(mem.total, mem.used(), mem.free))
160}
161
162/// Check if CUDA monitoring is available
163#[cfg(feature = "cuda-monitor")]
164#[must_use]
165pub fn cuda_monitor_available() -> bool {
166    trueno_gpu::cuda_monitoring_available()
167}
168
169/// Check if CUDA monitoring is available (stub when feature disabled)
170#[cfg(not(feature = "cuda-monitor"))]
171#[must_use]
172pub fn cuda_monitor_available() -> bool {
173    false
174}