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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use nanometrics_agent::metrics::smart;
|
||||
|
||||
const SMART_JSON_OK: &str = r#"{
|
||||
"smart_status": {"passed": true},
|
||||
"temperature": {"current": 34},
|
||||
"ata_smart_attributes": {
|
||||
"table": [
|
||||
{"id": 5, "name": "Reallocated_Sector_Ct", "raw": {"value": 0}, "value": 200},
|
||||
{"id": 9, "name": "Power_On_Hours", "raw": {"value": 4213}, "value": 77},
|
||||
{"id": 177, "name": "Wear_Leveling_Count", "raw": {"value": 0}, "value": 98}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn test_parse_smart_json_ok() {
|
||||
let result = smart::parse_json(SMART_JSON_OK).unwrap();
|
||||
assert!(result.passed);
|
||||
assert_eq!(result.temperature, Some(34));
|
||||
assert_eq!(result.reallocated_sectors, Some(0));
|
||||
assert_eq!(result.power_on_hours, Some(4213));
|
||||
assert_eq!(result.wear_level, Some(98));
|
||||
}
|
||||
|
||||
const SMART_JSON_FAIL: &str = r#"{
|
||||
"smart_status": {"passed": false},
|
||||
"temperature": {"current": 52},
|
||||
"ata_smart_attributes": {
|
||||
"table": [
|
||||
{"id": 5, "name": "Reallocated_Sector_Ct", "raw": {"value": 47}, "value": 150}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn test_parse_smart_json_fail() {
|
||||
let result = smart::parse_json(SMART_JSON_FAIL).unwrap();
|
||||
assert!(!result.passed);
|
||||
assert_eq!(result.reallocated_sectors, Some(47));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_disponible() {
|
||||
let _ = smart::is_available();
|
||||
}
|
||||
Reference in New Issue
Block a user