From 47df51ea402e3f8822e0f69628a500dcfca65f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 21 Aug 2026 10:07:30 +0200 Subject: [PATCH] shared/blockdev-list: add flag to gather read&write stats --- src/shared/blockdev-list.c | 30 ++++++++++++++++++++++++++++-- src/shared/blockdev-list.h | 13 +++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index 1a92b0dea7..32e121bb4b 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -285,6 +285,30 @@ int blockdev_list_one( log_device_debug_errno(dev, r, "Failed to acquire diskseq of device '%s', ignoring: %m", node); } + uint64_t read_bytes, write_bytes; + bool bytes_good = false; + if (FLAGS_SET(flags, BLOCKDEV_LIST_RW_STATS)) { + const char *v; + + r = sd_device_get_sysattr_value(dev, "stat", &v); + if (r < 0) + log_device_full_errno(dev, ERRNO_IS_NEG_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, + r, "%s: failed to read 'stat' attribute, ignoring: %m", node); + else { + /* Fields 3 and 7: the number of sectors read and written. The "stat" attribute + * always counts in 512 byte sectors, regardless of the device's logical block size. + * The skipped fields are consumed with %*s, since their values may exceed any fixed + * integer width. */ + if (sscanf(v, "%*s %*s %"SCNu64" %*s %*s %*s %"SCNu64, &read_bytes, &write_bytes) < 2) + log_device_warning(dev, "%s: failed to parse 'stat' attribute.", node); + else if (!MUL_SAFE(&read_bytes, read_bytes, UINT64_C(512)) || + !MUL_SAFE(&write_bytes, write_bytes, UINT64_C(512))) + log_device_debug(dev, "%s: sector counters out of range, ignoring.", node); + else + bytes_good = true; + } + } + _cleanup_free_ char *m = strdup(node); if (!m) return log_oom(); @@ -292,11 +316,13 @@ int blockdev_list_one( *ret = (BlockDevice) { .node = TAKE_PTR(m), .symlinks = TAKE_PTR(list), - .diskseq = diskseq, - .size = size, .model = TAKE_PTR(model), .vendor = TAKE_PTR(vendor), .subsystem = TAKE_PTR(subsystem), + .diskseq = diskseq, + .size = size, + .read_bytes = bytes_good ? read_bytes : UINT64_MAX, + .write_bytes = bytes_good ? write_bytes : UINT64_MAX, .read_only = ro, }; diff --git a/src/shared/blockdev-list.h b/src/shared/blockdev-list.h index 6a2f350a2e..e79ad74a76 100644 --- a/src/shared/blockdev-list.h +++ b/src/shared/blockdev-list.h @@ -15,6 +15,7 @@ typedef enum BlockDevListFlags { BLOCKDEV_LIST_REQUIRE_LUKS = 1 << 8, /* Only consider block devices with LUKS superblocks */ BLOCKDEV_LIST_METADATA = 1 << 9, /* Fill in model, vendor, subsystem, read_only */ BLOCKDEV_LIST_DISKSEQ = 1 << 10, /* Fill in the diskseq field */ + BLOCKDEV_LIST_RW_STATS = 1 << 11, /* Fill in read and write measurements */ } BlockDevListFlags; /* The "dynamic" filters – ones the kernel can flip at runtime via sysattrs on a live device. @@ -31,13 +32,17 @@ typedef struct BlockDevice { char *subsystem; uint64_t diskseq; uint64_t size; /* in bytes */ + uint64_t read_bytes; + uint64_t write_bytes; int read_only; } BlockDevice; -#define BLOCK_DEVICE_NULL (BlockDevice) { \ - .diskseq = UINT64_MAX, \ - .size = UINT64_MAX, \ - .read_only = -1, \ +#define BLOCK_DEVICE_NULL (BlockDevice) { \ + .diskseq = UINT64_MAX, \ + .size = UINT64_MAX, \ + .read_bytes = UINT64_MAX, \ + .write_bytes = UINT64_MAX, \ + .read_only = -1, \ } /* Return values of blockdev_list_one(). Not its own type – returned as int. */