feat(agent): collecte SMART via smartctl -j

This commit is contained in:
Gilles Soulier
2026-05-22 11:36:51 +02:00
parent b78788b931
commit b53456dad8
2 changed files with 134 additions and 4 deletions
+89 -4
View File
@@ -1,13 +1,98 @@
use serde::Deserialize;
use crate::payload::SmartMetrics;
pub fn is_available() -> bool {
false
#[derive(Deserialize)]
struct SmartJson {
smart_status: SmartStatus,
temperature: Option<SmartTemp>,
ata_smart_attributes: Option<SmartAttrs>,
nvme_smart_health_information_log: Option<NvmeHealth>,
}
pub fn parse_json(_: &str) -> Result<SmartMetrics, serde_json::Error> {
serde_json::from_str("null")
#[derive(Deserialize)]
struct SmartStatus { passed: bool }
#[derive(Deserialize)]
struct SmartTemp { current: i64 }
#[derive(Deserialize)]
struct SmartAttrs { table: Vec<SmartAttr> }
#[derive(Deserialize)]
struct SmartAttr {
id: u8,
raw: SmartAttrRaw,
value: Option<i64>,
}
#[derive(Deserialize)]
struct SmartAttrRaw { value: i64 }
#[derive(Deserialize)]
struct NvmeHealth {
percentage_used: Option<i64>,
temperature: Option<i64>,
}
pub fn is_available() -> bool {
std::process::Command::new("which")
.arg("smartctl")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn parse_json(json: &str) -> Result<SmartMetrics, serde_json::Error> {
let s: SmartJson = serde_json::from_str(json)?;
let temperature = s.temperature.as_ref().map(|t| t.current)
.or_else(|| s.nvme_smart_health_information_log.as_ref()?.temperature);
let mut reallocated = None;
let mut power_hours = None;
let mut wear = None;
if let Some(attrs) = &s.ata_smart_attributes {
for attr in &attrs.table {
match attr.id {
5 => reallocated = Some(attr.raw.value),
9 => power_hours = Some(attr.raw.value),
177 => wear = attr.value,
_ => {}
}
}
}
if wear.is_none() {
if let Some(nvme) = &s.nvme_smart_health_information_log {
if let Some(pct) = nvme.percentage_used {
wear = Some(100 - pct);
}
}
}
Ok(SmartMetrics {
passed: s.smart_status.passed,
temperature,
reallocated_sectors: reallocated,
power_on_hours: power_hours,
wear_level: wear,
})
}
pub fn collect() -> Option<SmartMetrics> {
if !is_available() {
return None;
}
for dev in &["/dev/sda", "/dev/nvme0"] {
let output = std::process::Command::new("smartctl")
.args(["-j", dev])
.output()
.ok()?;
let json = String::from_utf8_lossy(&output.stdout);
if let Ok(metrics) = parse_json(&json) {
return Some(metrics);
}
}
None
}