use crate::device::{
ChassisInfo, CpuInfo, GpuInfo, MemoryInfo, MigGpuInfo, ProcessInfo, VgpuHostInfo,
};
use crate::metrics::energy::PowerIntegrator;
use crate::storage::info::StorageInfo;
use crate::utils::RuntimeEnvironment;
use super::{
MetricExporter, chassis::ChassisMetricExporter, cpu::CpuMetricExporter,
disk::DiskMetricExporter, energy::EnergyMetricExporter,
exporter_status::ExporterStatusMetricExporter, gpu::GpuMetricExporter,
hardware::HardwareMetricExporter, memory::MemoryMetricExporter, mig::MigMetricExporter,
npu::NpuMetricExporter, process::ProcessMetricExporter, runtime::RuntimeMetricExporter,
vgpu::VgpuMetricExporter,
};
pub struct MetricsRenderInputs<'a> {
pub gpu_info: &'a [GpuInfo],
pub process_info: &'a [ProcessInfo],
pub cpu_info: &'a [CpuInfo],
pub memory_info: &'a [MemoryInfo],
pub storage_info: &'a [StorageInfo],
pub runtime_environment: &'a RuntimeEnvironment,
pub chassis_info: &'a [ChassisInfo],
pub vgpu_info: &'a [VgpuHostInfo],
pub mig_info: &'a [MigGpuInfo],
pub energy_integrator: Option<&'a PowerIntegrator>,
pub ready: bool,
}
pub fn render_prometheus_exposition(inputs: &MetricsRenderInputs<'_>) -> String {
let mut all_metrics = String::new();
let status_exporter = ExporterStatusMetricExporter::new(inputs.ready);
all_metrics.push_str(&status_exporter.export_metrics());
if !inputs.gpu_info.is_empty() {
let gpu_exporter = GpuMetricExporter::new(inputs.gpu_info);
all_metrics.push_str(&gpu_exporter.export_metrics());
let npu_exporter = NpuMetricExporter::new(inputs.gpu_info);
all_metrics.push_str(&npu_exporter.export_metrics());
}
if !inputs.process_info.is_empty() {
let process_exporter = ProcessMetricExporter::new(inputs.process_info);
all_metrics.push_str(&process_exporter.export_metrics());
}
if !inputs.cpu_info.is_empty() {
let cpu_exporter = CpuMetricExporter::new(inputs.cpu_info);
all_metrics.push_str(&cpu_exporter.export_metrics());
}
if !inputs.memory_info.is_empty() {
let memory_exporter = MemoryMetricExporter::new(inputs.memory_info);
all_metrics.push_str(&memory_exporter.export_metrics());
}
if !inputs.storage_info.is_empty() {
let disk_exporter = DiskMetricExporter::new(inputs.storage_info);
all_metrics.push_str(&disk_exporter.export_metrics());
}
let runtime_exporter = RuntimeMetricExporter::new(inputs.runtime_environment);
all_metrics.push_str(&runtime_exporter.export_metrics());
if !inputs.chassis_info.is_empty() {
let chassis_exporter = ChassisMetricExporter::new(inputs.chassis_info);
all_metrics.push_str(&chassis_exporter.export_metrics());
}
if !inputs.vgpu_info.is_empty() {
let vgpu_exporter = VgpuMetricExporter::new(inputs.vgpu_info);
all_metrics.push_str(&vgpu_exporter.export_metrics());
}
if !inputs.mig_info.is_empty() {
let mig_exporter = MigMetricExporter::new(inputs.mig_info);
all_metrics.push_str(&mig_exporter.export_metrics());
}
if !inputs.gpu_info.is_empty() {
let hw_exporter = HardwareMetricExporter::new(inputs.gpu_info);
all_metrics.push_str(&hw_exporter.export_metrics());
}
if let Some(integrator) = inputs.energy_integrator {
let energy_exporter = EnergyMetricExporter::new(integrator, inputs.gpu_info);
all_metrics.push_str(&energy_exporter.export_metrics());
}
all_metrics
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_inputs(env: &RuntimeEnvironment, ready: bool) -> MetricsRenderInputs<'_> {
MetricsRenderInputs {
gpu_info: &[],
process_info: &[],
cpu_info: &[],
memory_info: &[],
storage_info: &[],
runtime_environment: env,
chassis_info: &[],
vgpu_info: &[],
mig_info: &[],
energy_integrator: None,
ready,
}
}
#[test]
fn empty_inputs_still_render_the_baseline() {
let env = RuntimeEnvironment::default();
let rendered = render_prometheus_exposition(&empty_inputs(&env, false));
assert!(
!rendered.is_empty(),
"the exposition must never be byte-empty (issue #324)"
);
assert!(rendered.contains("all_smi_up{"), "{rendered}");
assert!(rendered.contains("all_smi_build_info{"), "{rendered}");
}
#[test]
fn empty_inputs_render_only_the_baseline_families() {
let env = RuntimeEnvironment::default();
let rendered = render_prometheus_exposition(&empty_inputs(&env, false));
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 input set: {line}"
);
}
}
#[test]
fn ready_flag_drives_the_up_gauge() {
let env = RuntimeEnvironment::default();
for (ready, expected) in [(false, " 0"), (true, " 1")] {
let rendered = render_prometheus_exposition(&empty_inputs(&env, ready));
let up_line = rendered
.lines()
.find(|l| l.starts_with("all_smi_up{"))
.expect("all_smi_up sample line");
assert!(
up_line.ends_with(expected),
"ready={ready} should render all_smi_up{expected}, got {up_line}"
);
}
}
}