use anyhow::Result;
use crate::api::metrics::render::{MetricsRenderInputs, render_prometheus_exposition};
use crate::snapshot::Snapshot;
use crate::utils::RuntimeEnvironment;
pub fn render(snapshots: &[Snapshot]) -> Result<String> {
let snap = snapshots
.first()
.ok_or_else(|| anyhow::anyhow!("Prometheus serializer requires at least one snapshot"))?;
let runtime_env = RuntimeEnvironment::default();
let empty_vgpu = Vec::new();
let empty_mig = Vec::new();
let inputs = MetricsRenderInputs {
gpu_info: snap.gpus.as_deref().unwrap_or(&[]),
process_info: snap.processes.as_deref().unwrap_or(&[]),
cpu_info: snap.cpus.as_deref().unwrap_or(&[]),
memory_info: snap.memory.as_deref().unwrap_or(&[]),
storage_info: snap.storage.as_deref().unwrap_or(&[]),
runtime_environment: &runtime_env,
chassis_info: snap.chassis.as_deref().unwrap_or(&[]),
vgpu_info: &empty_vgpu,
mig_info: &empty_mig,
energy_integrator: None,
ready: true,
};
let out = render_prometheus_exposition(&inputs);
for err in &snap.errors {
eprintln!(
"snapshot: {section} reader {kind}: {message}",
section = err.section,
kind = err.kind,
message = err.message
);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::GpuInfo;
use crate::snapshot::Snapshot;
use std::collections::HashMap;
fn make_gpu() -> GpuInfo {
GpuInfo {
uuid: "GPU-0".to_string(),
time: "2026-04-20T00:00:00Z".to_string(),
name: "Test GPU".to_string(),
device_type: "GPU".to_string(),
host_id: "host0".to_string(),
hostname: "host0".to_string(),
instance: "host0:9090".to_string(),
utilization: 50.0,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature: 55,
used_memory: 2048,
total_memory: 8192,
frequency: 1500,
power_consumption: 200.0,
gpu_core_count: None,
temperature_threshold_slowdown: None,
temperature_threshold_shutdown: None,
temperature_threshold_max_operating: None,
temperature_threshold_acoustic: None,
performance_state: None,
fan_speed_rpm: None,
numa_node_id: None,
gsp_firmware_mode: None,
gsp_firmware_version: None,
nvlink_remote_devices: Vec::new(),
gpm_metrics: None,
detail: HashMap::new(),
}
}
#[test]
fn empty_snapshot_still_renders_the_baseline() {
let snap = Snapshot {
schema: 1,
timestamp: "2026-04-20T00:00:00Z".to_string(),
hostname: "host0".to_string(),
gpus: None,
cpus: None,
memory: None,
chassis: None,
processes: None,
storage: None,
errors: Vec::new(),
};
let rendered = render(&[snap]).unwrap();
assert!(!rendered.is_empty());
let up = rendered
.lines()
.find(|l| l.starts_with("all_smi_up{"))
.expect("all_smi_up sample line");
assert!(
up.ends_with(" 1"),
"a completed one-shot collection is up, even with no sections: {up}"
);
assert!(rendered.contains("all_smi_build_info{"));
for line in rendered.lines().filter(|l| !l.starts_with('#')) {
assert!(
line.starts_with("all_smi_up{") || line.starts_with("all_smi_build_info{"),
"unexpected sample from an empty snapshot: {line}"
);
}
}
#[test]
fn gpu_snapshot_produces_expected_metric_names() {
let snap = Snapshot {
schema: 1,
timestamp: "2026-04-20T00:00:00Z".to_string(),
hostname: "host0".to_string(),
gpus: Some(vec![make_gpu()]),
cpus: None,
memory: None,
chassis: None,
processes: None,
storage: None,
errors: Vec::new(),
};
let rendered = render(&[snap]).unwrap();
assert!(
rendered.contains("all_smi_gpu_utilization"),
"missing GPU utilization metric: {rendered}"
);
assert!(rendered.contains("all_smi_gpu_memory_used_bytes"));
assert!(rendered.contains("all_smi_gpu_temperature_celsius"));
}
#[test]
fn empty_inputs_return_error() {
let result = render(&[]);
assert!(result.is_err());
}
}