Add disk usage detection based on access counters

This commit is contained in:
Andrea Mazzoleni 2025-12-25 00:49:58 +01:00
parent 75b6170b79
commit 986d7464b4
7 changed files with 222 additions and 1 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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. */

View file

@ -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;

View file

@ -1592,6 +1592,18 @@ read.
.B attr:<device_file>:<disk_name>[/<split_index>]:power:down
Logs a successful spin\-down operation initiated via the
smartctl command.
.TP
.B attr:<device_file>:<disk_name>[/<split_index>]:stat:<access_number>
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 <access_number>
Total number of accesses (int64).
.PD
.RE
.SS Summary
Tags logging the overall failure probabilities for the array.
.TP

View file

@ -1016,6 +1016,13 @@ Command Smart Tags
Logs a successful spin-down operation initiated via the
smartctl command.
=attr:<device_file>:<disk_name>[/<split_index>]:stat:<access_number>
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.
<access_number> - Total number of accesses (int64).
Summary
Tags logging the overall failure probabilities for the array.

View file

@ -1058,6 +1058,13 @@ Tags logging specific disk identification and attribute values.
Logs a successful spin-down operation initiated via the
smartctl command.
attr:<device_file>:<disk_name>[/<split_index>]:stat:<access_number>
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.
<access_number> - Total number of accesses (int64).
---- 14.0.2 Summary ----
Tags logging the overall failure probabilities for the array.