1 use std
::collections
::{HashMap, HashSet}
;
3 use lazy_static
::lazy_static
;
4 use anyhow
::{bail, Error}
;
5 use ::serde
::{Deserialize, Serialize}
;
10 #[derive(Debug, Serialize, Deserialize)]
11 #[serde(rename_all="lowercase")]
13 pub enum SmartStatus
{
14 /// Smart tests passed - everything is OK
16 /// Smart tests failed - disk has problems
23 #[derive(Debug, Serialize, Deserialize)]
25 pub struct SmartAttribute
{
28 /// Attribute raw value
30 // the rest of the values is available for ATA type
32 #[serde(skip_serializing_if="Option::is_none")]
35 #[serde(skip_serializing_if="Option::is_none")]
36 flags
: Option
<String
>,
37 /// ATA normalized value (0..100)
38 #[serde(skip_serializing_if="Option::is_none")]
39 normalized
: Option
<f64>,
41 #[serde(skip_serializing_if="Option::is_none")]
44 #[serde(skip_serializing_if="Option::is_none")]
45 threshold
: Option
<f64>,
55 description
: "Wearout level.",
60 description
: "SMART attributes.",
68 #[derive(Debug, Serialize, Deserialize)]
69 /// Data from smartctl
70 pub struct SmartData
{
71 pub status
: SmartStatus
,
72 pub wearout
: Option
<f64>,
73 pub attributes
: Vec
<SmartAttribute
>,
76 /// Read smartctl data for a disk (/dev/XXX).
77 pub fn get_smart_data(
80 ) -> Result
<SmartData
, Error
> {
82 const SMARTCTL_BIN_PATH
: &str = "smartctl";
84 let mut command
= std
::process
::Command
::new(SMARTCTL_BIN_PATH
);
86 if !health_only { command.args(&["-A", "-j"]); }
88 let disk_path
= match disk
.device_path() {
90 None
=> bail
!("disk {:?} has no node in /dev", disk
.syspath()),
92 command
.arg(disk_path
);
94 let output
= pbs_tools
::run_command(command
, Some(|exitcode
|
95 (exitcode
& 0b0111) == 0 // only bits 0-2 are fatal errors
98 let output
: serde_json
::Value
= output
.parse()?
;
100 let mut wearout
= None
;
102 let mut attributes
= Vec
::new();
103 let mut wearout_candidates
= HashMap
::new();
106 if let Some(list
) = output
["ata_smart_attributes"]["table"].as_array() {
108 let id
= match item
["id"].as_u64() {
110 None
=> continue, // skip attributes without id
113 let name
= match item
["name"].as_str() {
114 Some(name
) => name
.to_string(),
115 None
=> continue, // skip attributes without name
118 let raw_value
= match item
["raw"]["string"].as_str() {
119 Some(value
) => value
.to_string(),
120 None
=> continue, // skip attributes without raw value
123 let flags
= match item
["flags"]["string"].as_str() {
124 Some(flags
) => flags
.to_string(),
125 None
=> continue, // skip attributes without flags
128 let normalized
= match item
["value"].as_f64() {
130 None
=> continue, // skip attributes without normalize value
133 let worst
= match item
["worst"].as_f64() {
135 None
=> continue, // skip attributes without worst entry
138 let threshold
= match item
["thresh"].as_f64() {
140 None
=> continue, // skip attributes without threshold entry
143 if WEAROUT_FIELD_NAMES
.contains(&name
as &str) {
144 wearout_candidates
.insert(name
.clone(), normalized
);
147 attributes
.push(SmartAttribute
{
152 normalized
: Some(normalized
),
154 threshold
: Some(threshold
),
159 if !wearout_candidates
.is_empty() {
160 for field
in WEAROUT_FIELD_ORDER
{
161 if let Some(value
) = wearout_candidates
.get(field
as &str) {
162 wearout
= Some(*value
);
169 if let Some(list
) = output
["nvme_smart_health_information_log"].as_object() {
170 for (name
, value
) in list
{
171 if name
== "percentage_used" {
172 // extract wearout from nvme text, allow for decimal values
173 if let Some(v
) = value
.as_f64() {
175 wearout
= Some(100.0 - v
);
179 if let Some(value
) = value
.as_f64() {
180 attributes
.push(SmartAttribute
{
181 name
: name
.to_string(),
182 value
: value
.to_string(),
193 let status
= match output
["smart_status"]["passed"].as_bool() {
194 None
=> SmartStatus
::Unknown
,
195 Some(true) => SmartStatus
::Passed
,
196 Some(false) => SmartStatus
::Failed
,
200 Ok(SmartData { status, wearout, attributes }
)
203 static WEAROUT_FIELD_ORDER
: &[&'
static str] = &[
204 "Media_Wearout_Indicator",
206 "Wear_Leveling_Count",
207 "Perc_Write/Erase_Ct_BC",
208 "Perc_Rated_Life_Remain",
209 "Remaining_Lifetime_Perc",
210 "Percent_Lifetime_Remain",
212 "PCT_Life_Remaining",
213 "Lifetime_Remaining",
214 "Percent_Life_Remaining",
215 "Percent_Lifetime_Used",
216 "Perc_Rated_Life_Used"
220 static ref WEAROUT_FIELD_NAMES
: HashSet
<&'
static str> = {
221 WEAROUT_FIELD_ORDER
.iter().cloned().collect()