selfware 0.6.1

Your personal AI workshop — software you own, software that lasts
//! GPU resource management

use crate::config::GpuConfig;
use crate::errors::{ResourceError, SelfwareError};
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use tracing::{debug, info, warn};

/// GPU manager for monitoring and controlling GPU resources
pub struct GpuManager {
    config: GpuConfig,
    nvml: Option<nvml_wrapper::Nvml>,
    devices: Vec<GpuDevice>,
    throttled: AtomicU32,
}

/// GPU device information
#[derive(Debug, Clone)]
pub struct GpuDevice {
    pub index: u32,
    pub uuid: String,
    pub name: String,
    pub memory_total: u64,
    pub memory_allocated: Arc<AtomicU64>,
}

/// GPU usage statistics
#[derive(Debug, Clone, Default)]
pub struct GpuUsage {
    pub memory_used: u64,
    pub memory_total: u64,
    pub utilization: f32,
    pub temperature: u32,
    pub power_draw: f32,
}

/// Quantization level for model compression
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum QuantizationLevel {
    None, // Full precision (FP16/FP32)
    FP8,  // 8-bit floating point
    Int8, // 8-bit integer
    Int4, // 4-bit integer
}

impl GpuManager {
    /// Create a new GPU manager
    pub async fn new(config: &GpuConfig) -> Result<Self, SelfwareError> {
        let nvml = nvml_wrapper::Nvml::init().ok();
        let mut devices = Vec::new();

        if let Some(ref nvml) = nvml {
            match nvml.device_count() {
                Ok(count) => {
                    for i in 0..count {
                        match nvml.device_by_index(i) {
                            Ok(device) => {
                                // Get device info with proper error handling
                                let uuid = match device.uuid() {
                                    Ok(u) => u,
                                    Err(e) => {
                                        warn!(index = i, error = %e, "Failed to get GPU UUID");
                                        format!("unknown-{}", i)
                                    }
                                };
                                let name = match device.name() {
                                    Ok(n) => n,
                                    Err(e) => {
                                        warn!(index = i, error = %e, "Failed to get GPU name");
                                        format!("Unknown GPU {}", i)
                                    }
                                };

                                // Try to get memory info - skip device if we can't
                                let memory_total = match device.memory_info() {
                                    Ok(mem) => mem.total,
                                    Err(e) => {
                                        warn!(index = i, error = %e, "Failed to get GPU memory info, skipping device");
                                        continue;
                                    }
                                };

                                devices.push(GpuDevice {
                                    index: i,
                                    uuid,
                                    name: name.clone(),
                                    memory_total,
                                    memory_allocated: Arc::new(AtomicU64::new(0)),
                                });

                                info!(
                                    index = i,
                                    name = %name,
                                    memory_gb = memory_total / 1_000_000_000,
                                    "GPU device found"
                                );
                            }
                            Err(e) => {
                                warn!(index = i, error = %e, "Failed to get GPU device info");
                            }
                        }
                    }
                }
                Err(e) => {
                    warn!(error = %e, "Failed to get GPU device count");
                }
            }
        } else {
            warn!("NVML not available, GPU monitoring disabled");
        }

        Ok(Self {
            config: config.clone(),
            nvml,
            devices,
            throttled: AtomicU32::new(0),
        })
    }

    /// Get current GPU usage
    pub async fn get_usage(&self) -> Result<GpuUsage, ResourceError> {
        let Some(ref nvml) = self.nvml else {
            return Ok(GpuUsage::default());
        };

        let mut total_usage = GpuUsage::default();

        for device in &self.devices {
            match nvml.device_by_index(device.index) {
                Ok(dev) => {
                    // Memory info
                    if let Ok(mem) = dev.memory_info() {
                        total_usage.memory_used += mem.used;
                        total_usage.memory_total += mem.total;
                    }

                    // Utilization
                    if let Ok(util) = dev.utilization_rates() {
                        total_usage.utilization = total_usage.utilization.max(util.gpu as f32);
                    }

                    // Temperature
                    if let Ok(temp) =
                        dev.temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu)
                    {
                        total_usage.temperature = total_usage.temperature.max(temp);
                    }

                    // Power
                    if let Ok(power) = dev.power_usage() {
                        total_usage.power_draw += power as f32 / 1000.0;
                    }
                }
                Err(e) => {
                    debug!(index = device.index, error = %e, "Failed to get GPU stats");
                }
            }
        }

        Ok(total_usage)
    }

    /// Get available GPU memory
    pub async fn get_available_memory(&self) -> u64 {
        if let Ok(usage) = self.get_usage().await {
            usage.memory_total.saturating_sub(usage.memory_used)
        } else {
            0
        }
    }

    /// Monitor GPU continuously
    pub async fn monitor(&self) {
        let mut interval = tokio::time::interval(std::time::Duration::from_secs(
            self.config.monitor_interval_seconds,
        ));

        loop {
            interval.tick().await;

            if let Ok(usage) = self.get_usage().await {
                // Check temperature
                if usage.temperature > self.config.temperature_threshold {
                    warn!(temp = usage.temperature, "GPU temperature high");

                    if self.config.throttle_on_overheat {
                        self.throttle_compute(0.7).await;
                    }
                }

                // Check memory utilization
                let mem_util = if usage.memory_total > 0 {
                    usage.memory_used as f32 / usage.memory_total as f32
                } else {
                    0.0
                };

                if mem_util > self.config.memory_utilization_threshold {
                    warn!(utilization = mem_util, "GPU memory utilization high");
                }

                // Emit metrics
                // metrics::gauge!("gpu.memory.used_bytes", usage.memory_used as f64);
                // metrics::gauge!("gpu.utilization", usage.utilization as f64);
                // metrics::gauge!("gpu.temperature", usage.temperature as f64);
                // metrics::gauge!("gpu.power_draw", usage.power_draw as f64);
            }
        }
    }

    /// Throttle GPU compute
    pub async fn throttle_compute(&self, factor: f32) {
        let current = self.throttled.load(Ordering::Relaxed);
        let new = (current as f32 * factor) as u32;
        self.throttled.store(new, Ordering::Relaxed);

        warn!(throttle_factor = factor, "GPU compute throttled");

        // In a real implementation, this would adjust vLLM batch sizes,
        // reduce concurrent requests, etc.
    }

    /// Reduce batch size for inference
    pub async fn reduce_batch_size(&self) {
        warn!("Reducing GPU batch size");
        // This would communicate with the LLM engine to reduce batch size
    }

    /// Allocate GPU memory for a model
    pub async fn allocate_memory(
        &self,
        device_index: u32,
        bytes: u64,
    ) -> Result<(), ResourceError> {
        if let Some(device) = self.devices.get(device_index as usize) {
            let current = device.memory_allocated.load(Ordering::Relaxed);
            let new_total = current + bytes;

            if new_total > device.memory_total {
                return Err(ResourceError::Gpu(format!(
                    "Cannot allocate {} bytes, only {} available",
                    bytes,
                    device.memory_total - current
                )));
            }

            device.memory_allocated.store(new_total, Ordering::Relaxed);
            debug!(
                device = device_index,
                allocated_bytes = bytes,
                "GPU memory allocated"
            );

            Ok(())
        } else {
            Err(ResourceError::Gpu(format!(
                "Invalid device index: {}",
                device_index
            )))
        }
    }

    /// Free GPU memory
    pub async fn free_memory(&self, device_index: u32, bytes: u64) {
        if let Some(device) = self.devices.get(device_index as usize) {
            let current = device.memory_allocated.load(Ordering::Relaxed);
            let new_total = current.saturating_sub(bytes);
            device.memory_allocated.store(new_total, Ordering::Relaxed);

            debug!(
                device = device_index,
                freed_bytes = bytes,
                "GPU memory freed"
            );
        }
    }

    /// Get list of GPU devices
    pub fn devices(&self) -> &[GpuDevice] {
        &self.devices
    }
}

#[cfg(test)]
#[path = "../../tests/unit/resource/gpu/gpu_test.rs"]
mod tests;