From 986d7464b416195c3729c2f8f5c424ed6fe444d5 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Thu, 25 Dec 2025 00:49:58 +0100 Subject: [PATCH] Add disk usage detection based on access counters --- HISTORY | 3 +- cmdline/device.c | 2 + cmdline/portable.h | 1 + cmdline/unix.c | 191 +++++++++++++++++++++++++++++++++++++++++++ doc/snapraid_log.1 | 12 +++ doc/snapraid_log.d | 7 ++ doc/snapraid_log.txt | 7 ++ 7 files changed, 222 insertions(+), 1 deletion(-) diff --git a/HISTORY b/HISTORY index e5a7c8a4..76936f27 100644 --- a/HISTORY +++ b/HISTORY @@ -6,7 +6,8 @@ SnapRAID HISTORY * Added detection of `smartctl` executable. Improves reliability when running under `sudo` or environments where `/sbin` and `/usr/sbin` are not in PATH. * Avoided saving the content file when it was not strictly necessary. - * More log tags for the `status` command [Tristan Teufel] + * More log tags for the `status` command [Tristan Teufel]. + * More log tags for the `probe` command. * Added documentation of the tags used in the log file in the new snapraid_log manpage. * Added manual translations in multiple languages. diff --git a/cmdline/device.c b/cmdline/device.c index a19cd041..71fe3260 100644 --- a/cmdline/device.c +++ b/cmdline/device.c @@ -558,6 +558,8 @@ static void state_info_log(devinfo_t* devinfo) log_tag("attr:%s:%s:size:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->info[INFO_SIZE]); if (devinfo->info[INFO_ROTATION_RATE] != SMART_UNASSIGNED) log_tag("attr:%s:%s:rotationrate:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->info[INFO_ROTATION_RATE]); + if (devinfo->access_stat != 0) + log_tag("attr:%s:%s:stat:%" PRIu64 "\n", devinfo->file, devinfo->name, devinfo->access_stat); } static void state_smart_log(devinfo_t* devinfo, double afr) diff --git a/cmdline/portable.h b/cmdline/portable.h index ed61c50d..bc941a0b 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -520,6 +520,7 @@ struct devinfo_struct { struct devinfo_struct* parent; /**< Pointer at the parent if any. */ uint64_t smart[SMART_COUNT]; /**< SMART attributes. */ uint64_t info[INFO_COUNT]; /**< Info attributes. */ + uint64_t access_stat; /**< Access stat. */ char serial[SMART_MAX]; /**< Serial number. */ char family[SMART_MAX]; /**< Family. */ char model[SMART_MAX]; /**< Model. */ diff --git a/cmdline/unix.c b/cmdline/unix.c index ab5ef254..59934598 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -1274,6 +1274,192 @@ static int devsmart(dev_t device, const char* name, const char* smartctl, uint64 } #endif +/** + * Compute disk usage by aggregating access statistics. + * + * This function determines whether a disk has been used by summing read + * and write access counters from all related partitions and device holders. + * Disk-level statistics are not used directly, as they may include + * kernel-generated fake accesses that do not reflect real I/O activity. + * + * By walking the device hierarchy and accumulating per-device statistics, + * the function provides a reliable measure of actual disk usage between + * sampling points. + */ +#if HAVE_LINUX_DEVICE +static int devstat_recurse(dev_t device, uint64_t* count) +{ + char device_path[PATH_MAX]; + char path[PATH_MAX]; + char buf[512]; + char* device_file; + int device_len; + DIR* d; + struct dirent* dd; + int holders = 0; + int partitions = 0; + int token; + int ret; + char* i; + + /* get the device name */ + if (devresolve(device, device_path, sizeof(device_path)) != 0) + return -1; + + device_len = strlen(device_path); + if (device_len <= 5) + return -1; + device_file = device_path + 5; /* skip /dev/ */ + device_len -= 5; + + pathprint(path, sizeof(path), "/sys/dev/block/%u:%u", major(device), minor(device)); + + /* check if there are partitions */ + d = opendir(path); + if (d == 0) + return -1; + + while ((dd = readdir(d)) != 0) { + dev_t subdev; + + if (dd->d_name[0] == '.') + continue; + + if (strncmp(device_file, dd->d_name, device_len) != 0) + continue; + + pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/%s/dev", major(device), minor(device), dd->d_name); + + subdev = devread(path); + if (!subdev) { + /* LCOV_EXCL_START */ + closedir(d); + return -1; + /* LCOV_EXCL_STOP */ + } + + if (devstat_recurse(subdev, count) != 0) { + /* LCOV_EXCL_START */ + closedir(d); + return -1; + /* LCOV_EXCL_STOP */ + } + + ++partitions; + } + + closedir(d); + + /* stop if processed partitions */ + if (partitions != 0) + return 0; + + pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/holders", major(device), minor(device)); + + /* check if there are holders */ + d = opendir(path); + if (d == 0) + return -1; + + while ((dd = readdir(d)) != 0) { + dev_t subdev; + + if (dd->d_name[0] == '.') + continue; + + + /* for each slave, expand the full potential tree */ + pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/holders/%s/dev", major(device), minor(device), dd->d_name); + + subdev = devread(path); + if (!subdev) { + /* LCOV_EXCL_START */ + closedir(d); + return -1; + /* LCOV_EXCL_STOP */ + } + + if (devstat_recurse(subdev, count) != 0) { + /* LCOV_EXCL_START */ + closedir(d); + return -1; + /* LCOV_EXCL_STOP */ + } + + ++holders; + } + + closedir(d); + + /* stop if processed holders */ + if (holders != 0) + return 0; + + pathprint(path, sizeof(path), "/sys/dev/block/%u:%u/stat", major(device), minor(device)); + + ret = sysread(path, buf, sizeof(buf)); + if (ret < 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to read '%s'.\n", path); + return -1; + /* LCOV_EXCL_STOP */ + } + if ((size_t)ret + 1 > sizeof(buf)) { + /* LCOV_EXCL_START */ + log_fatal("Too long read '%s'.\n", path); + return -1; + /* LCOV_EXCL_STOP */ + } + + /* ending 0 */ + buf[ret] = 0; + + i = buf; + token = 1; /* token number */ + while (*i) { + char* n; + char* e; + unsigned long long v; + + /* skip spaces */ + while (*i && isspace(*i)) + ++i; + + /* read digits */ + n = i; + while (*i && isdigit(*i)) + ++i; + + if (i == n) /* if no digit, abort */ + break; + if (*i == 0 || !isspace(*i)) /* if no space, abort */ + break; + *i++ = 0; /* put a terminator */ + + v = strtoull(n, &e, 10); + if (*e != 0) + break; + + /* sum reads and writes completed */ + if (token == 1 || token == 5) { + *count += v; + if (token == 5) + break; /* stop here */ + } + + ++token; + } + + return 0; +} + +static int devstat(dev_t device, uint64_t* count) +{ + *count = 0; + return devstat_recurse(device, count); +} +#endif + /** * Get device attributes. */ @@ -1731,6 +1917,11 @@ static void* thread_probe(void* arg) */ devattr(devinfo->device, devinfo->info, devinfo->serial, devinfo->family, devinfo->model); + /* + * Retrieve access stat for the device + */ + devstat(devinfo->device, &devinfo->access_stat); + return 0; #else (void)arg; diff --git a/doc/snapraid_log.1 b/doc/snapraid_log.1 index 3cb67d8d..10f9e013 100644 --- a/doc/snapraid_log.1 +++ b/doc/snapraid_log.1 @@ -1592,6 +1592,18 @@ read. .B attr::[/]:power:down Logs a successful spin\-down operation initiated via the smartctl command. +.TP +.B attr::[/]:stat: +Logs the total number of read and write accesses done on the disk. +Comparing this number at different times, you can detect if the +disk was used during that time. +.RS 4 +.PD 0 +.HP 4 +.I +Total number of accesses (int64). +.PD +.RE .SS Summary Tags logging the overall failure probabilities for the array. .TP diff --git a/doc/snapraid_log.d b/doc/snapraid_log.d index 497d7e31..0821cbaa 100644 --- a/doc/snapraid_log.d +++ b/doc/snapraid_log.d @@ -1016,6 +1016,13 @@ Command Smart Tags Logs a successful spin-down operation initiated via the smartctl command. + =attr::[/]:stat: + Logs the total number of read and write accesses done on the disk. + Comparing this number at different times, you can detect if the + disk was used during that time. + + - Total number of accesses (int64). + Summary Tags logging the overall failure probabilities for the array. diff --git a/doc/snapraid_log.txt b/doc/snapraid_log.txt index 3191e568..e5f320a1 100644 --- a/doc/snapraid_log.txt +++ b/doc/snapraid_log.txt @@ -1058,6 +1058,13 @@ Tags logging specific disk identification and attribute values. Logs a successful spin-down operation initiated via the smartctl command. + attr::[/]:stat: + Logs the total number of read and write accesses done on the disk. + Comparing this number at different times, you can detect if the + disk was used during that time. + + - Total number of accesses (int64). + ---- 14.0.2 Summary ---- Tags logging the overall failure probabilities for the array.