feat(agent): collecte SMART via smartctl -j
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user