use crate::mock::metrics::{CpuMetrics, GpuMetrics, MemoryMetrics};
use all_smi::traits::mock_generator::{
MockConfig, MockData, MockGenerator, MockPlatform, MockResult,
};
pub const HARDWARE_DETAILS_ENV_VAR: &str = "ALL_SMI_MOCK_HARDWARE_DETAILS";
pub fn is_hardware_details_enabled() -> bool {
std::env::var(HARDWARE_DETAILS_ENV_VAR)
.map(|v| !v.is_empty())
.unwrap_or(false)
}
pub struct NvidiaMockGenerator {
gpu_name: String,
instance_name: String,
}
impl NvidiaMockGenerator {
pub fn new(gpu_name: Option<String>, instance_name: String) -> Self {
Self {
gpu_name: gpu_name.unwrap_or_else(|| "NVIDIA H100 80GB HBM3".to_string()),
instance_name,
}
}
pub fn build_nvidia_template(
&self,
gpus: &[GpuMetrics],
cpu: &CpuMetrics,
memory: &MemoryMetrics,
) -> String {
let mut template = String::with_capacity(4096);
self.add_gpu_metrics(&mut template, gpus);
if is_hardware_details_enabled() {
self.add_pstate_metrics(&mut template, gpus);
self.add_thermal_threshold_metrics(&mut template, gpus);
self.add_hardware_detail_metrics(&mut template, gpus);
}
self.add_process_metrics(&mut template, gpus);
self.add_driver_metrics(&mut template);
self.add_system_metrics(&mut template, cpu, memory);
crate::mock::templates::common::add_chassis_metrics(&mut template, &self.instance_name);
crate::mock::templates::vgpu::maybe_add_vgpu_template(
&mut template,
&self.instance_name,
&self.gpu_name,
gpus,
);
crate::mock::templates::mig::maybe_add_mig_template(
&mut template,
&self.instance_name,
&self.gpu_name,
gpus,
);
crate::mock::templates::process::maybe_add_process_template(
&mut template,
&self.instance_name,
gpus,
);
crate::mock::templates::topology::maybe_add_topology_template(
&mut template,
&self.gpu_name,
&self.instance_name,
gpus,
);
template
}
fn add_thermal_threshold_metrics(&self, template: &mut String, gpus: &[GpuMetrics]) {
const SLOWDOWN: u32 = 90;
const SHUTDOWN: u32 = 95;
const MAX_OPERATING: u32 = 87;
const ACOUSTIC: u32 = 77;
for (metric_name, help_text, value) in [
(
"all_smi_gpu_temperature_threshold_slowdown_celsius",
"GPU slowdown temperature threshold in Celsius",
SLOWDOWN,
),
(
"all_smi_gpu_temperature_threshold_shutdown_celsius",
"GPU shutdown temperature threshold in Celsius",
SHUTDOWN,
),
(
"all_smi_gpu_temperature_threshold_max_operating_celsius",
"GPU maximum operating temperature threshold in Celsius",
MAX_OPERATING,
),
(
"all_smi_gpu_temperature_threshold_acoustic_celsius",
"GPU acoustic (noise) temperature threshold in Celsius",
ACOUSTIC,
),
] {
template.push_str(&format!("# HELP {metric_name} {help_text}\n"));
template.push_str(&format!("# TYPE {metric_name} gauge\n"));
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!("{metric_name}{{{labels}}} {value}\n"));
}
}
template.push_str(
"# HELP all_smi_gpu_performance_state GPU performance state \
(0=P0 fastest, 15=P15 idlest; metric is omitted when the device does not report a P-state)\n",
);
template.push_str("# TYPE all_smi_gpu_performance_state gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_performance_state{{{labels}}} {{{{PSTATE_{i}}}}}\n"
));
}
}
fn add_hardware_detail_metrics(&self, template: &mut String, gpus: &[GpuMetrics]) {
const GSP_MODE: u8 = 1;
const GSP_VERSION: &str = "550.54.15";
const NVLINK_COUNT: u32 = 6;
const SM_OCCUPANCY: f32 = 0.67;
const MEMORY_BW_UTIL: f32 = 0.42;
template.push_str(
"# HELP all_smi_gpu_numa_node_id NUMA node the GPU is attached to \
(metric is omitted when the host has no NUMA topology or the driver does not report one)\n",
);
template.push_str("# TYPE all_smi_gpu_numa_node_id gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let numa = (i as u32) % 2;
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!("all_smi_gpu_numa_node_id{{{labels}}} {numa}\n"));
}
template.push_str(
"# HELP all_smi_gpu_gsp_firmware_mode GSP firmware mode \
(0=disabled, 1=enabled, 2=default); omitted when the driver does not expose the GSP firmware API\n",
);
template.push_str("# TYPE all_smi_gpu_gsp_firmware_mode gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_gsp_firmware_mode{{{labels}}} {GSP_MODE}\n"
));
}
template.push_str(
"# HELP all_smi_gpu_gsp_firmware_version_info GSP firmware version, encoded as a constant 1 \
with the version in a `version` label; omitted when unsupported\n",
);
template.push_str("# TYPE all_smi_gpu_gsp_firmware_version_info gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\", version=\"{GSP_VERSION}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_gsp_firmware_version_info{{{labels}}} 1\n"
));
}
template.push_str(
"# HELP all_smi_nvlink_remote_device_type NvLink remote endpoint classification per active link. \
Value is always 1; classification is carried in the `remote_type` label (gpu / switch / ibmnpu / unknown).\n",
);
template.push_str("# TYPE all_smi_nvlink_remote_device_type gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
for link in 0..NVLINK_COUNT {
let remote_type = if link == NVLINK_COUNT - 1 {
"switch"
} else {
"gpu"
};
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\", link_index=\"{link}\", remote_type=\"{remote_type}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_nvlink_remote_device_type{{{labels}}} 1\n"
));
}
}
template.push_str(
"# HELP all_smi_gpu_sm_occupancy GPM-reported SM occupancy fraction (0.0-1.0); \
omitted on devices that do not support GPM (pre-Hopper)\n",
);
template.push_str("# TYPE all_smi_gpu_sm_occupancy gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_sm_occupancy{{{labels}}} {SM_OCCUPANCY:.2}\n"
));
}
template.push_str(
"# HELP all_smi_gpu_memory_bandwidth_utilization GPM-reported memory bandwidth utilization fraction (0.0-1.0); \
omitted on devices that do not support GPM (pre-Hopper)\n",
);
template.push_str("# TYPE all_smi_gpu_memory_bandwidth_utilization gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_memory_bandwidth_utilization{{{labels}}} {MEMORY_BW_UTIL:.2}\n"
));
}
}
fn add_gpu_metrics(&self, template: &mut String, gpus: &[GpuMetrics]) {
let gpu_metrics = [
("all_smi_gpu_utilization", "GPU utilization percentage"),
("all_smi_gpu_memory_used_bytes", "GPU memory used in bytes"),
(
"all_smi_gpu_memory_total_bytes",
"GPU memory total in bytes",
),
(
"all_smi_gpu_temperature_celsius",
"GPU temperature in celsius",
),
(
"all_smi_gpu_power_consumption_watts",
"GPU power consumption in watts",
),
("all_smi_gpu_frequency_mhz", "GPU frequency in MHz"),
];
for (metric_name, help_text) in gpu_metrics {
template.push_str(&format!("# HELP {metric_name} {help_text}\n"));
template.push_str(&format!("# TYPE {metric_name} gauge\n"));
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
let placeholder = match metric_name {
"all_smi_gpu_utilization" => format!("{{{{UTIL_{i}}}}}"),
"all_smi_gpu_memory_used_bytes" => format!("{{{{MEM_USED_{i}}}}}"),
"all_smi_gpu_memory_total_bytes" => format!("{{{{MEM_TOTAL_{i}}}}}"),
"all_smi_gpu_temperature_celsius" => format!("{{{{TEMP_{i}}}}}"),
"all_smi_gpu_power_consumption_watts" => format!("{{{{POWER_{i}}}}}"),
"all_smi_gpu_frequency_mhz" => format!("{{{{FREQ_{i}}}}}"),
_ => "0".to_string(),
};
template.push_str(&format!("{metric_name}{{{labels}}} {placeholder}\n"));
}
}
self.add_gpu_info_metric(template, gpus);
}
fn add_gpu_info_metric(&self, template: &mut String, gpus: &[GpuMetrics]) {
use crate::mock::constants::{DEFAULT_CUDA_VERSION, DEFAULT_NVIDIA_DRIVER_VERSION};
template.push_str("# HELP all_smi_gpu_info GPU device information\n");
template.push_str("# TYPE all_smi_gpu_info gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\", \
driver_version=\"{DEFAULT_NVIDIA_DRIVER_VERSION}\", cuda_version=\"{DEFAULT_CUDA_VERSION}\", \
lib_name=\"CUDA\", lib_version=\"{DEFAULT_CUDA_VERSION}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!("all_smi_gpu_info{{{labels}}} 1\n"));
}
}
fn add_pstate_metrics(&self, template: &mut String, gpus: &[GpuMetrics]) {
template.push_str("# HELP all_smi_gpu_pstate GPU performance state\n");
template.push_str("# TYPE all_smi_gpu_pstate gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_pstate{{{labels}}} {{{{PSTATE_{i}}}}}\n"
));
}
}
fn add_process_metrics(&self, template: &mut String, gpus: &[GpuMetrics]) {
template.push_str("# HELP all_smi_gpu_process_count Number of processes running on GPU\n");
template.push_str("# TYPE all_smi_gpu_process_count gauge\n");
for (i, gpu) in gpus.iter().enumerate() {
let labels = format!(
"gpu=\"{}\", instance=\"{}\", gpu_uuid=\"{}\", gpu_index=\"{i}\"",
self.gpu_name, self.instance_name, gpu.uuid
);
template.push_str(&format!(
"all_smi_gpu_process_count{{{labels}}} {{{{PROC_COUNT_{i}}}}}\n"
));
}
}
fn add_driver_metrics(&self, template: &mut String) {
template.push_str("# HELP all_smi_nvidia_driver_version NVIDIA driver version\n");
template.push_str("# TYPE all_smi_nvidia_driver_version gauge\n");
template.push_str(&format!(
"all_smi_nvidia_driver_version{{instance=\"{}\"}} 1\n",
self.instance_name
));
}
fn add_system_metrics(&self, template: &mut String, cpu: &CpuMetrics, memory: &MemoryMetrics) {
template.push_str("# HELP all_smi_cpu_utilization CPU utilization percentage\n");
template.push_str("# TYPE all_smi_cpu_utilization gauge\n");
template.push_str(&format!(
"all_smi_cpu_utilization{{instance=\"{}\"}} {{{{CPU_UTIL}}}}\n",
self.instance_name
));
template.push_str("# HELP all_smi_cpu_core_count Total number of CPU cores\n");
template.push_str("# TYPE all_smi_cpu_core_count gauge\n");
template.push_str(&format!(
"all_smi_cpu_core_count{{instance=\"{}\"}} {}\n",
self.instance_name, cpu.core_count
));
template.push_str("# HELP all_smi_cpu_model CPU model name\n");
template.push_str("# TYPE all_smi_cpu_model info\n");
template.push_str(&format!(
"all_smi_cpu_model{{instance=\"{}\", model=\"{}\"}} 1\n",
self.instance_name, cpu.model
));
template.push_str("# HELP all_smi_cpu_frequency_mhz CPU frequency in MHz\n");
template.push_str("# TYPE all_smi_cpu_frequency_mhz gauge\n");
template.push_str(&format!(
"all_smi_cpu_frequency_mhz{{instance=\"{}\"}} {}\n",
self.instance_name, cpu.frequency_mhz
));
template.push_str("# HELP all_smi_cpu_temperature_celsius CPU temperature in celsius\n");
template.push_str("# TYPE all_smi_cpu_temperature_celsius gauge\n");
if let Some(temp) = cpu.temperature_celsius {
template.push_str(&format!(
"all_smi_cpu_temperature_celsius{{instance=\"{}\"}} {temp}\n",
self.instance_name
));
}
template.push_str("# HELP all_smi_memory_used_bytes System memory used in bytes\n");
template.push_str("# TYPE all_smi_memory_used_bytes gauge\n");
template.push_str(&format!(
"all_smi_memory_used_bytes{{instance=\"{}\"}} {{{{MEM_USED}}}}\n",
self.instance_name
));
template.push_str("# HELP all_smi_memory_total_bytes System memory total in bytes\n");
template.push_str("# TYPE all_smi_memory_total_bytes gauge\n");
template.push_str(&format!(
"all_smi_memory_total_bytes{{instance=\"{}\"}} {}\n",
self.instance_name, memory.total_bytes
));
if memory.swap_total_bytes > 0 {
template.push_str("# HELP all_smi_swap_total_bytes Total swap space in bytes\n");
template.push_str("# TYPE all_smi_swap_total_bytes gauge\n");
template.push_str(&format!(
"all_smi_swap_total_bytes{{instance=\"{}\"}} {}\n",
self.instance_name, memory.swap_total_bytes
));
template.push_str("# HELP all_smi_swap_used_bytes Used swap space in bytes\n");
template.push_str("# TYPE all_smi_swap_used_bytes gauge\n");
template.push_str(&format!(
"all_smi_swap_used_bytes{{instance=\"{}\"}} {{{{SWAP_USED}}}}\n",
self.instance_name
));
template.push_str("# HELP all_smi_swap_free_bytes Free swap space in bytes\n");
template.push_str("# TYPE all_smi_swap_free_bytes gauge\n");
template.push_str(&format!(
"all_smi_swap_free_bytes{{instance=\"{}\"}} {{{{SWAP_FREE}}}}\n",
self.instance_name
));
}
}
pub fn render_nvidia_response(
&self,
template: &str,
gpus: &[GpuMetrics],
cpu: &CpuMetrics,
memory: &MemoryMetrics,
) -> String {
let mut response = template.to_string();
for (i, gpu) in gpus.iter().enumerate() {
response = response
.replace(
&format!("{{{{UTIL_{i}}}}}"),
&format!("{:.2}", gpu.utilization),
)
.replace(
&format!("{{{{MEM_USED_{i}}}}}"),
&gpu.memory_used_bytes.to_string(),
)
.replace(
&format!("{{{{MEM_TOTAL_{i}}}}}"),
&gpu.memory_total_bytes.to_string(),
)
.replace(
&format!("{{{{TEMP_{i}}}}}"),
&gpu.temperature_celsius.to_string(),
)
.replace(
&format!("{{{{POWER_{i}}}}}"),
&format!("{:.3}", gpu.power_consumption_watts),
)
.replace(&format!("{{{{FREQ_{i}}}}}"), &gpu.frequency_mhz.to_string());
let pstate = if gpu.utilization > 80.0 {
0 } else if gpu.utilization > 50.0 {
2 } else if gpu.utilization > 20.0 {
5 } else if gpu.utilization > 0.0 {
8 } else {
12 };
response = response.replace(&format!("{{{{PSTATE_{i}}}}}"), &pstate.to_string());
response = response.replace(&format!("{{{{PROC_COUNT_{i}}}}}"), "0");
}
response = response
.replace("{{CPU_UTIL}}", &format!("{:.2}", cpu.utilization))
.replace("{{MEM_USED}}", &memory.used_bytes.to_string());
response = response
.replace("{{SWAP_USED}}", &memory.swap_used_bytes.to_string())
.replace("{{SWAP_FREE}}", &memory.swap_free_bytes.to_string());
response = crate::mock::templates::common::render_chassis_metrics(response, gpus);
response = crate::mock::templates::vgpu::maybe_render_vgpu_response(response, gpus);
response = crate::mock::templates::mig::maybe_render_mig_response(response, gpus);
response
}
}
impl MockGenerator for NvidiaMockGenerator {
fn generate(&self, config: &MockConfig) -> MockResult<MockData> {
self.validate_config(config)?;
use rand::{RngExt, rng};
let mut rng = rng();
let gpus: Vec<GpuMetrics> = (0..config.device_count)
.map(|_| {
GpuMetrics {
uuid: crate::mock::metrics::gpu::generate_uuid_with_rng(&mut rng),
utilization: rng.random_range(0.0..100.0),
memory_used_bytes: rng.random_range(1_000_000_000..80_000_000_000),
memory_total_bytes: 85_899_345_920, temperature_celsius: rng.random_range(35..75),
power_consumption_watts: rng.random_range(100.0..450.0),
frequency_mhz: rng.random_range(1200..1980),
ane_utilization_watts: 0.0,
thermal_pressure_level: None,
}
})
.collect();
let cpu = CpuMetrics {
model: "Intel Xeon Platinum".to_string(),
utilization: rng.random_range(10.0..90.0),
socket_count: 2,
core_count: 128,
thread_count: 256,
frequency_mhz: 2400,
temperature_celsius: Some(65),
power_consumption_watts: Some(250.0),
socket_utilizations: vec![rng.random_range(10.0..90.0), rng.random_range(10.0..90.0)],
p_core_count: None,
e_core_count: None,
gpu_core_count: None,
p_core_utilization: None,
e_core_utilization: None,
p_cluster_frequency_mhz: None,
e_cluster_frequency_mhz: None,
per_core_utilization: vec![],
};
let swap_total: u64 = 34_359_738_368; let swap_used = rng.random_range(0..8_000_000_000);
let memory = MemoryMetrics {
total_bytes: 1099511627776, used_bytes: rng.random_range(10_000_000_000..500_000_000_000),
available_bytes: rng.random_range(100_000_000_000..600_000_000_000),
free_bytes: rng.random_range(50_000_000_000..400_000_000_000),
cached_bytes: rng.random_range(10_000_000_000..100_000_000_000),
buffers_bytes: rng.random_range(1_000_000_000..10_000_000_000),
swap_total_bytes: swap_total,
swap_used_bytes: swap_used,
swap_free_bytes: swap_total.saturating_sub(swap_used),
utilization: rng.random_range(10.0..90.0),
};
let template = self.build_nvidia_template(&gpus, &cpu, &memory);
let response = self.render_nvidia_response(&template, &gpus, &cpu, &memory);
Ok(MockData {
response,
content_type: "text/plain; version=0.0.4".to_string(),
timestamp: chrono::Utc::now(),
platform: MockPlatform::Nvidia,
})
}
fn generate_template(&self, config: &MockConfig) -> MockResult<String> {
self.validate_config(config)?;
let gpus: Vec<GpuMetrics> = (0..config.device_count)
.map(|i| GpuMetrics {
uuid: format!("GPU-{:08x}", i as u32),
utilization: 0.0,
memory_used_bytes: 0,
memory_total_bytes: 85_899_345_920,
temperature_celsius: 0,
power_consumption_watts: 0.0,
frequency_mhz: 0,
ane_utilization_watts: 0.0,
thermal_pressure_level: None,
})
.collect();
let cpu = CpuMetrics {
model: "Intel Xeon Platinum".to_string(),
utilization: 0.0,
socket_count: 2,
core_count: 128,
thread_count: 256,
frequency_mhz: 2400,
temperature_celsius: Some(65),
power_consumption_watts: Some(250.0),
socket_utilizations: vec![0.0, 0.0],
p_core_count: None,
e_core_count: None,
gpu_core_count: None,
p_core_utilization: None,
e_core_utilization: None,
p_cluster_frequency_mhz: None,
e_cluster_frequency_mhz: None,
per_core_utilization: vec![],
};
let memory = MemoryMetrics {
total_bytes: 1099511627776,
used_bytes: 0,
available_bytes: 1099511627776,
free_bytes: 1099511627776,
cached_bytes: 0,
buffers_bytes: 0,
swap_total_bytes: 0,
swap_used_bytes: 0,
swap_free_bytes: 0,
utilization: 0.0,
};
Ok(self.build_nvidia_template(&gpus, &cpu, &memory))
}
fn render(&self, template: &str, config: &MockConfig) -> MockResult<String> {
self.validate_config(config)?;
Ok(template.to_string())
}
fn platform(&self) -> MockPlatform {
MockPlatform::Nvidia
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_gpu_metrics() -> Vec<GpuMetrics> {
vec![GpuMetrics {
uuid: "GPU-0".to_string(),
utilization: 50.0,
memory_used_bytes: 1024,
memory_total_bytes: 8192,
temperature_celsius: 65,
power_consumption_watts: 200.0,
frequency_mhz: 1500,
ane_utilization_watts: 0.0,
thermal_pressure_level: None,
}]
}
fn make_cpu_metrics() -> CpuMetrics {
CpuMetrics {
model: "Intel Xeon".to_string(),
utilization: 10.0,
socket_count: 1,
core_count: 8,
thread_count: 16,
frequency_mhz: 2400,
temperature_celsius: Some(50),
power_consumption_watts: Some(100.0),
socket_utilizations: vec![10.0],
p_core_count: None,
e_core_count: None,
gpu_core_count: None,
p_core_utilization: None,
e_core_utilization: None,
p_cluster_frequency_mhz: None,
e_cluster_frequency_mhz: None,
per_core_utilization: vec![],
}
}
fn make_memory_metrics() -> MemoryMetrics {
MemoryMetrics {
total_bytes: 1024,
used_bytes: 512,
available_bytes: 512,
free_bytes: 512,
cached_bytes: 0,
buffers_bytes: 0,
swap_total_bytes: 0,
swap_used_bytes: 0,
swap_free_bytes: 0,
utilization: 50.0,
}
}
fn make_memory_metrics_with_swap() -> MemoryMetrics {
MemoryMetrics {
total_bytes: 1024,
used_bytes: 512,
available_bytes: 512,
free_bytes: 512,
cached_bytes: 0,
buffers_bytes: 0,
swap_total_bytes: 4_294_967_296,
swap_used_bytes: 536_870_912,
swap_free_bytes: 3_758_096_384,
utilization: 50.0,
}
}
#[test]
fn mock_template_omits_swap_metrics_when_swap_total_is_zero() {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl = gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
for metric in &[
"all_smi_swap_total_bytes",
"all_smi_swap_used_bytes",
"all_smi_swap_free_bytes",
] {
assert!(
!tpl.contains(metric),
"swap metric {metric:?} should be absent when swap_total_bytes == 0"
);
}
}
#[test]
fn mock_template_emits_swap_metrics_when_swap_total_is_nonzero() {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let memory = make_memory_metrics_with_swap();
let tpl = gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &memory);
for metric in &[
"all_smi_swap_total_bytes",
"all_smi_swap_used_bytes",
"all_smi_swap_free_bytes",
] {
assert!(
tpl.contains(metric),
"swap metric {metric:?} should be present when swap_total_bytes > 0"
);
}
assert!(
tpl.contains("{{SWAP_USED}}") && tpl.contains("{{SWAP_FREE}}"),
"swap value placeholders missing from template"
);
}
#[test]
fn mock_render_resolves_swap_placeholders() {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let memory = make_memory_metrics_with_swap();
let tpl = gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &memory);
let rendered = gen_.render_nvidia_response(&tpl, &gpus, &make_cpu_metrics(), &memory);
assert!(
!rendered.contains("{{SWAP_USED}}") && !rendered.contains("{{SWAP_FREE}}"),
"swap placeholders should be resolved after render; got:\n{rendered}"
);
assert!(rendered.contains(&memory.swap_used_bytes.to_string()));
assert!(rendered.contains(&memory.swap_free_bytes.to_string()));
}
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
struct EnvVarGuard {
_lock: std::sync::MutexGuard<'static, ()>,
prior: Option<String>,
}
impl EnvVarGuard {
fn new() -> Self {
let lock = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let prior = std::env::var(HARDWARE_DETAILS_ENV_VAR).ok();
Self { _lock: lock, prior }
}
fn set(&self, value: &str) {
unsafe {
std::env::set_var(HARDWARE_DETAILS_ENV_VAR, value);
}
}
fn remove(&self) {
unsafe {
std::env::remove_var(HARDWARE_DETAILS_ENV_VAR);
}
}
}
impl Drop for EnvVarGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(HARDWARE_DETAILS_ENV_VAR, v),
None => std::env::remove_var(HARDWARE_DETAILS_ENV_VAR),
}
}
}
}
fn with_hardware_details_enabled<F: FnOnce()>(f: F) {
let guard = EnvVarGuard::new();
guard.set("1");
f();
}
#[test]
fn mock_template_omits_hardware_details_by_default() {
let guard = EnvVarGuard::new();
guard.remove();
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl = gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
for metric in &[
"all_smi_gpu_temperature_threshold_slowdown_celsius",
"all_smi_gpu_temperature_threshold_shutdown_celsius",
"all_smi_gpu_temperature_threshold_max_operating_celsius",
"all_smi_gpu_temperature_threshold_acoustic_celsius",
"all_smi_gpu_performance_state{",
"all_smi_gpu_pstate{",
"all_smi_gpu_numa_node_id{",
"all_smi_gpu_gsp_firmware_mode{",
"all_smi_gpu_gsp_firmware_version_info{",
"all_smi_nvlink_remote_device_type{",
"all_smi_gpu_sm_occupancy{",
"all_smi_gpu_memory_bandwidth_utilization{",
] {
assert!(
!tpl.contains(metric),
"metric {metric:?} should be absent when ALL_SMI_MOCK_HARDWARE_DETAILS is unset, but was found in:\n{tpl}"
);
}
assert!(
tpl.contains("all_smi_gpu_utilization{"),
"basic GPU metric absent"
);
}
#[test]
fn is_hardware_details_enabled_reflects_env_var() {
let guard = EnvVarGuard::new();
guard.remove();
assert!(!is_hardware_details_enabled());
guard.set("1");
assert!(is_hardware_details_enabled());
guard.set("");
assert!(!is_hardware_details_enabled());
}
#[test]
fn mock_template_includes_threshold_metrics() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
assert!(
tpl.contains("all_smi_gpu_temperature_threshold_slowdown_celsius"),
"mock template missing slowdown metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_temperature_threshold_shutdown_celsius"),
"mock template missing shutdown metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_temperature_threshold_max_operating_celsius"),
"mock template missing max_operating metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_temperature_threshold_acoustic_celsius"),
"mock template missing acoustic metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_performance_state{"),
"mock template missing canonical pstate metric:\n{tpl}"
);
});
}
#[test]
fn mock_render_resolves_pstate_placeholders() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
let rendered = gen_.render_nvidia_response(
&tpl,
&gpus,
&make_cpu_metrics(),
&make_memory_metrics(),
);
assert!(
!rendered.contains("{{PSTATE_"),
"unresolved PSTATE placeholder in rendered output"
);
});
}
fn make_multi_gpu_metrics(count: usize) -> Vec<GpuMetrics> {
(0..count)
.map(|i| GpuMetrics {
uuid: format!("GPU-{i}"),
utilization: 50.0,
memory_used_bytes: 1024,
memory_total_bytes: 8192,
temperature_celsius: 65,
power_consumption_watts: 200.0,
frequency_mhz: 1500,
ane_utilization_watts: 0.0,
thermal_pressure_level: None,
})
.collect()
}
#[test]
fn mock_template_includes_all_hardware_detail_metrics() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
assert!(
tpl.contains("all_smi_gpu_numa_node_id{"),
"mock template missing NUMA metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_gsp_firmware_mode{"),
"mock template missing GSP firmware mode metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_gsp_firmware_version_info{"),
"mock template missing GSP firmware version info metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_nvlink_remote_device_type{"),
"mock template missing NvLink metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_sm_occupancy{"),
"mock template missing SM occupancy metric:\n{tpl}"
);
assert!(
tpl.contains("all_smi_gpu_memory_bandwidth_utilization{"),
"mock template missing memory bandwidth utilization metric:\n{tpl}"
);
});
}
#[test]
fn mock_template_emits_six_nvlinks_per_gpu() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_multi_gpu_metrics(2);
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
let nvlink_lines: Vec<_> = tpl
.lines()
.filter(|l| l.starts_with("all_smi_nvlink_remote_device_type{"))
.collect();
assert_eq!(
nvlink_lines.len(),
12,
"expected 12 NvLink rows (2 GPUs x 6 links):\n{}",
nvlink_lines.join("\n")
);
let gpu_remote_count = nvlink_lines
.iter()
.filter(|l| l.contains(r#"remote_type="gpu""#))
.count();
let switch_remote_count = nvlink_lines
.iter()
.filter(|l| l.contains(r#"remote_type="switch""#))
.count();
assert_eq!(gpu_remote_count, 10);
assert_eq!(switch_remote_count, 2);
});
}
#[test]
fn mock_template_numa_alternates_between_zero_and_one() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_multi_gpu_metrics(4);
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
let numa_lines: Vec<_> = tpl
.lines()
.filter(|l| l.starts_with("all_smi_gpu_numa_node_id{"))
.collect();
assert_eq!(numa_lines.len(), 4);
for (i, line) in numa_lines.iter().enumerate() {
let expected = (i as u32) % 2;
assert!(
line.ends_with(&format!(" {expected}")),
"GPU {i} NUMA line expected to end with ' {expected}', got: {line}"
);
}
});
}
#[test]
fn mock_template_emits_gsp_firmware_version_label() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
assert!(
tpl.contains(r#"version="550.54.15""#),
"mock template missing GSP firmware version label:\n{tpl}"
);
});
}
#[test]
fn mock_template_gpm_values_are_in_0_to_1_range() {
with_hardware_details_enabled(|| {
let gen_ = NvidiaMockGenerator::new(None, "mock-node".to_string());
let gpus = make_gpu_metrics();
let tpl =
gen_.build_nvidia_template(&gpus, &make_cpu_metrics(), &make_memory_metrics());
let sm_line = tpl
.lines()
.find(|l| l.starts_with("all_smi_gpu_sm_occupancy{"))
.expect("SM occupancy line");
let value: f32 = sm_line
.rsplit(' ')
.next()
.and_then(|s| s.parse().ok())
.expect("SM occupancy value");
assert!(
(0.0..=1.0).contains(&value),
"SM occupancy out of range: {value}"
);
});
}
}