]>
git.proxmox.com Git - proxmox-backup.git/blob - src/tools/disks/smart.rs
1 use std
::collections
::{HashMap, HashSet}
;
3 use ::serde
::{Deserialize, Serialize}
;
4 use anyhow
::{bail, Error}
;
5 use lazy_static
::lazy_static
;
7 use proxmox_schema
::api
;
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 // FIXME: remove value with next major relase (PBS 3.0)
29 /// duplicate of raw - kept for API stability
31 /// Attribute raw value
33 // the rest of the values is available for ATA type
35 #[serde(skip_serializing_if = "Option::is_none")]
38 #[serde(skip_serializing_if = "Option::is_none")]
39 flags
: Option
<String
>,
40 /// ATA normalized value (0..100)
41 #[serde(skip_serializing_if = "Option::is_none")]
42 normalized
: Option
<f64>,
44 #[serde(skip_serializing_if = "Option::is_none")]
47 #[serde(skip_serializing_if = "Option::is_none")]
48 threshold
: Option
<f64>,
57 description
: "Wearout level.",
62 description
: "SMART attributes.",
70 #[derive(Debug, Serialize, Deserialize)]
71 /// Data from smartctl
72 pub struct SmartData
{
73 pub status
: SmartStatus
,
74 pub wearout
: Option
<f64>,
75 pub attributes
: Vec
<SmartAttribute
>,
78 /// Read smartctl data for a disk (/dev/XXX).
79 pub fn get_smart_data(disk
: &super::Disk
, health_only
: bool
) -> Result
<SmartData
, Error
> {
80 const SMARTCTL_BIN_PATH
: &str = "smartctl";
82 let mut command
= std
::process
::Command
::new(SMARTCTL_BIN_PATH
);
85 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
= proxmox_sys
::command
::run_command(
97 |exitcode
| (exitcode
& 0b0011) == 0, // only bits 0-1 are fatal errors
101 let output
: serde_json
::Value
= output
.parse()?
;
103 let mut wearout
= None
;
105 let mut attributes
= Vec
::new();
106 let mut wearout_candidates
= HashMap
::new();
109 if let Some(list
) = output
["ata_smart_attributes"]["table"].as_array() {
111 let id
= match item
["id"].as_u64() {
113 None
=> continue, // skip attributes without id
116 let name
= match item
["name"].as_str() {
117 Some(name
) => name
.to_string(),
118 None
=> continue, // skip attributes without name
121 let raw_value
= match item
["raw"]["string"].as_str() {
122 Some(value
) => value
.to_string(),
123 None
=> continue, // skip attributes without raw value
126 let flags
= match item
["flags"]["string"].as_str() {
127 Some(flags
) => flags
.to_string(),
128 None
=> continue, // skip attributes without flags
131 let normalized
= match item
["value"].as_f64() {
133 None
=> continue, // skip attributes without normalize value
136 let worst
= match item
["worst"].as_f64() {
138 None
=> continue, // skip attributes without worst entry
141 let threshold
= match item
["thresh"].as_f64() {
143 None
=> continue, // skip attributes without threshold entry
146 if WEAROUT_FIELD_NAMES
.contains(&name
as &str) {
147 wearout_candidates
.insert(name
.clone(), normalized
);
150 attributes
.push(SmartAttribute
{
152 value
: raw_value
.clone(),
156 normalized
: Some(normalized
),
158 threshold
: Some(threshold
),
163 if !wearout_candidates
.is_empty() {
164 for field
in WEAROUT_FIELD_ORDER
{
165 if let Some(value
) = wearout_candidates
.get(field
as &str) {
166 wearout
= Some(*value
);
173 if let Some(list
) = output
["nvme_smart_health_information_log"].as_object() {
174 for (name
, value
) in list
{
175 if name
== "percentage_used" {
176 // extract wearout from nvme text, allow for decimal values
177 if let Some(v
) = value
.as_f64() {
179 wearout
= Some(100.0 - v
);
183 if let Some(value
) = value
.as_f64() {
184 attributes
.push(SmartAttribute
{
185 name
: name
.to_string(),
186 value
: value
.to_string(),
187 raw
: value
.to_string(),
198 let status
= match output
["smart_status"]["passed"].as_bool() {
199 None
=> SmartStatus
::Unknown
,
200 Some(true) => SmartStatus
::Passed
,
201 Some(false) => SmartStatus
::Failed
,
211 static WEAROUT_FIELD_ORDER
: &[&str] = &[
212 "Media_Wearout_Indicator",
214 "Wear_Leveling_Count",
215 "Perc_Write/Erase_Ct_BC",
216 "Perc_Rated_Life_Remain",
217 "Remaining_Lifetime_Perc",
218 "Percent_Lifetime_Remain",
220 "PCT_Life_Remaining",
221 "Lifetime_Remaining",
222 "Percent_Life_Remaining",
223 "Percent_Lifetime_Used",
224 "Perc_Rated_Life_Used",
228 static ref WEAROUT_FIELD_NAMES
: HashSet
<&'
static str> =
229 WEAROUT_FIELD_ORDER
.iter().cloned().collect();