]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
nvme: export I/O requeue count when no path is usable via sysfs
authorNilay Shroff <nilay@linux.ibm.com>
Sat, 16 May 2026 18:36:52 +0000 (00:06 +0530)
committerKeith Busch <kbusch@kernel.org>
Thu, 4 Jun 2026 08:57:28 +0000 (01:57 -0700)
When the NVMe namespace head determines that there is no currently
available path to handle I/O (for example, while a controller is
resetting/connecting or due to a transient link failure), incoming
I/Os are added to the requeue list.

Currently, there is no visibility into how many I/Os have been requeued
in this situation. Add a new ns-head sysfs counter
io_requeue_no_usable_path_count, under diag attribute group to expose
the number of I/Os that were requeued due to the absence of an available
path. This counter is also writable thus allowing user to reset it, if
needed.

This statistic can help users understand I/O slowdowns or stalls caused
by temporary path unavailability, and can be consumed by monitoring
tools such as nvme-top for real-time observability.

Tested-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>
drivers/nvme/host/multipath.c
drivers/nvme/host/nvme.h
drivers/nvme/host/sysfs.c

index 51c8d928fc80de5957b9919e54403d659ec40b8f..9021fd44f193e8407c4198412a1cdfd68597947a 100644 (file)
@@ -538,6 +538,7 @@ static void nvme_ns_head_submit_bio(struct bio *bio)
                spin_lock_irq(&head->requeue_lock);
                bio_list_add(&head->requeue_list, bio);
                spin_unlock_irq(&head->requeue_lock);
+               atomic_long_inc(&head->io_requeue_no_usable_path_count);
        } else {
                dev_warn_ratelimited(dev, "no available path - failing I/O\n");
 
@@ -1192,6 +1193,35 @@ static ssize_t multipath_failover_count_store(struct device *dev,
 
 DEVICE_ATTR_RW(multipath_failover_count);
 
+static ssize_t io_requeue_no_usable_path_count_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct gendisk *disk = dev_to_disk(dev);
+       struct nvme_ns_head *head = disk->private_data;
+
+       return sysfs_emit(buf, "%lu\n",
+                   atomic_long_read(&head->io_requeue_no_usable_path_count));
+}
+
+static ssize_t io_requeue_no_usable_path_count_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       int err;
+       unsigned long requeue_cnt;
+       struct gendisk *disk = dev_to_disk(dev);
+       struct nvme_ns_head *head = disk->private_data;
+
+       err = kstrtoul(buf, 0, &requeue_cnt);
+       if (err)
+               return -EINVAL;
+
+       atomic_long_set(&head->io_requeue_no_usable_path_count, requeue_cnt);
+
+       return count;
+}
+
+DEVICE_ATTR_RW(io_requeue_no_usable_path_count);
+
 static int nvme_lookup_ana_group_desc(struct nvme_ctrl *ctrl,
                struct nvme_ana_group_desc *desc, void *data)
 {
index f2734f03682f1ec801149e08255c5149f12a074d..bfd427184d69e5754c6a2e36b91dad241e19316d 100644 (file)
@@ -566,6 +566,7 @@ struct nvme_ns_head {
        unsigned long           flags;
        struct delayed_work     remove_work;
        unsigned int            delayed_removal_secs;
+       atomic_long_t           io_requeue_no_usable_path_count;
 #define NVME_NSHEAD_DISK_LIVE          0
 #define NVME_NSHEAD_QUEUE_IF_NO_PATH   1
        struct nvme_ns __rcu    *current_path[];
@@ -1069,6 +1070,7 @@ extern struct device_attribute dev_attr_queue_depth;
 extern struct device_attribute dev_attr_numa_nodes;
 extern struct device_attribute dev_attr_delayed_removal_secs;
 extern struct device_attribute dev_attr_multipath_failover_count;
+extern struct device_attribute dev_attr_io_requeue_no_usable_path_count;
 extern struct device_attribute subsys_attr_iopolicy;
 
 static inline bool nvme_disk_is_ns_head(struct gendisk *disk)
index a03a22c832d8bb59304e02b7124c3f293e927047..7f0575b7cdd0ef4187c2fff339b3a4b833862c9d 100644 (file)
@@ -403,6 +403,7 @@ static struct attribute *nvme_ns_diag_attrs[] = {
        &dev_attr_io_errors.attr,
 #ifdef CONFIG_NVME_MULTIPATH
        &dev_attr_multipath_failover_count.attr,
+       &dev_attr_io_requeue_no_usable_path_count.attr,
 #endif
        NULL,
 };
@@ -427,6 +428,10 @@ static umode_t nvme_ns_diag_attrs_are_visible(struct kobject *kobj,
                if (nvme_disk_is_ns_head(dev_to_disk(dev)))
                        return 0;
        }
+       if (a == &dev_attr_io_requeue_no_usable_path_count.attr) {
+               if (!nvme_disk_is_ns_head(dev_to_disk(dev)))
+                       return 0;
+       }
 #endif
        return a->mode;
 }