use std::fmt;
use crate::GpuError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DeviceId {
pub device_type: DeviceType,
pub index: u32,
}
impl DeviceId {
#[must_use]
pub const fn new(device_type: DeviceType, index: u32) -> Self {
Self { device_type, index }
}
#[must_use]
pub const fn cpu() -> Self {
Self::new(DeviceType::Cpu, 0)
}
#[must_use]
pub const fn nvidia(index: u32) -> Self {
Self::new(DeviceType::NvidiaGpu, index)
}
#[must_use]
pub const fn amd(index: u32) -> Self {
Self::new(DeviceType::AmdGpu, index)
}
}
impl fmt::Display for DeviceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.device_type {
DeviceType::Cpu => write!(f, "CPU"),
DeviceType::NvidiaGpu => write!(f, "NVIDIA:{}", self.index),
DeviceType::AmdGpu => write!(f, "AMD:{}", self.index),
DeviceType::IntelGpu => write!(f, "Intel:{}", self.index),
DeviceType::AppleSilicon => write!(f, "Apple:{}", self.index),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceType {
Cpu,
NvidiaGpu,
AmdGpu,
IntelGpu,
AppleSilicon,
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cpu => write!(f, "CPU"),
Self::NvidiaGpu => write!(f, "NVIDIA GPU"),
Self::AmdGpu => write!(f, "AMD GPU"),
Self::IntelGpu => write!(f, "Intel GPU"),
Self::AppleSilicon => write!(f, "Apple Silicon"),
}
}
}
pub trait ComputeDevice: Send + Sync {
fn device_id(&self) -> DeviceId;
fn device_name(&self) -> &str;
fn device_type(&self) -> DeviceType;
fn compute_utilization(&self) -> Result<f64, GpuError>;
fn compute_clock_mhz(&self) -> Result<u32, GpuError>;
fn compute_temperature_c(&self) -> Result<f64, GpuError>;
fn compute_power_watts(&self) -> Result<f64, GpuError>;
fn compute_power_limit_watts(&self) -> Result<f64, GpuError>;
fn memory_used_bytes(&self) -> Result<u64, GpuError>;
fn memory_total_bytes(&self) -> Result<u64, GpuError>;
fn memory_bandwidth_gbps(&self) -> Result<f64, GpuError>;
fn compute_unit_count(&self) -> u32;
fn active_compute_units(&self) -> Result<u32, GpuError>;
fn pcie_tx_bytes_per_sec(&self) -> Result<u64, GpuError>;
fn pcie_rx_bytes_per_sec(&self) -> Result<u64, GpuError>;
fn pcie_generation(&self) -> u8;
fn pcie_width(&self) -> u8;
fn refresh(&mut self) -> Result<(), GpuError>;
fn memory_usage_percent(&self) -> Result<f64, GpuError> {
let used = self.memory_used_bytes()?;
let total = self.memory_total_bytes()?;
if total == 0 {
return Ok(0.0);
}
Ok((used as f64 / total as f64) * 100.0)
}
fn memory_available_bytes(&self) -> Result<u64, GpuError> {
let used = self.memory_used_bytes()?;
let total = self.memory_total_bytes()?;
Ok(total.saturating_sub(used))
}
fn memory_used_mb(&self) -> Result<u64, GpuError> {
Ok(self.memory_used_bytes()? / (1024 * 1024))
}
fn memory_total_mb(&self) -> Result<u64, GpuError> {
Ok(self.memory_total_bytes()? / (1024 * 1024))
}
fn memory_total_gb(&self) -> Result<f64, GpuError> {
Ok(self.memory_total_bytes()? as f64 / (1024.0 * 1024.0 * 1024.0))
}
fn power_usage_percent(&self) -> Result<f64, GpuError> {
let current = self.compute_power_watts()?;
let limit = self.compute_power_limit_watts()?;
if limit == 0.0 {
return Ok(0.0);
}
Ok((current / limit) * 100.0)
}
fn is_thermal_throttling(&self) -> Result<bool, GpuError> {
let temp = self.compute_temperature_c()?;
Ok(temp > 80.0)
}
fn is_power_throttling(&self) -> Result<bool, GpuError> {
let percent = self.power_usage_percent()?;
Ok(percent > 95.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThrottleReason {
None,
Thermal,
Power,
ApplicationClocks,
SwPowerCap,
HwSlowdown,
SyncBoost,
}
impl fmt::Display for ThrottleReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Thermal => write!(f, "Thermal"),
Self::Power => write!(f, "Power"),
Self::ApplicationClocks => write!(f, "AppClocks"),
Self::SwPowerCap => write!(f, "SwPowerCap"),
Self::HwSlowdown => write!(f, "HwSlowdown"),
Self::SyncBoost => write!(f, "SyncBoost"),
}
}
}
#[derive(Debug, Clone)]
pub struct DeviceSnapshot {
pub device_id: DeviceId,
pub timestamp_ms: u64,
pub compute_utilization: f64,
pub memory_used_bytes: u64,
pub memory_total_bytes: u64,
pub temperature_c: f64,
pub power_watts: f64,
pub clock_mhz: u32,
}
impl DeviceSnapshot {
pub fn capture<D: ComputeDevice>(device: &D) -> Result<Self, GpuError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Ok(Self {
device_id: device.device_id(),
timestamp_ms: now,
compute_utilization: device.compute_utilization().unwrap_or(0.0),
memory_used_bytes: device.memory_used_bytes().unwrap_or(0),
memory_total_bytes: device.memory_total_bytes().unwrap_or(0),
temperature_c: device.compute_temperature_c().unwrap_or(0.0),
power_watts: device.compute_power_watts().unwrap_or(0.0),
clock_mhz: device.compute_clock_mhz().unwrap_or(0),
})
}
#[must_use]
pub fn memory_usage_percent(&self) -> f64 {
if self.memory_total_bytes == 0 {
return 0.0;
}
(self.memory_used_bytes as f64 / self.memory_total_bytes as f64) * 100.0
}
}
#[derive(Debug)]
pub struct CpuDevice {
name: String,
core_count: u32,
total_memory: u64,
cpu_usage: f64,
memory_used: u64,
temperature: Option<f64>,
}
impl CpuDevice {
#[must_use]
pub fn new() -> Self {
let name = Self::read_cpu_name().unwrap_or_else(|| "Unknown CPU".to_string());
let core_count = Self::read_core_count();
let total_memory = Self::read_total_memory();
Self {
name,
core_count,
total_memory,
cpu_usage: 0.0,
memory_used: 0,
temperature: None,
}
}
fn read_cpu_name() -> Option<String> {
#[cfg(target_os = "linux")]
{
let content = std::fs::read_to_string("/proc/cpuinfo").ok()?;
for line in content.lines() {
if line.starts_with("model name") {
return line.split(':').nth(1).map(|s| s.trim().to_string());
}
}
}
None
}
fn read_core_count() -> u32 {
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
return content
.lines()
.filter(|line| line.starts_with("processor"))
.count() as u32;
}
}
std::thread::available_parallelism()
.map(|n| n.get() as u32)
.unwrap_or(1)
}
fn read_total_memory() -> u64 {
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("MemTotal:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(kb) = parts[1].parse::<u64>() {
return kb * 1024; }
}
}
}
}
}
0
}
fn read_memory_used() -> u64 {
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
let mut total = 0u64;
let mut available = 0u64;
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if line.starts_with("MemTotal:") {
total = parts[1].parse().unwrap_or(0) * 1024;
} else if line.starts_with("MemAvailable:") {
available = parts[1].parse().unwrap_or(0) * 1024;
}
}
}
return total.saturating_sub(available);
}
}
0
}
fn read_cpu_usage() -> f64 {
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/stat") {
for line in content.lines() {
if line.starts_with("cpu ") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 {
let user: u64 = parts[1].parse().unwrap_or(0);
let nice: u64 = parts[2].parse().unwrap_or(0);
let system: u64 = parts[3].parse().unwrap_or(0);
let idle: u64 = parts[4].parse().unwrap_or(0);
let total = user + nice + system + idle;
let busy = user + nice + system;
if total > 0 {
return (busy as f64 / total as f64) * 100.0;
}
}
}
}
}
}
0.0
}
fn read_temperature() -> Option<f64> {
#[cfg(target_os = "linux")]
{
if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
for entry in entries.flatten() {
let temp_path = entry.path().join("temp1_input");
if let Ok(content) = std::fs::read_to_string(&temp_path) {
if let Ok(millidegrees) = content.trim().parse::<i64>() {
return Some(millidegrees as f64 / 1000.0);
}
}
}
}
if let Ok(content) = std::fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") {
if let Ok(millidegrees) = content.trim().parse::<i64>() {
return Some(millidegrees as f64 / 1000.0);
}
}
}
None
}
}
impl Default for CpuDevice {
fn default() -> Self {
Self::new()
}
}
impl ComputeDevice for CpuDevice {
fn device_id(&self) -> DeviceId {
DeviceId::cpu()
}
fn device_name(&self) -> &str {
&self.name
}
fn device_type(&self) -> DeviceType {
DeviceType::Cpu
}
fn compute_utilization(&self) -> Result<f64, GpuError> {
Ok(self.cpu_usage)
}
fn compute_clock_mhz(&self) -> Result<u32, GpuError> {
#[cfg(target_os = "linux")]
{
if let Ok(content) =
std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")
{
if let Ok(khz) = content.trim().parse::<u64>() {
return Ok((khz / 1000) as u32);
}
}
}
Err(GpuError::NotSupported(
"CPU frequency not available".to_string(),
))
}
fn compute_temperature_c(&self) -> Result<f64, GpuError> {
self.temperature
.ok_or_else(|| GpuError::NotSupported("CPU temperature not available".to_string()))
}
fn compute_power_watts(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported(
"CPU power not available".to_string(),
))
}
fn compute_power_limit_watts(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported(
"CPU power limit not available".to_string(),
))
}
fn memory_used_bytes(&self) -> Result<u64, GpuError> {
Ok(self.memory_used)
}
fn memory_total_bytes(&self) -> Result<u64, GpuError> {
Ok(self.total_memory)
}
fn memory_bandwidth_gbps(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported(
"Memory bandwidth not available".to_string(),
))
}
fn compute_unit_count(&self) -> u32 {
self.core_count
}
fn active_compute_units(&self) -> Result<u32, GpuError> {
Ok(self.core_count)
}
fn pcie_tx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported(
"CPU has no PCIe metrics".to_string(),
))
}
fn pcie_rx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported(
"CPU has no PCIe metrics".to_string(),
))
}
fn pcie_generation(&self) -> u8 {
0 }
fn pcie_width(&self) -> u8 {
0 }
fn refresh(&mut self) -> Result<(), GpuError> {
self.cpu_usage = Self::read_cpu_usage();
self.memory_used = Self::read_memory_used();
self.temperature = Self::read_temperature();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn h001_device_id_cpu() {
let id = DeviceId::cpu();
assert_eq!(id.device_type, DeviceType::Cpu);
assert_eq!(id.index, 0);
assert_eq!(format!("{}", id), "CPU");
}
#[test]
fn h001_device_id_nvidia() {
let id = DeviceId::nvidia(0);
assert_eq!(id.device_type, DeviceType::NvidiaGpu);
assert_eq!(id.index, 0);
assert_eq!(format!("{}", id), "NVIDIA:0");
let id2 = DeviceId::nvidia(1);
assert_eq!(format!("{}", id2), "NVIDIA:1");
}
#[test]
fn h001_device_id_amd() {
let id = DeviceId::amd(0);
assert_eq!(id.device_type, DeviceType::AmdGpu);
assert_eq!(id.index, 0);
assert_eq!(format!("{}", id), "AMD:0");
}
#[test]
fn h001_device_id_equality() {
let id1 = DeviceId::nvidia(0);
let id2 = DeviceId::nvidia(0);
let id3 = DeviceId::nvidia(1);
let id4 = DeviceId::amd(0);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
assert_ne!(id1, id4);
}
#[test]
fn h002_device_type_display() {
assert_eq!(format!("{}", DeviceType::Cpu), "CPU");
assert_eq!(format!("{}", DeviceType::NvidiaGpu), "NVIDIA GPU");
assert_eq!(format!("{}", DeviceType::AmdGpu), "AMD GPU");
assert_eq!(format!("{}", DeviceType::IntelGpu), "Intel GPU");
assert_eq!(format!("{}", DeviceType::AppleSilicon), "Apple Silicon");
}
#[test]
fn h003_cpu_device_creation() {
let cpu = CpuDevice::new();
assert_eq!(cpu.device_type(), DeviceType::Cpu);
assert_eq!(cpu.device_id(), DeviceId::cpu());
assert!(cpu.core_count > 0);
}
#[test]
fn h003_cpu_device_default() {
let cpu = CpuDevice::default();
assert!(cpu.compute_unit_count() > 0);
}
#[test]
fn h003_cpu_device_name() {
let cpu = CpuDevice::new();
assert!(!cpu.device_name().is_empty());
}
#[test]
fn h003_cpu_device_memory_total() {
let cpu = CpuDevice::new();
let total = cpu.memory_total_bytes().unwrap_or(0);
assert!(total > 0, "CPU should report total memory");
}
#[test]
fn h003_cpu_device_refresh() {
let mut cpu = CpuDevice::new();
assert!(cpu.refresh().is_ok());
}
#[test]
fn h004_device_snapshot_capture() {
let cpu = CpuDevice::new();
let snapshot = DeviceSnapshot::capture(&cpu);
assert!(snapshot.is_ok());
let snap = snapshot.unwrap();
assert_eq!(snap.device_id, DeviceId::cpu());
assert!(snap.timestamp_ms > 0);
}
#[test]
fn h004_device_snapshot_memory_percent() {
let snap = DeviceSnapshot {
device_id: DeviceId::cpu(),
timestamp_ms: 0,
compute_utilization: 50.0,
memory_used_bytes: 50 * 1024 * 1024 * 1024, memory_total_bytes: 100 * 1024 * 1024 * 1024, temperature_c: 45.0,
power_watts: 100.0,
clock_mhz: 3000,
};
assert!((snap.memory_usage_percent() - 50.0).abs() < 0.01);
}
#[test]
fn h004_device_snapshot_memory_percent_zero_total() {
let snap = DeviceSnapshot {
device_id: DeviceId::cpu(),
timestamp_ms: 0,
compute_utilization: 0.0,
memory_used_bytes: 0,
memory_total_bytes: 0, temperature_c: 0.0,
power_watts: 0.0,
clock_mhz: 0,
};
assert!((snap.memory_usage_percent() - 0.0).abs() < 0.01);
}
#[test]
fn h005_throttle_reason_display() {
assert_eq!(format!("{}", ThrottleReason::None), "None");
assert_eq!(format!("{}", ThrottleReason::Thermal), "Thermal");
assert_eq!(format!("{}", ThrottleReason::Power), "Power");
}
#[test]
fn h006_memory_usage_percent() {
let cpu = CpuDevice::new();
let percent = cpu.memory_usage_percent();
assert!(percent.is_ok());
let p = percent.unwrap();
assert!(p >= 0.0 && p <= 100.0);
}
#[test]
fn h006_memory_available_bytes() {
let cpu = CpuDevice::new();
let avail = cpu.memory_available_bytes().unwrap();
let total = cpu.memory_total_bytes().unwrap();
assert!(avail <= total);
}
#[test]
fn h006_memory_mb_helpers() {
let cpu = CpuDevice::new();
let used_mb = cpu.memory_used_mb().unwrap();
let total_mb = cpu.memory_total_mb().unwrap();
assert!(used_mb <= total_mb);
}
#[test]
fn h006_memory_gb_helper() {
let cpu = CpuDevice::new();
let total_gb = cpu.memory_total_gb().unwrap();
assert!(total_gb > 0.0);
}
#[test]
fn h007_thermal_throttling_detection() {
let cpu = CpuDevice::new();
let _ = cpu.is_thermal_throttling();
}
#[test]
fn h008_power_throttling_detection() {
let cpu = CpuDevice::new();
let _ = cpu.is_power_throttling();
}
#[test]
fn h009_cpu_unsupported_metrics() {
let cpu = CpuDevice::new();
assert!(cpu.pcie_tx_bytes_per_sec().is_err());
assert!(cpu.pcie_rx_bytes_per_sec().is_err());
assert_eq!(cpu.pcie_generation(), 0);
assert_eq!(cpu.pcie_width(), 0);
}
#[test]
fn h009_device_id_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(DeviceId::cpu());
set.insert(DeviceId::nvidia(0));
set.insert(DeviceId::nvidia(1));
set.insert(DeviceId::amd(0));
assert_eq!(set.len(), 4);
set.insert(DeviceId::cpu());
assert_eq!(set.len(), 4);
}
#[test]
fn h010_device_id_intel_display() {
let id = DeviceId::new(DeviceType::IntelGpu, 0);
assert_eq!(format!("{}", id), "Intel:0");
assert_eq!(id.device_type, DeviceType::IntelGpu);
let id2 = DeviceId::new(DeviceType::IntelGpu, 2);
assert_eq!(format!("{}", id2), "Intel:2");
}
#[test]
fn h010_device_id_apple_display() {
let id = DeviceId::new(DeviceType::AppleSilicon, 0);
assert_eq!(format!("{}", id), "Apple:0");
assert_eq!(id.device_type, DeviceType::AppleSilicon);
}
#[test]
fn h010_throttle_reason_all_variants() {
assert_eq!(format!("{}", ThrottleReason::None), "None");
assert_eq!(format!("{}", ThrottleReason::Thermal), "Thermal");
assert_eq!(format!("{}", ThrottleReason::Power), "Power");
assert_eq!(
format!("{}", ThrottleReason::ApplicationClocks),
"AppClocks"
);
assert_eq!(format!("{}", ThrottleReason::SwPowerCap), "SwPowerCap");
assert_eq!(format!("{}", ThrottleReason::HwSlowdown), "HwSlowdown");
assert_eq!(format!("{}", ThrottleReason::SyncBoost), "SyncBoost");
}
struct MockDevice {
mem_used: u64,
mem_total: u64,
power_current: f64,
power_limit: f64,
temperature: f64,
}
impl MockDevice {
fn new(
mem_used: u64,
mem_total: u64,
power_current: f64,
power_limit: f64,
temperature: f64,
) -> Self {
Self {
mem_used,
mem_total,
power_current,
power_limit,
temperature,
}
}
}
impl ComputeDevice for MockDevice {
fn device_id(&self) -> DeviceId {
DeviceId::cpu()
}
fn device_name(&self) -> &str {
"Mock"
}
fn device_type(&self) -> DeviceType {
DeviceType::Cpu
}
fn compute_utilization(&self) -> Result<f64, GpuError> {
Ok(50.0)
}
fn compute_clock_mhz(&self) -> Result<u32, GpuError> {
Ok(3000)
}
fn compute_temperature_c(&self) -> Result<f64, GpuError> {
Ok(self.temperature)
}
fn compute_power_watts(&self) -> Result<f64, GpuError> {
Ok(self.power_current)
}
fn compute_power_limit_watts(&self) -> Result<f64, GpuError> {
Ok(self.power_limit)
}
fn memory_used_bytes(&self) -> Result<u64, GpuError> {
Ok(self.mem_used)
}
fn memory_total_bytes(&self) -> Result<u64, GpuError> {
Ok(self.mem_total)
}
fn memory_bandwidth_gbps(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn compute_unit_count(&self) -> u32 {
8
}
fn active_compute_units(&self) -> Result<u32, GpuError> {
Ok(8)
}
fn pcie_tx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_rx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_generation(&self) -> u8 {
0
}
fn pcie_width(&self) -> u8 {
0
}
fn refresh(&mut self) -> Result<(), GpuError> {
Ok(())
}
}
#[test]
fn h011_memory_usage_percent_zero_total() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!((mock.memory_usage_percent().unwrap() - 0.0).abs() < 0.01);
}
#[test]
fn h011_memory_usage_percent_normal() {
let mock = MockDevice::new(
50 * 1024 * 1024 * 1024,
100 * 1024 * 1024 * 1024,
0.0,
0.0,
0.0,
);
assert!((mock.memory_usage_percent().unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn h011_memory_available_bytes() {
let mock = MockDevice::new(
30 * 1024 * 1024 * 1024,
100 * 1024 * 1024 * 1024,
0.0,
0.0,
0.0,
);
let available = mock.memory_available_bytes().unwrap();
assert_eq!(available, 70 * 1024 * 1024 * 1024);
}
#[test]
fn h011_memory_mb_gb_conversions() {
let mock = MockDevice::new(1024 * 1024 * 1024, 16 * 1024 * 1024 * 1024, 0.0, 0.0, 0.0);
assert_eq!(mock.memory_used_mb().unwrap(), 1024);
assert_eq!(mock.memory_total_mb().unwrap(), 16384);
assert!((mock.memory_total_gb().unwrap() - 16.0).abs() < 0.01);
}
#[test]
fn h011_power_usage_percent_zero_limit() {
let mock = MockDevice::new(0, 0, 100.0, 0.0, 0.0);
assert!((mock.power_usage_percent().unwrap() - 0.0).abs() < 0.01);
}
#[test]
fn h011_power_usage_percent_normal() {
let mock = MockDevice::new(0, 0, 150.0, 300.0, 0.0);
assert!((mock.power_usage_percent().unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn h011_thermal_throttling_below_threshold() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 75.0);
assert!(!mock.is_thermal_throttling().unwrap());
}
#[test]
fn h011_thermal_throttling_above_threshold() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 85.0);
assert!(mock.is_thermal_throttling().unwrap());
}
#[test]
fn h011_power_throttling_below_threshold() {
let mock = MockDevice::new(0, 0, 90.0, 100.0, 0.0);
assert!(!mock.is_power_throttling().unwrap());
}
#[test]
fn h011_power_throttling_above_threshold() {
let mock = MockDevice::new(0, 0, 98.0, 100.0, 0.0);
assert!(mock.is_power_throttling().unwrap());
}
#[test]
fn h012_device_snapshot_from_mock() {
let mock = MockDevice::new(
8 * 1024 * 1024 * 1024,
16 * 1024 * 1024 * 1024,
150.0,
300.0,
65.0,
);
let snapshot = DeviceSnapshot::capture(&mock).unwrap();
assert_eq!(snapshot.device_id, DeviceId::cpu());
assert!((snapshot.compute_utilization - 50.0).abs() < 0.01);
assert_eq!(snapshot.memory_used_bytes, 8 * 1024 * 1024 * 1024);
assert_eq!(snapshot.memory_total_bytes, 16 * 1024 * 1024 * 1024);
assert!((snapshot.temperature_c - 65.0).abs() < 0.01);
assert!((snapshot.power_watts - 150.0).abs() < 0.01);
assert_eq!(snapshot.clock_mhz, 3000);
}
#[test]
fn h013_cpu_device_refresh() {
let mut cpu = CpuDevice::new();
let _ = cpu.refresh();
let _ = cpu.compute_utilization();
let _ = cpu.memory_used_bytes();
}
#[test]
fn h013_cpu_device_multiple_refreshes() {
let mut cpu = CpuDevice::new();
for _ in 0..3 {
let _ = cpu.refresh();
}
}
#[test]
fn h013_cpu_device_name_not_empty() {
let cpu = CpuDevice::new();
assert!(!cpu.device_name().is_empty());
}
#[test]
fn h013_cpu_device_core_count_positive() {
let cpu = CpuDevice::new();
assert!(cpu.compute_unit_count() > 0);
}
#[test]
fn h013_cpu_clock_speed() {
let cpu = CpuDevice::new();
let _ = cpu.compute_clock_mhz();
}
#[test]
fn h013_cpu_temperature() {
let cpu = CpuDevice::new();
let _ = cpu.compute_temperature_c();
}
#[test]
fn h014_mock_device_all_methods() {
let mock = MockDevice::new(1024, 2048, 10.0, 100.0, 30.0);
assert_eq!(mock.device_id(), DeviceId::cpu());
assert_eq!(mock.device_name(), "Mock");
assert!(matches!(mock.device_type(), DeviceType::Cpu));
assert_eq!(mock.compute_unit_count(), 8);
assert_eq!(mock.memory_used_bytes().unwrap(), 1024);
assert_eq!(mock.memory_total_bytes().unwrap(), 2048);
assert!((mock.compute_utilization().unwrap() - 50.0).abs() < 0.01); assert!((mock.compute_temperature_c().unwrap() - 30.0).abs() < 0.01);
assert!((mock.compute_power_watts().unwrap() - 10.0).abs() < 0.01);
assert_eq!(mock.compute_clock_mhz().unwrap(), 3000);
}
#[test]
fn h014_mock_device_derived_metrics() {
let mock = MockDevice::new(1024, 2048, 10.0, 100.0, 30.0);
let usage_percent = mock.memory_usage_percent().unwrap();
assert!((usage_percent - 50.0).abs() < 0.01);
let available = mock.memory_available_bytes().unwrap();
assert_eq!(available, 1024); }
#[test]
fn h014_mock_device_mb_gb_helpers() {
let mock = MockDevice::new(
1024 * 1024 * 1024,
2 * 1024 * 1024 * 1024,
10.0,
100.0,
30.0,
);
let used_mb = mock.memory_used_mb().unwrap();
assert_eq!(used_mb, 1024);
let total_gb = mock.memory_total_gb().unwrap();
assert!((total_gb - 2.0).abs() < 0.1); }
#[test]
fn h015_device_id_display() {
assert_eq!(format!("{}", DeviceId::cpu()), "CPU");
assert_eq!(format!("{}", DeviceId::nvidia(0)), "NVIDIA:0");
assert_eq!(format!("{}", DeviceId::nvidia(1)), "NVIDIA:1");
assert_eq!(format!("{}", DeviceId::amd(0)), "AMD:0");
}
#[test]
fn h015_device_id_debug() {
let cpu_id = DeviceId::cpu();
let debug_str = format!("{:?}", cpu_id);
assert!(debug_str.contains("Cpu"));
}
#[test]
fn h015_device_id_clone() {
let id1 = DeviceId::nvidia(0);
let id2 = id1.clone();
assert_eq!(id1, id2);
}
#[test]
fn h016_snapshot_debug() {
let mock = MockDevice::new(1024, 2048, 10.0, 100.0, 30.0);
let snapshot = DeviceSnapshot::capture(&mock).unwrap();
let debug_str = format!("{:?}", snapshot);
assert!(debug_str.contains("DeviceSnapshot"));
}
#[test]
fn h016_snapshot_clone() {
let mock = MockDevice::new(1024, 2048, 10.0, 100.0, 30.0);
let snapshot1 = DeviceSnapshot::capture(&mock).unwrap();
let snapshot2 = snapshot1.clone();
assert_eq!(snapshot1.device_id, snapshot2.device_id);
assert_eq!(snapshot1.memory_used_bytes, snapshot2.memory_used_bytes);
}
#[test]
fn h017_device_type_display() {
assert_eq!(format!("{}", DeviceType::Cpu), "CPU");
assert_eq!(format!("{}", DeviceType::NvidiaGpu), "NVIDIA GPU");
assert_eq!(format!("{}", DeviceType::AmdGpu), "AMD GPU");
assert_eq!(format!("{}", DeviceType::IntelGpu), "Intel GPU");
}
#[test]
fn h017_device_type_debug() {
let gpu = DeviceType::NvidiaGpu;
let debug_str = format!("{:?}", gpu);
assert!(debug_str.contains("NvidiaGpu"));
}
#[test]
fn h018_cpu_unsupported_pcie_metrics() {
let cpu = CpuDevice::new();
let tx = cpu.pcie_tx_bytes_per_sec();
assert!(matches!(tx, Err(GpuError::NotSupported(_))));
let rx = cpu.pcie_rx_bytes_per_sec();
assert!(matches!(rx, Err(GpuError::NotSupported(_))));
}
#[test]
fn h018_cpu_power_metrics() {
let cpu = CpuDevice::new();
let power = cpu.compute_power_watts();
assert!(matches!(power, Err(GpuError::NotSupported(_))));
let power_limit = cpu.compute_power_limit_watts();
assert!(matches!(power_limit, Err(GpuError::NotSupported(_))));
let bw = cpu.memory_bandwidth_gbps();
assert!(matches!(bw, Err(GpuError::NotSupported(_))));
}
#[test]
fn h019_cpu_active_compute_units() {
let cpu = CpuDevice::new();
let active = cpu.active_compute_units();
assert!(active.is_ok());
let count = active.unwrap();
assert!(count > 0, "Should have at least one active compute unit");
assert_eq!(
count,
cpu.compute_unit_count(),
"Active should equal total cores"
);
}
#[test]
fn h020_mock_device_pcie_metrics() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!(matches!(
mock.pcie_tx_bytes_per_sec(),
Err(GpuError::NotSupported(_))
));
assert!(matches!(
mock.pcie_rx_bytes_per_sec(),
Err(GpuError::NotSupported(_))
));
assert_eq!(mock.pcie_generation(), 0);
assert_eq!(mock.pcie_width(), 0);
}
#[test]
fn h020_mock_device_active_compute_units() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
let active = mock.active_compute_units();
assert!(active.is_ok());
assert_eq!(active.unwrap(), 8); }
#[test]
fn h020_mock_device_memory_bandwidth() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!(matches!(
mock.memory_bandwidth_gbps(),
Err(GpuError::NotSupported(_))
));
}
#[test]
fn h020_mock_device_refresh() {
let mut mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!(mock.refresh().is_ok());
}
#[test]
fn h021_cpu_device_memory_mb_conversion() {
let cpu = CpuDevice::new();
if let Ok(used_bytes) = cpu.memory_used_bytes() {
let used_mb = cpu.memory_used_mb();
assert!(used_mb.is_ok());
assert_eq!(used_mb.unwrap(), used_bytes / (1024 * 1024));
}
}
#[test]
fn h021_cpu_device_memory_total_mb() {
let cpu = CpuDevice::new();
if let Ok(total_bytes) = cpu.memory_total_bytes() {
let total_mb = cpu.memory_total_mb();
assert!(total_mb.is_ok());
assert_eq!(total_mb.unwrap(), total_bytes / (1024 * 1024));
}
}
#[test]
fn h021_cpu_device_memory_total_gb() {
let cpu = CpuDevice::new();
if let Ok(total_bytes) = cpu.memory_total_bytes() {
let total_gb = cpu.memory_total_gb();
assert!(total_gb.is_ok());
let expected_gb = total_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
assert!((total_gb.unwrap() - expected_gb).abs() < 0.001);
}
}
#[test]
fn h021_cpu_device_memory_available() {
let cpu = CpuDevice::new();
if let (Ok(used), Ok(total)) = (cpu.memory_used_bytes(), cpu.memory_total_bytes()) {
let available = cpu.memory_available_bytes();
assert!(available.is_ok());
assert_eq!(available.unwrap(), total.saturating_sub(used));
}
}
#[test]
fn h022_mock_device_thermal_throttling_at_threshold() {
let mock_at_80 = MockDevice::new(0, 0, 0.0, 0.0, 80.0);
assert!(!mock_at_80.is_thermal_throttling().unwrap());
let mock_at_80_1 = MockDevice::new(0, 0, 0.0, 0.0, 80.1);
assert!(mock_at_80_1.is_thermal_throttling().unwrap());
}
#[test]
fn h022_mock_device_power_throttling_at_threshold() {
let mock_at_95 = MockDevice::new(0, 0, 95.0, 100.0, 0.0);
assert!(!mock_at_95.is_power_throttling().unwrap());
let mock_at_95_1 = MockDevice::new(0, 0, 95.1, 100.0, 0.0);
assert!(mock_at_95_1.is_power_throttling().unwrap());
}
#[test]
fn h022_mock_device_memory_usage_full() {
let mock_full = MockDevice::new(100, 100, 0.0, 0.0, 0.0);
assert!((mock_full.memory_usage_percent().unwrap() - 100.0).abs() < 0.01);
assert_eq!(mock_full.memory_available_bytes().unwrap(), 0);
}
#[test]
fn h023_device_snapshot_field_access() {
let mock = MockDevice::new(
8 * 1024 * 1024 * 1024,
32 * 1024 * 1024 * 1024,
250.0,
350.0,
72.0,
);
let snapshot = DeviceSnapshot::capture(&mock).unwrap();
assert_eq!(snapshot.memory_used_bytes, 8 * 1024 * 1024 * 1024);
assert_eq!(snapshot.memory_total_bytes, 32 * 1024 * 1024 * 1024);
assert!((snapshot.temperature_c - 72.0).abs() < 0.01);
assert!((snapshot.power_watts - 250.0).abs() < 0.01);
assert_eq!(snapshot.clock_mhz, 3000);
assert!((snapshot.memory_usage_percent() - 25.0).abs() < 0.01);
}
#[test]
fn h024_cpu_device_refresh_populates_fields() {
let mut cpu = CpuDevice::new();
let result = cpu.refresh();
assert!(result.is_ok());
let util = cpu.compute_utilization();
assert!(util.is_ok());
let util_val = util.unwrap();
assert!(util_val >= 0.0 && util_val <= 100.0);
let mem = cpu.memory_used_bytes();
assert!(mem.is_ok());
}
#[test]
fn h024_cpu_device_refresh_multiple_times() {
let mut cpu = CpuDevice::new();
for _ in 0..5 {
assert!(cpu.refresh().is_ok());
}
assert!(cpu.compute_utilization().is_ok());
assert!(cpu.memory_used_bytes().is_ok());
}
#[test]
fn h025_cpu_device_read_core_count() {
let cpu = CpuDevice::new();
let count = cpu.compute_unit_count();
assert!(count >= 1, "Should have at least 1 core");
assert!(
count <= 1024,
"Sanity check: should have fewer than 1024 cores"
);
}
#[test]
fn h025_cpu_device_read_total_memory() {
let cpu = CpuDevice::new();
let total = cpu.memory_total_bytes().unwrap();
assert!(total >= 1024 * 1024 * 1024, "Should have at least 1GB");
assert!(total < 100 * 1024 * 1024 * 1024 * 1024, "Sanity: < 100TB");
}
#[test]
fn h025_cpu_device_read_cpu_name() {
let cpu = CpuDevice::new();
let name = cpu.device_name();
assert!(!name.is_empty(), "CPU name should not be empty");
}
#[test]
fn h026_cpu_device_compute_clock_value() {
let cpu = CpuDevice::new();
match cpu.compute_clock_mhz() {
Ok(mhz) => {
assert!(mhz >= 100, "Clock should be at least 100 MHz");
assert!(mhz <= 10000, "Clock should be at most 10 GHz");
}
Err(GpuError::NotSupported(_)) => {
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
fn h027_cpu_device_temperature_value() {
let mut cpu = CpuDevice::new();
cpu.refresh().unwrap();
match cpu.compute_temperature_c() {
Ok(temp) => {
assert!(temp >= 0.0, "Temperature should be non-negative");
assert!(temp <= 150.0, "Temperature should be at most 150C");
}
Err(GpuError::NotSupported(_)) => {
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
fn h028_cpu_device_cpu_usage_after_refresh() {
let mut cpu = CpuDevice::new();
cpu.refresh().unwrap();
let usage = cpu.compute_utilization().unwrap();
assert!(usage >= 0.0, "CPU usage should be non-negative");
assert!(usage <= 100.0, "CPU usage should be at most 100%");
}
#[test]
fn h029_cpu_device_memory_used_after_refresh() {
let mut cpu = CpuDevice::new();
cpu.refresh().unwrap();
let used = cpu.memory_used_bytes().unwrap();
let total = cpu.memory_total_bytes().unwrap();
assert!(used <= total, "Used memory should not exceed total");
assert!(used > 0, "Some memory should be in use");
}
#[test]
fn h030_mock_device_device_name() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert_eq!(mock.device_name(), "Mock");
}
#[test]
fn h030_mock_device_device_type() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!(matches!(mock.device_type(), DeviceType::Cpu));
}
#[test]
fn h030_mock_device_device_id() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert_eq!(mock.device_id(), DeviceId::cpu());
}
#[test]
fn h030_mock_device_compute_utilization() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert!((mock.compute_utilization().unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn h030_mock_device_compute_clock() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert_eq!(mock.compute_clock_mhz().unwrap(), 3000);
}
#[test]
fn h030_mock_device_compute_temperature() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 45.0);
assert!((mock.compute_temperature_c().unwrap() - 45.0).abs() < 0.01);
}
#[test]
fn h030_mock_device_compute_power() {
let mock = MockDevice::new(0, 0, 200.0, 300.0, 0.0);
assert!((mock.compute_power_watts().unwrap() - 200.0).abs() < 0.01);
assert!((mock.compute_power_limit_watts().unwrap() - 300.0).abs() < 0.01);
}
#[test]
fn h030_mock_device_compute_unit_count() {
let mock = MockDevice::new(0, 0, 0.0, 0.0, 0.0);
assert_eq!(mock.compute_unit_count(), 8);
}
#[test]
fn h030_mock_device_memory_bytes() {
let mock = MockDevice::new(1000, 2000, 0.0, 0.0, 0.0);
assert_eq!(mock.memory_used_bytes().unwrap(), 1000);
assert_eq!(mock.memory_total_bytes().unwrap(), 2000);
}
struct ErrorMockDevice {
return_error: bool,
}
impl ErrorMockDevice {
fn new(return_error: bool) -> Self {
Self { return_error }
}
}
impl ComputeDevice for ErrorMockDevice {
fn device_id(&self) -> DeviceId {
DeviceId::cpu()
}
fn device_name(&self) -> &str {
"ErrorMock"
}
fn device_type(&self) -> DeviceType {
DeviceType::Cpu
}
fn compute_utilization(&self) -> Result<f64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(50.0)
}
}
fn compute_clock_mhz(&self) -> Result<u32, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(3000)
}
}
fn compute_temperature_c(&self) -> Result<f64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(50.0)
}
}
fn compute_power_watts(&self) -> Result<f64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(100.0)
}
}
fn compute_power_limit_watts(&self) -> Result<f64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(200.0)
}
}
fn memory_used_bytes(&self) -> Result<u64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(1024)
}
}
fn memory_total_bytes(&self) -> Result<u64, GpuError> {
if self.return_error {
Err(GpuError::NotSupported("test".into()))
} else {
Ok(2048)
}
}
fn memory_bandwidth_gbps(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn compute_unit_count(&self) -> u32 {
8
}
fn active_compute_units(&self) -> Result<u32, GpuError> {
Ok(8)
}
fn pcie_tx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_rx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_generation(&self) -> u8 {
0
}
fn pcie_width(&self) -> u8 {
0
}
fn refresh(&mut self) -> Result<(), GpuError> {
Ok(())
}
}
#[test]
fn h031_error_mock_memory_usage_percent_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.memory_usage_percent();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_memory_available_bytes_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.memory_available_bytes();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_memory_used_mb_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.memory_used_mb();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_memory_total_mb_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.memory_total_mb();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_memory_total_gb_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.memory_total_gb();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_power_usage_percent_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.power_usage_percent();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_is_thermal_throttling_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.is_thermal_throttling();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_is_power_throttling_error() {
let mock = ErrorMockDevice::new(true);
let result = mock.is_power_throttling();
assert!(result.is_err());
}
#[test]
fn h031_error_mock_working_correctly() {
let mock = ErrorMockDevice::new(false);
assert!(mock.memory_usage_percent().is_ok());
assert!(mock.memory_available_bytes().is_ok());
assert!(mock.memory_used_mb().is_ok());
assert!(mock.memory_total_mb().is_ok());
assert!(mock.memory_total_gb().is_ok());
assert!(mock.power_usage_percent().is_ok());
assert!(mock.is_thermal_throttling().is_ok());
assert!(mock.is_power_throttling().is_ok());
}
#[test]
fn h032_device_snapshot_with_errors() {
let mock = ErrorMockDevice::new(true);
let snapshot = DeviceSnapshot::capture(&mock);
assert!(snapshot.is_ok());
let snap = snapshot.unwrap();
assert_eq!(snap.compute_utilization, 0.0);
assert_eq!(snap.memory_used_bytes, 0);
assert_eq!(snap.memory_total_bytes, 0);
assert_eq!(snap.temperature_c, 0.0);
assert_eq!(snap.power_watts, 0.0);
assert_eq!(snap.clock_mhz, 0);
}
#[test]
fn h033_throttle_reason_clone() {
let reason = ThrottleReason::Power;
let cloned = reason.clone();
assert_eq!(reason, cloned);
}
#[test]
fn h033_throttle_reason_copy() {
let reason = ThrottleReason::Thermal;
let copied: ThrottleReason = reason; assert_eq!(reason, copied);
}
#[test]
fn h033_throttle_reason_equality() {
assert_eq!(ThrottleReason::None, ThrottleReason::None);
assert_eq!(ThrottleReason::Thermal, ThrottleReason::Thermal);
assert_ne!(ThrottleReason::None, ThrottleReason::Thermal);
assert_ne!(ThrottleReason::Power, ThrottleReason::Thermal);
}
#[test]
fn h033_throttle_reason_debug() {
let reason = ThrottleReason::HwSlowdown;
let debug_str = format!("{:?}", reason);
assert!(debug_str.contains("HwSlowdown"));
}
#[test]
fn h034_device_type_clone() {
let dt = DeviceType::NvidiaGpu;
let cloned = dt.clone();
assert_eq!(dt, cloned);
}
#[test]
fn h034_device_type_copy() {
let dt = DeviceType::AmdGpu;
let copied: DeviceType = dt; assert_eq!(dt, copied);
}
#[test]
fn h034_device_type_equality() {
assert_eq!(DeviceType::Cpu, DeviceType::Cpu);
assert_ne!(DeviceType::Cpu, DeviceType::NvidiaGpu);
assert_ne!(DeviceType::NvidiaGpu, DeviceType::AmdGpu);
assert_ne!(DeviceType::AmdGpu, DeviceType::IntelGpu);
assert_ne!(DeviceType::IntelGpu, DeviceType::AppleSilicon);
}
#[test]
fn h034_device_type_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(DeviceType::Cpu);
set.insert(DeviceType::NvidiaGpu);
set.insert(DeviceType::AmdGpu);
set.insert(DeviceType::IntelGpu);
set.insert(DeviceType::AppleSilicon);
assert_eq!(set.len(), 5);
set.insert(DeviceType::Cpu);
assert_eq!(set.len(), 5);
}
#[test]
fn h035_device_id_copy() {
let id = DeviceId::nvidia(0);
let copied: DeviceId = id; assert_eq!(id, copied);
}
#[test]
fn h035_device_id_new_with_all_types() {
let cpu = DeviceId::new(DeviceType::Cpu, 0);
let nvidia = DeviceId::new(DeviceType::NvidiaGpu, 1);
let amd = DeviceId::new(DeviceType::AmdGpu, 2);
let intel = DeviceId::new(DeviceType::IntelGpu, 3);
let apple = DeviceId::new(DeviceType::AppleSilicon, 4);
assert_eq!(cpu.device_type, DeviceType::Cpu);
assert_eq!(cpu.index, 0);
assert_eq!(nvidia.device_type, DeviceType::NvidiaGpu);
assert_eq!(nvidia.index, 1);
assert_eq!(amd.device_type, DeviceType::AmdGpu);
assert_eq!(amd.index, 2);
assert_eq!(intel.device_type, DeviceType::IntelGpu);
assert_eq!(intel.index, 3);
assert_eq!(apple.device_type, DeviceType::AppleSilicon);
assert_eq!(apple.index, 4);
}
#[test]
fn h036_cpu_device_memory_used_realistic() {
let mut cpu = CpuDevice::new();
cpu.refresh().unwrap();
let used = cpu.memory_used_bytes().unwrap();
let total = cpu.memory_total_bytes().unwrap();
assert!(used <= total);
}
#[test]
fn h037_memory_usage_percent_boundary_values() {
let mock_empty = MockDevice::new(0, 1000, 0.0, 0.0, 0.0);
assert!((mock_empty.memory_usage_percent().unwrap() - 0.0).abs() < 0.01);
let mock_full = MockDevice::new(1000, 1000, 0.0, 0.0, 0.0);
assert!((mock_full.memory_usage_percent().unwrap() - 100.0).abs() < 0.01);
}
#[test]
fn h037_memory_available_boundary() {
let mock_empty = MockDevice::new(0, 1000, 0.0, 0.0, 0.0);
assert_eq!(mock_empty.memory_available_bytes().unwrap(), 1000);
let mock_full = MockDevice::new(1000, 1000, 0.0, 0.0, 0.0);
assert_eq!(mock_full.memory_available_bytes().unwrap(), 0);
}
#[test]
fn h037_power_usage_percent_boundary() {
let mock_idle = MockDevice::new(0, 0, 0.0, 100.0, 0.0);
assert!((mock_idle.power_usage_percent().unwrap() - 0.0).abs() < 0.01);
let mock_max = MockDevice::new(0, 0, 100.0, 100.0, 0.0);
assert!((mock_max.power_usage_percent().unwrap() - 100.0).abs() < 0.01);
}
#[test]
fn h038_snapshot_memory_percent_edge_cases() {
let snap_50 = DeviceSnapshot {
device_id: DeviceId::cpu(),
timestamp_ms: 12345,
compute_utilization: 25.0,
memory_used_bytes: 500,
memory_total_bytes: 1000,
temperature_c: 60.0,
power_watts: 75.0,
clock_mhz: 2500,
};
assert!((snap_50.memory_usage_percent() - 50.0).abs() < 0.01);
let snap_0 = DeviceSnapshot {
device_id: DeviceId::cpu(),
timestamp_ms: 0,
compute_utilization: 0.0,
memory_used_bytes: 0,
memory_total_bytes: 1000,
temperature_c: 0.0,
power_watts: 0.0,
clock_mhz: 0,
};
assert!((snap_0.memory_usage_percent() - 0.0).abs() < 0.01);
let snap_100 = DeviceSnapshot {
device_id: DeviceId::cpu(),
timestamp_ms: 0,
compute_utilization: 0.0,
memory_used_bytes: 1000,
memory_total_bytes: 1000,
temperature_c: 0.0,
power_watts: 0.0,
clock_mhz: 0,
};
assert!((snap_100.memory_usage_percent() - 100.0).abs() < 0.01);
}
#[test]
fn h039_cpu_device_device_id_and_type() {
let cpu = CpuDevice::new();
assert_eq!(cpu.device_id(), DeviceId::cpu());
assert_eq!(cpu.device_type(), DeviceType::Cpu);
}
#[test]
fn h039_cpu_utilization_initial() {
let cpu = CpuDevice::new();
let util = cpu.compute_utilization().unwrap();
assert!(util >= 0.0 && util <= 100.0);
}
#[test]
fn h039_cpu_memory_used_initial() {
let cpu = CpuDevice::new();
let used = cpu.memory_used_bytes().unwrap();
assert!(used >= 0);
}
#[test]
fn h039_cpu_active_units_equals_total() {
let cpu = CpuDevice::new();
let total = cpu.compute_unit_count();
let active = cpu.active_compute_units().unwrap();
assert_eq!(total, active);
}
#[test]
fn h040_mock_device_power_limit_access() {
let mock = MockDevice::new(0, 0, 50.0, 100.0, 0.0);
let limit = mock.compute_power_limit_watts().unwrap();
assert!((limit - 100.0).abs() < 0.01);
}
#[test]
fn h040_mock_device_memory_total_access() {
let mock = MockDevice::new(500, 1000, 0.0, 0.0, 0.0);
let total = mock.memory_total_bytes().unwrap();
assert_eq!(total, 1000);
}
#[test]
fn h041_error_mock_device_name() {
let mock = ErrorMockDevice::new(false);
assert_eq!(mock.device_name(), "ErrorMock");
}
#[test]
fn h041_error_mock_device_type() {
let mock = ErrorMockDevice::new(false);
assert_eq!(mock.device_type(), DeviceType::Cpu);
}
#[test]
fn h041_error_mock_compute_units() {
let mock = ErrorMockDevice::new(false);
assert_eq!(mock.compute_unit_count(), 8);
assert_eq!(mock.active_compute_units().unwrap(), 8);
}
#[test]
fn h041_error_mock_pcie_metrics() {
let mock = ErrorMockDevice::new(false);
assert_eq!(mock.pcie_generation(), 0);
assert_eq!(mock.pcie_width(), 0);
assert!(mock.pcie_tx_bytes_per_sec().is_err());
assert!(mock.pcie_rx_bytes_per_sec().is_err());
}
#[test]
fn h041_error_mock_refresh() {
let mut mock = ErrorMockDevice::new(false);
assert!(mock.refresh().is_ok());
}
#[test]
fn h041_error_mock_memory_bandwidth() {
let mock = ErrorMockDevice::new(false);
assert!(mock.memory_bandwidth_gbps().is_err());
}
struct PartialErrorMockDevice {
error_on_total: bool,
error_on_limit: bool,
}
impl PartialErrorMockDevice {
fn with_total_error() -> Self {
Self {
error_on_total: true,
error_on_limit: false,
}
}
fn with_limit_error() -> Self {
Self {
error_on_total: false,
error_on_limit: true,
}
}
}
impl ComputeDevice for PartialErrorMockDevice {
fn device_id(&self) -> DeviceId {
DeviceId::cpu()
}
fn device_name(&self) -> &str {
"PartialErrorMock"
}
fn device_type(&self) -> DeviceType {
DeviceType::Cpu
}
fn compute_utilization(&self) -> Result<f64, GpuError> {
Ok(50.0)
}
fn compute_clock_mhz(&self) -> Result<u32, GpuError> {
Ok(3000)
}
fn compute_temperature_c(&self) -> Result<f64, GpuError> {
Ok(50.0)
}
fn compute_power_watts(&self) -> Result<f64, GpuError> {
Ok(100.0)
}
fn compute_power_limit_watts(&self) -> Result<f64, GpuError> {
if self.error_on_limit {
Err(GpuError::NotSupported("limit error".into()))
} else {
Ok(200.0)
}
}
fn memory_used_bytes(&self) -> Result<u64, GpuError> {
Ok(1024)
}
fn memory_total_bytes(&self) -> Result<u64, GpuError> {
if self.error_on_total {
Err(GpuError::NotSupported("total error".into()))
} else {
Ok(2048)
}
}
fn memory_bandwidth_gbps(&self) -> Result<f64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn compute_unit_count(&self) -> u32 {
8
}
fn active_compute_units(&self) -> Result<u32, GpuError> {
Ok(8)
}
fn pcie_tx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_rx_bytes_per_sec(&self) -> Result<u64, GpuError> {
Err(GpuError::NotSupported("mock".into()))
}
fn pcie_generation(&self) -> u8 {
0
}
fn pcie_width(&self) -> u8 {
0
}
fn refresh(&mut self) -> Result<(), GpuError> {
Ok(())
}
}
#[test]
fn h042_partial_error_memory_usage_percent_total_error() {
let mock = PartialErrorMockDevice::with_total_error();
let result = mock.memory_usage_percent();
assert!(result.is_err());
}
#[test]
fn h042_partial_error_memory_available_bytes_total_error() {
let mock = PartialErrorMockDevice::with_total_error();
let result = mock.memory_available_bytes();
assert!(result.is_err());
}
#[test]
fn h042_partial_error_power_usage_percent_limit_error() {
let mock = PartialErrorMockDevice::with_limit_error();
let result = mock.power_usage_percent();
assert!(result.is_err());
}
#[test]
fn h042_partial_error_device_name() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.device_name(), "PartialErrorMock");
}
#[test]
fn h042_partial_error_device_type() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.device_type(), DeviceType::Cpu);
}
#[test]
fn h042_partial_error_device_id() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.device_id(), DeviceId::cpu());
}
#[test]
fn h042_partial_error_compute_utilization() {
let mock = PartialErrorMockDevice::with_total_error();
assert!((mock.compute_utilization().unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn h042_partial_error_compute_clock() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.compute_clock_mhz().unwrap(), 3000);
}
#[test]
fn h042_partial_error_compute_temperature() {
let mock = PartialErrorMockDevice::with_total_error();
assert!((mock.compute_temperature_c().unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn h042_partial_error_compute_power() {
let mock = PartialErrorMockDevice::with_total_error();
assert!((mock.compute_power_watts().unwrap() - 100.0).abs() < 0.01);
}
#[test]
fn h042_partial_error_memory_used() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.memory_used_bytes().unwrap(), 1024);
}
#[test]
fn h042_partial_error_compute_units() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.compute_unit_count(), 8);
assert_eq!(mock.active_compute_units().unwrap(), 8);
}
#[test]
fn h042_partial_error_pcie_metrics() {
let mock = PartialErrorMockDevice::with_total_error();
assert_eq!(mock.pcie_generation(), 0);
assert_eq!(mock.pcie_width(), 0);
assert!(mock.pcie_tx_bytes_per_sec().is_err());
assert!(mock.pcie_rx_bytes_per_sec().is_err());
}
#[test]
fn h042_partial_error_memory_bandwidth() {
let mock = PartialErrorMockDevice::with_total_error();
assert!(mock.memory_bandwidth_gbps().is_err());
}
#[test]
fn h042_partial_error_refresh() {
let mut mock = PartialErrorMockDevice::with_total_error();
assert!(mock.refresh().is_ok());
}
#[test]
fn h043_device_snapshot_timestamp_non_zero() {
let mock = MockDevice::new(1024, 2048, 100.0, 200.0, 50.0);
let snapshot = DeviceSnapshot::capture(&mock).unwrap();
assert!(snapshot.timestamp_ms > 0);
}
#[test]
fn h043_device_snapshot_all_fields_populated() {
let mock = MockDevice::new(1024, 2048, 100.0, 200.0, 50.0);
let snapshot = DeviceSnapshot::capture(&mock).unwrap();
assert_eq!(snapshot.device_id, DeviceId::cpu());
assert!((snapshot.compute_utilization - 50.0).abs() < 0.01);
assert_eq!(snapshot.memory_used_bytes, 1024);
assert_eq!(snapshot.memory_total_bytes, 2048);
assert!((snapshot.temperature_c - 50.0).abs() < 0.01);
assert!((snapshot.power_watts - 100.0).abs() < 0.01);
assert_eq!(snapshot.clock_mhz, 3000);
}
#[test]
fn h044_cpu_device_debug() {
let cpu = CpuDevice::new();
let debug_str = format!("{:?}", cpu);
assert!(debug_str.contains("CpuDevice"));
}
}