From ede99cbe80ea26143cc7683b663cf1a32d3d6ba1 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 19 Jan 2026 22:57:53 +0100 Subject: [PATCH] Added wear level percentage to the SMART report --- HISTORY | 1 + cmdline/device.c | 16 +++++++++++++--- cmdline/portable.h | 11 ++++++++++- cmdline/support.c | 34 ++++++++++++++++++++++++++-------- doc/snapraid_log.1 | 17 +++++++++++++++++ doc/snapraid_log.d | 12 ++++++++++++ doc/snapraid_log.txt | 12 ++++++++++++ 7 files changed, 91 insertions(+), 12 deletions(-) diff --git a/HISTORY b/HISTORY index 9068b322..b1c6f9d6 100644 --- a/HISTORY +++ b/HISTORY @@ -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. diff --git a/cmdline/device.c b/cmdline/device.c index 88adb418..3df7f6fe 100644 --- a/cmdline/device.c +++ b/cmdline/device.c @@ -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) { diff --git a/cmdline/portable.h b/cmdline/portable.h index b34ca639..c89d9e10 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -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. diff --git a/cmdline/support.c b/cmdline/support.c index 433d30ac..41a0ef83 100644 --- a/cmdline/support.c +++ b/cmdline/support.c @@ -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; + } + } } } diff --git a/doc/snapraid_log.1 b/doc/snapraid_log.1 index e14b5657..82b7e4bf 100644 --- a/doc/snapraid_log.1 +++ b/doc/snapraid_log.1 @@ -1640,6 +1640,23 @@ The total error count (uint). .PD .RE .TP +.B attr::[/]:wear_level: +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 +Wear level percentage (uint). +.PD +.RE +.TP .B attr::[/]:rotationrate: Logs the disk\'s rotation rate. .RS 4 diff --git a/doc/snapraid_log.d b/doc/snapraid_log.d index 9375c0e9..5cce900c 100644 --- a/doc/snapraid_log.d +++ b/doc/snapraid_log.d @@ -1037,6 +1037,18 @@ Command Smart And Probe Tags - The total error count (uint). + =attr::[/]:wear_level: + 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. + + - Wear level percentage (uint). + =attr::[/]:rotationrate: Logs the disk's rotation rate. diff --git a/doc/snapraid_log.txt b/doc/snapraid_log.txt index 523b5a02..f63f34d1 100644 --- a/doc/snapraid_log.txt +++ b/doc/snapraid_log.txt @@ -1083,6 +1083,18 @@ Tags logging specific disk identification and attribute values. - The total error count (uint). + attr::[/]:wear_level: + 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. + + - Wear level percentage (uint). + attr::[/]:rotationrate: Logs the disk's rotation rate.