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