#[derive(Debug, Default, Clone)]
pub struct GaudiMetricsData {
pub devices: Vec<GaudiDeviceMetrics>,
}
#[derive(Debug, Clone)]
pub struct GaudiDeviceMetrics {
pub index: u32,
pub uuid: String,
pub name: String,
pub driver_version: String,
pub memory_total: u64,
pub memory_used: u64,
pub memory_free: u64,
pub power_draw: f64,
pub power_max: f64,
pub temperature: u32,
pub utilization: f64,
}
pub fn map_device_name(internal_name: &str) -> String {
let name = internal_name.trim();
if name.starts_with("HL-100") {
return "Intel Gaudi".to_string();
}
if name.starts_with("HL-2") {
let variant = match name {
n if n.starts_with("HL-200") => "Mezzanine",
n if n.starts_with("HL-205") => "PCIe",
n if n.starts_with("HL-225") => "OAM",
_ => "",
};
return if variant.is_empty() {
"Intel Gaudi 2".to_string()
} else {
format!("Intel Gaudi 2 {variant}")
};
}
if name.starts_with("HL-3") {
let (variant, suffix) = match name {
n if n.starts_with("HL-325L") => ("PCIe", " LP"), n if n.starts_with("HL-325") => ("PCIe", ""),
n if n.starts_with("HL-328") => ("OAM", ""),
n if n.starts_with("HL-338") => ("UBB", ""),
n if n.starts_with("HL-388") => ("HLS", ""), _ => ("", ""),
};
return if variant.is_empty() {
"Intel Gaudi 3".to_string()
} else {
format!("Intel Gaudi 3 {variant}{suffix}")
};
}
if name.starts_with("HL-4") {
return "Intel Gaudi 4".to_string();
}
if name.starts_with("HL-5") {
return "Intel Gaudi 5".to_string();
}
format!("Intel {name}")
}
impl Default for GaudiDeviceMetrics {
fn default() -> Self {
Self {
index: 0,
uuid: String::new(),
name: String::new(),
driver_version: String::new(),
memory_total: 0,
memory_used: 0,
memory_free: 0,
power_draw: 0.0,
power_max: 0.0,
temperature: 0,
utilization: 0.0,
}
}
}
pub fn parse_hlsmi_output(output: &str) -> Result<GaudiMetricsData, Box<dyn std::error::Error>> {
let mut data = GaudiMetricsData::default();
for line in output.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
if parts.len() < 11 {
continue; }
let device = GaudiDeviceMetrics {
index: parse_index(parts[0])?,
uuid: parts[1].to_string(),
name: parts[2].to_string(),
driver_version: strip_driver_revision(parts[3]),
memory_total: parse_memory_mib(parts[4])?,
memory_used: parse_memory_mib(parts[5])?,
memory_free: parse_memory_mib(parts[6])?,
power_draw: parse_power(parts[7])?,
power_max: parse_power(parts[8])?,
temperature: parse_temperature(parts[9])?,
utilization: parse_utilization(parts[10])?,
};
data.devices.push(device);
}
Ok(data)
}
fn parse_index(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
s.trim()
.parse::<u32>()
.map_err(|e| format!("Failed to parse index '{s}': {e}").into())
}
fn parse_memory_mib(s: &str) -> Result<u64, Box<dyn std::error::Error>> {
let s = s.trim().trim_end_matches("MiB").trim();
s.parse::<u64>()
.map_err(|e| format!("Failed to parse memory '{s}': {e}").into())
}
fn parse_power(s: &str) -> Result<f64, Box<dyn std::error::Error>> {
let s = s.trim().trim_end_matches('W').trim();
s.parse::<f64>()
.map_err(|e| format!("Failed to parse power '{s}': {e}").into())
}
fn parse_temperature(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
let s = s.trim().trim_end_matches('C').trim();
s.parse::<u32>()
.map_err(|e| format!("Failed to parse temperature '{s}': {e}").into())
}
fn parse_utilization(s: &str) -> Result<f64, Box<dyn std::error::Error>> {
let s = s.trim().trim_end_matches('%').trim();
s.parse::<f64>()
.map_err(|e| format!("Failed to parse utilization '{s}': {e}").into())
}
fn strip_driver_revision(s: &str) -> String {
let s = s.trim();
if let Some(idx) = s.rfind('-') {
let suffix = &s[idx + 1..];
if suffix.len() >= 6 && suffix.chars().all(|c| c.is_ascii_hexdigit()) {
return s[..idx].to_string();
}
}
s.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_hlsmi_output() {
let output = "0, 01P4-HL3090A0-18-U4V193-22-07-00, HL-325L, 1.22.1-97ec1a4, 131072 MiB, 672 MiB, 130400 MiB, 226 W, 850 W, 36 C, 0 %\n\
1, 01P4-HL3090A0-18-U4V298-03-04-04, HL-325L, 1.22.1-97ec1a4, 131072 MiB, 672 MiB, 130400 MiB, 230 W, 850 W, 39 C, 0 %";
let result = parse_hlsmi_output(output);
assert!(result.is_ok());
let data = result.unwrap();
assert_eq!(data.devices.len(), 2);
assert_eq!(data.devices[0].index, 0);
assert_eq!(data.devices[0].uuid, "01P4-HL3090A0-18-U4V193-22-07-00");
assert_eq!(data.devices[0].name, "HL-325L");
assert_eq!(data.devices[0].driver_version, "1.22.1");
assert_eq!(data.devices[0].memory_total, 131072);
assert_eq!(data.devices[0].memory_used, 672);
assert_eq!(data.devices[0].memory_free, 130400);
assert_eq!(data.devices[0].power_draw, 226.0);
assert_eq!(data.devices[0].power_max, 850.0);
assert_eq!(data.devices[0].temperature, 36);
assert_eq!(data.devices[0].utilization, 0.0);
assert_eq!(data.devices[1].index, 1);
assert_eq!(data.devices[1].temperature, 39);
}
#[test]
fn test_map_device_name() {
assert_eq!(map_device_name("HL-100"), "Intel Gaudi");
assert_eq!(map_device_name("HL-200"), "Intel Gaudi 2 Mezzanine");
assert_eq!(map_device_name("HL-205"), "Intel Gaudi 2 PCIe");
assert_eq!(map_device_name("HL-225"), "Intel Gaudi 2 OAM");
assert_eq!(map_device_name("HL-325"), "Intel Gaudi 3 PCIe");
assert_eq!(map_device_name("HL-325L"), "Intel Gaudi 3 PCIe LP");
assert_eq!(map_device_name("HL-328"), "Intel Gaudi 3 OAM");
assert_eq!(map_device_name("HL-338"), "Intel Gaudi 3 UBB");
assert_eq!(map_device_name("HL-388"), "Intel Gaudi 3 HLS");
assert_eq!(map_device_name("HL-999"), "Intel HL-999");
}
#[test]
fn test_parse_memory_mib() {
assert_eq!(parse_memory_mib("131072 MiB").unwrap(), 131072);
assert_eq!(parse_memory_mib("672 MiB").unwrap(), 672);
assert_eq!(parse_memory_mib("130400 MiB").unwrap(), 130400);
}
#[test]
fn test_parse_power() {
assert_eq!(parse_power("226 W").unwrap(), 226.0);
assert_eq!(parse_power("850 W").unwrap(), 850.0);
assert_eq!(parse_power("0 W").unwrap(), 0.0);
}
#[test]
fn test_parse_temperature() {
assert_eq!(parse_temperature("36 C").unwrap(), 36);
assert_eq!(parse_temperature("39 C").unwrap(), 39);
assert_eq!(parse_temperature("0 C").unwrap(), 0);
}
#[test]
fn test_parse_utilization() {
assert_eq!(parse_utilization("0 %").unwrap(), 0.0);
assert_eq!(parse_utilization("50 %").unwrap(), 50.0);
assert_eq!(parse_utilization("100 %").unwrap(), 100.0);
}
#[test]
fn test_strip_driver_revision() {
assert_eq!(strip_driver_revision("1.22.1-97ec1a4"), "1.22.1");
assert_eq!(strip_driver_revision("1.20.0-abcdef1"), "1.20.0");
assert_eq!(strip_driver_revision("1.22.1"), "1.22.1");
assert_eq!(strip_driver_revision("1.22.1-abc"), "1.22.1-abc");
assert_eq!(strip_driver_revision("1.22.1-release"), "1.22.1-release");
assert_eq!(strip_driver_revision(" 1.22.1-97ec1a4 "), "1.22.1");
}
}