From: Zygo Blaxell Date: Fri, 31 Jul 2026 08:41:19 +0000 (-0400) Subject: btrfs: raid56: expose parking statistics in sysfs X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27dbfa603e6cb6d780bddfc0604cce39df2950ea;p=linux btrfs: raid56: expose parking statistics in sysfs /sys/fs/btrfs//stripe_park_stats reports lifetime counters for the parking machinery: rbios parked, bytes merged into parked rbios, and how each park ended (filled to a full stripe, kicked by a waiter or a settle/retire flush, or expired at its deadline), plus how many flushed writes were padded to full stripes versus still needing the RMW read phase. The filled/kicked/expired split shows directly whether the deadlines are sized right for a given system -- expired parks that later reappear as rmw_reads are the batching the window failed to capture -- so any future retuning (or an adaptive deadline) can argue from measurements instead of taste. Assisted-by: Claude:claude-fable-5 --- diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 9fd43f6dc9a81..59c369d0fb9e8 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -758,6 +758,20 @@ struct btrfs_fs_info { u32 data_chunk_allocations; u32 metadata_ratio; + + /* + * raid56 parking observability (see rbio_try_park()): lifetime + * counters exposed through sysfs stripe_park_stats. + */ + struct { + atomic64_t parked; + atomic64_t merged_bytes; + atomic64_t filled; + atomic64_t expired; + atomic64_t kicked; + atomic64_t rmw_reads; + atomic64_t padded; + } stripe_park_stats; /* BTRFS_STRIPE_RMW_* masks; see btrfs_stripe_allow_rmw(). */ u32 stripe_rmw_opt; u32 stripe_rmw_prop; diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index 9f01a4d5eccd4..ebefa58e92655 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -55,6 +55,9 @@ */ #define RBIO_PADDED_BIT 5 +/* Set at park time and never cleared: this rbio once parked (stats). */ +#define RBIO_WAS_PARKED_BIT 7 + #define RBIO_CACHE_SIZE 1024 /* @@ -465,6 +468,9 @@ static void steal_rbio(struct btrfs_raid_bio *src, struct btrfs_raid_bio *dest) static void merge_rbio(struct btrfs_raid_bio *dest, struct btrfs_raid_bio *victim) { + if (test_bit(RBIO_PARKED_BIT, &dest->flags)) + atomic64_add(victim->bio_list_bytes, + &dest->bioc->fs_info->stripe_park_stats.merged_bytes); bio_list_merge_init(&dest->bio_list, &victim->bio_list); dest->bio_list_bytes += victim->bio_list_bytes; /* Also inherit the bitmaps from @victim. */ @@ -795,9 +801,11 @@ static bool rbio_try_park(struct btrfs_raid_bio *rbio) spin_lock(&table->parked_lock); set_bit(RBIO_PARKED_BIT, &rbio->flags); + set_bit(RBIO_WAS_PARKED_BIT, &rbio->flags); rbio->park_deadline = jiffies + msecs_to_jiffies(timeout_ms); list_add_tail(&rbio->parked_node, &table->parked); spin_unlock(&table->parked_lock); + atomic64_inc(&fs_info->stripe_park_stats.parked); queue_delayed_work(system_percpu_wq, &table->parked_work, msecs_to_jiffies(BTRFS_RBIO_PARK_SCAN_MS)); @@ -835,6 +843,7 @@ static void unpark_full_rbio(struct btrfs_raid_bio *rbio) } rbio_unpark_locked(rbio); spin_unlock(&table->parked_lock); + atomic64_inc(&rbio->bioc->fs_info->stripe_park_stats.filled); start_async_work(rbio, rmw_rbio_work_locked); } @@ -868,6 +877,7 @@ void btrfs_flush_parked_rbios(struct btrfs_fs_info *fs_info, u64 start, list_for_each_entry_safe(rbio, tmp, &flush, parked_node) { list_del_init(&rbio->parked_node); + atomic64_inc(&fs_info->stripe_park_stats.kicked); start_async_work(rbio, rmw_rbio_work_locked); } } @@ -897,6 +907,7 @@ static void parked_rbios_timeout_work(struct work_struct *work) list_for_each_entry_safe(rbio, tmp, &flush, parked_node) { list_del_init(&rbio->parked_node); + atomic64_inc(&rbio->bioc->fs_info->stripe_park_stats.expired); start_async_work(rbio, rmw_rbio_work_locked); } if (rearm) @@ -2624,6 +2635,7 @@ static bool rmw_try_pad_full(struct btrfs_raid_bio *rbio) set_bit(RBIO_PADDED_BIT, &rbio->flags); /* The whole stripe goes out: every vertical position is written. */ bitmap_set(&rbio->dbitmap, 0, rbio->stripe_nsectors); + atomic64_inc(&fs_info->stripe_park_stats.padded); return true; } @@ -2692,6 +2704,10 @@ static void rmw_rbio(struct btrfs_raid_bio *rbio) */ if (!rbio_is_full(rbio) && !rmw_try_pad_full(rbio) && need_read_stripe_sectors(rbio)) { + /* A parked-then-flushed write that still had to read. */ + if (test_bit(RBIO_WAS_PARKED_BIT, &rbio->flags)) + atomic64_inc(&rbio->bioc->fs_info-> + stripe_park_stats.rmw_reads); /* * Now we're doing sub-stripe write, also need all data stripes * to do the full RMW. diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 53d4667230555..f2389424ea1e7 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -1219,6 +1219,29 @@ static ssize_t btrfs_commit_stats_store(struct kobject *kobj, } BTRFS_ATTR_RW(, commit_stats, btrfs_commit_stats_show, btrfs_commit_stats_store); +static ssize_t btrfs_stripe_park_stats_show(struct kobject *kobj, + struct kobj_attribute *a, char *buf) +{ + struct btrfs_fs_info *fs_info = to_fs_info(kobj); + + return sysfs_emit(buf, + "parked %lld\n" + "merged_bytes %lld\n" + "filled %lld\n" + "kicked %lld\n" + "expired %lld\n" + "padded %lld\n" + "rmw_reads %lld\n", + atomic64_read(&fs_info->stripe_park_stats.parked), + atomic64_read(&fs_info->stripe_park_stats.merged_bytes), + atomic64_read(&fs_info->stripe_park_stats.filled), + atomic64_read(&fs_info->stripe_park_stats.kicked), + atomic64_read(&fs_info->stripe_park_stats.expired), + atomic64_read(&fs_info->stripe_park_stats.padded), + atomic64_read(&fs_info->stripe_park_stats.rmw_reads)); +} +BTRFS_ATTR(, stripe_park_stats, btrfs_stripe_park_stats_show); + static ssize_t btrfs_clone_alignment_show(struct kobject *kobj, struct kobj_attribute *a, char *buf) { @@ -1630,6 +1653,7 @@ static const struct attribute *btrfs_attrs[] = { BTRFS_ATTR_PTR(, read_policy), BTRFS_ATTR_PTR(, bg_reclaim_threshold), BTRFS_ATTR_PTR(, commit_stats), + BTRFS_ATTR_PTR(, stripe_park_stats), BTRFS_ATTR_PTR(, temp_fsid), #ifdef CONFIG_BTRFS_EXPERIMENTAL BTRFS_ATTR_PTR(, offload_csum),