Added wear level percentage to the SMART report

This commit is contained in:
Andrea Mazzoleni 2026-01-19 22:57:53 +01:00
parent e99f43c8ca
commit ede99cbe80
7 changed files with 91 additions and 12 deletions

View file

@ -12,6 +12,7 @@ SnapRAID HISTORY
* Added detection of the smartctl executable. Improves reliability when running
under sudo or in environments where /sbin and /usr/sbin are not in PATH.
* Avoided saving the content file when it was not strictly necessary.
* Added wear level percentage to the SMART report.
* More log tags for the status and probe commands [Tristan Teufel].
* Added documentation of the tags used in the log file in the new
snapraid_log manpage.

View file

@ -569,6 +569,8 @@ static void state_smart_log(devinfo_t* devinfo, double afr)
log_tag("attr:%s:%s:error_protocol:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->smart[SMART_ERROR_PROTOCOL]);
if (devinfo->smart[SMART_ERROR_MEDIUM] != SMART_UNASSIGNED)
log_tag("attr:%s:%s:error_medium:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->smart[SMART_ERROR_MEDIUM]);
if (devinfo->smart[SMART_WEAR_LEVEL] != SMART_UNASSIGNED)
log_tag("attr:%s:%s:wear_level:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->smart[SMART_WEAR_LEVEL]);
if (devinfo->smart[SMART_FLAGS] != SMART_UNASSIGNED)
log_tag("attr:%s:%s:flags:%" PRIu64 ":%" PRIx64 "\n", devinfo->file, devinfo->name, devinfo->smart[SMART_FLAGS], devinfo->smart[SMART_FLAGS]);
@ -619,12 +621,14 @@ static void state_smart(struct snapraid_state* state, unsigned n, tommy_list* lo
printf(" Power");
printf(" Error");
printf(" FP");
printf(" Wear");
printf(" Size");
printf("\n");
printf(" C");
printf(" OnDays");
printf(" Count");
printf(" ");
printf("Level");
printf(" TB");
printf(" "); printl("Serial", serial_pad);
printf(" "); printl("Device", device_pad);
@ -712,6 +716,12 @@ static void state_smart(struct snapraid_state* state, unsigned n, tommy_list* lo
}
}
if (devinfo->smart[SMART_WEAR_LEVEL] != SMART_UNASSIGNED) {
printf(" %3" PRIu64 "%%", devinfo->smart[SMART_WEAR_LEVEL]);
} else {
printf(" -");
}
if (devinfo->info[INFO_SIZE] != SMART_UNASSIGNED)
printf(" %4.1f", devinfo->info[INFO_SIZE] / 1E12);
else
@ -783,9 +793,9 @@ static void state_smart(struct snapraid_state* state, unsigned n, tommy_list* lo
/* |<##################################################################72>|####80>| */
printf("These values are the probabilities that in the next year you'll have a\n");
printf("sequence of failures that the parity WON'T be able to recover, assuming\n");
printf("that you regularly scrub, and in case repair, the array in the specified\n");
printf("time.\n");
printf("sequence of failures from which the parity won't be able to recover,\n");
printf("assuming that you regularly scrub and repair the array (as needed)\n");
printf("within the specified time.\n");
bail:
if (make_it_fail) {

View file

@ -451,10 +451,19 @@ int randomize(void* ptr, size_t size);
*/
#define SMART_ERROR_MEDIUM 258
/**
* Unified Wear Level Metric (0-100)
*
* Represents the remaining life of an SSD.
* - 0: Brand new drive (0% wear).
* - 100: Drive has reached or exceeded its manufacturer-rated design life.
*/
#define SMART_WEAR_LEVEL 259
/**
* SMART attributes count.
*/
#define SMART_COUNT 259
#define SMART_COUNT 260
/**
* Info attributes.

View file

@ -1730,6 +1730,7 @@ int smartctl_attribute(FILE* f, const char* file, const char* name, uint64_t* sm
char buf[256];
unsigned id;
uint64_t raw;
uint64_t normalized;
char* s;
s = fgets(buf, sizeof(buf), f);
@ -1778,24 +1779,25 @@ int smartctl_attribute(FILE* f, const char* file, const char* name, uint64_t* sm
} else if (sscanf(s, "Serial Number: %63s", serial) == 1) {
} else if (smatch(s, "ID#") == 0) {
inside = 1;
} else if (smatch(s, "No Errors Logged") == 0) {
} else if (smatch(s, "No Errors Logged") == 0) { /* ATA */
smart[SMART_ERROR_PROTOCOL] = 0;
} else if (sscanf(s, "ATA Error Count: %" SCNu64, &raw) == 1) {
} else if (sscanf(s, "ATA Error Count: %" SCNu64, &raw) == 1) { /* ATA */
smart[SMART_ERROR_PROTOCOL] = raw;
} else if (sscanf(s, "Media and Data Integrity Errors: %" SCNu64, &raw) == 1) {
} else if (sscanf(s, "Media and Data Integrity Errors: %" SCNu64, &raw) == 1) { /* NVME */
smart[SMART_ERROR_MEDIUM] = raw;
} else if (sscanf(s, "Error Information Log Entries: %" SCNu64, &raw) == 1) {
} else if (sscanf(s, "Error Information Log Entries: %" SCNu64, &raw) == 1) { /* NVME */
smart[SMART_ERROR_PROTOCOL] = raw;
} else if (sscanf(s, "Medium error count: %" SCNu64, &raw) == 1) {
smart[SMART_ERROR_MEDIUM] = raw;
} else if (sscanf(s, "Non-medium error count: %" SCNu64, &raw) == 1) {
} else if (sscanf(s, "Non-medium error count: %" SCNu64, &raw) == 1) { /* SCSI */
smart[SMART_ERROR_PROTOCOL] = raw;
} else if (sscanf(s, "Percentage Used: %" SCNu64 "%%", &raw) == 1) { /* NVME */
if (raw <= 100)
smart[SMART_WEAR_LEVEL] = raw;
} else if (inside) {
if (sscanf(s, "%u %*s %*s %*s %*s %*s %*s %*s %*s %" SCNu64, &id, &raw) != 2) {
/* LCOV_EXCL_START */
if (sscanf(s, "%u %*s %*s %" SCNu64 " %*s %*s %*s %*s %*s %" SCNu64, &id, &normalized, &raw) != 3) {
log_fatal("Invalid smartctl line '%s'.\n", s);
return -1;
/* LCOV_EXCL_STOP */
}
if (id >= 256) {
@ -1806,6 +1808,22 @@ int smartctl_attribute(FILE* f, const char* file, const char* name, uint64_t* sm
}
smart[id] = raw;
/*
* Map normalized health percentage to our unified wear level for SSDs
* 177: Wear_Leveling_Count (Samsung/Crucial)
* 231: SSD_Life_Left (Kingston/WD)
* 233: Media_Wearout_Indicator (Intel)
*/
if (normalized <= 100) {
switch (id) {
case 177 :
case 233 :
case 231 :
smart[SMART_WEAR_LEVEL] = 100 - normalized;
break;
}
}
}
}

View file

@ -1640,6 +1640,23 @@ The total error count (uint).
.PD
.RE
.TP
.B attr:<device_file>:<disk_name>[/<split_index>]:wear_level:<percentage>
The wear level of the SSD/NVME expressed as a percentage, where a
value of 0 represents a brand new drive with zero wear and
a value of 100 indicates the drive has reached or exceeded its
manufacturer\-rated design life. This metric is provided as a
normalized unsigned integer, offering a consistent health
indicator across various SSD/NVME vendors and interface types
by mapping diverse hardware counters into a single
standardized scale.
.RS 4
.PD 0
.HP 4
.I <percentage>
Wear level percentage (uint).
.PD
.RE
.TP
.B attr:<device_file>:<disk_name>[/<split_index>]:rotationrate:<rate>
Logs the disk\'s rotation rate.
.RS 4

View file

@ -1037,6 +1037,18 @@ Command Smart And Probe Tags
<error_count> - The total error count (uint).
=attr:<device_file>:<disk_name>[/<split_index>]:wear_level:<percentage>
The wear level of the SSD/NVME expressed as a percentage, where a
value of 0 represents a brand new drive with zero wear and
a value of 100 indicates the drive has reached or exceeded its
manufacturer-rated design life. This metric is provided as a
normalized unsigned integer, offering a consistent health
indicator across various SSD/NVME vendors and interface types
by mapping diverse hardware counters into a single
standardized scale.
<percentage> - Wear level percentage (uint).
=attr:<device_file>:<disk_name>[/<split_index>]:rotationrate:<rate>
Logs the disk's rotation rate.

View file

@ -1083,6 +1083,18 @@ Tags logging specific disk identification and attribute values.
<error_count> - The total error count (uint).
attr:<device_file>:<disk_name>[/<split_index>]:wear_level:<percentage>
The wear level of the SSD/NVME expressed as a percentage, where a
value of 0 represents a brand new drive with zero wear and
a value of 100 indicates the drive has reached or exceeded its
manufacturer-rated design life. This metric is provided as a
normalized unsigned integer, offering a consistent health
indicator across various SSD/NVME vendors and interface types
by mapping diverse hardware counters into a single
standardized scale.
<percentage> - Wear level percentage (uint).
attr:<device_file>:<disk_name>[/<split_index>]:rotationrate:<rate>
Logs the disk's rotation rate.