[PATCH v8 09/10] nvme-multipath: add debugfs attribute latency_stat
Nilay Shroff <[email protected]>
| Newsgroups | org.infradead.lists.linux-nvme |
|---|---|
| Message-ID | <[email protected]> |
This commit introduces a new debugfs attribute, "latency_stat", under both per-path and head debugfs directories (defined under /sys/kernel/ debug/block/). This attribute provides visibility into the internal state of the latency I/O policy to aid in debugging and performance analysis. For per-path entries, "latency_stat" reports the corresponding path statistics such as I/O weight, selection count, processed samples, and ignored samples. For head entries, it reports per-CPU statistics for each reachable path, including I/O weight, path score, smoothed (EWMA) latency, selection count, processed samples, and ignored samples. These additions enhance observability of the I/O path selection behavior and help diagnose imbalance or instability in multipath performance. Reviewed-by: Guixin Liu <[email protected]> Reviewed-by: Hannes Reinecke <[email protected]> Signed-off-by: Nilay Shroff <[email protected]> --- drivers/nvme/host/debugfs.c | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/drivers/nvme/host/debugfs.c b/drivers/nvme/host/debugfs.c index b8850d37edce..b3496951f0b1 100644 --- a/drivers/nvme/host/debugfs.c +++ b/drivers/nvme/host/debugfs.c @@ -184,6 +184,125 @@ static ssize_t nvme_latency_batch_timeout_store(void *data, WRITE_ONCE(head->latency_batch_timeout, res * NSEC_PER_SEC); return count; } + +#define TO_CTX(m) ((struct nvme_debugfs_ctx *)m->private) +#define TO_NS_HEAD(m) ((struct nvme_ns_head *)(TO_CTX(m)->data)) + +static void *nvme_mpath_latency_stat_start(struct seq_file *m, loff_t *pos) + __acquires_shared(&TO_NS_HEAD(m)->srcu) +{ + struct nvme_ns *ns; + struct nvme_debugfs_ctx *ctx = m->private; + struct nvme_ns_head *head = ctx->data; + loff_t n = *pos; + + /* Remember srcu index, so we can unlock later. */ + ctx->srcu_idx = srcu_read_lock(&head->srcu); + ns = list_first_or_null_rcu(&head->list, struct nvme_ns, siblings); + + while (n && ns) { + ns = list_next_or_null_rcu(&head->list, &ns->siblings, + struct nvme_ns, siblings); + n--; + } + + return ns; +} + +static void *nvme_mpath_latency_stat_next(struct seq_file *m, void *v, + loff_t *pos) +{ + struct nvme_ns *ns = v; + struct nvme_debugfs_ctx *ctx = m->private; + struct nvme_ns_head *head = ctx->data; + + (*pos)++; + + return list_next_or_null_rcu(&head->list, &ns->siblings, + struct nvme_ns, siblings); +} + +static void nvme_mpath_latency_stat_stop(struct seq_file *m, void *v) + __releases_shared(&TO_NS_HEAD(m)->srcu) +{ + struct nvme_debugfs_ctx *ctx = m->private; + struct nvme_ns_head *head = ctx->data; + + srcu_read_unlock(&head->srcu, ctx->srcu_idx); +} + +static int nvme_mpath_latency_stat_show(struct seq_file *m, void *v) +{ + int i, cpu; + struct nvme_path_lat_stat *stat; + struct nvme_ns *ns = v; + + seq_printf(m, "%s:\n", ns->disk->disk_name); + for_each_online_cpu(cpu) { + seq_printf(m, "cpu %d : ", cpu); + for (i = 0; i < NVME_NUM_STAT_GROUPS; i++) { + stat = &per_cpu_ptr(ns->path_lat, cpu)[i].stat; + seq_printf(m, "%u %u %llu %llu %llu %llu %llu ", + stat->weight, stat->credit, stat->score, + stat->slat_ns, stat->sel, + stat->nr_samples, stat->nr_ignored); + } + seq_putc(m, '\n'); + } + return 0; +} + +static const struct seq_operations nvme_mpath_latency_stat_seq_ops = { + .start = nvme_mpath_latency_stat_start, + .next = nvme_mpath_latency_stat_next, + .stop = nvme_mpath_latency_stat_stop, + .show = nvme_mpath_latency_stat_show +}; + +static void nvme_latency_stat_read_all(struct nvme_ns *ns, + struct nvme_path_lat_stat *batch) +{ + int i, cpu; + u32 ncpu[NVME_NUM_STAT_GROUPS] = {0}; + struct nvme_path_lat_stat *stat; + + for_each_online_cpu(cpu) { + for (i = 0; i < NVME_NUM_STAT_GROUPS; i++) { + stat = &per_cpu_ptr(ns->path_lat, cpu)[i].stat; + batch[i].sel += stat->sel; + batch[i].nr_samples += stat->nr_samples; + batch[i].nr_ignored += stat->nr_ignored; + batch[i].weight += stat->weight; + if (stat->weight) + ncpu[i]++; + } + } + + for (i = 0; i < NVME_NUM_STAT_GROUPS; i++) { + if (!ncpu[i]) + continue; + batch[i].weight = DIV_U64_ROUND_CLOSEST(batch[i].weight, + ncpu[i]); + } +} + +static int nvme_ns_latency_stat_show(void *data, struct seq_file *m) +{ + int i; + struct nvme_path_lat_stat stat[NVME_NUM_STAT_GROUPS] = {0}; + struct nvme_ns *ns = (struct nvme_ns *)data; + + if (!ns->head->disk) + return 0; + + nvme_latency_stat_read_all(ns, stat); + for (i = 0; i < NVME_NUM_STAT_GROUPS; i++) { + seq_printf(m, "%u %llu %llu %llu ", + stat[i].weight, stat[i].sel, + stat[i].nr_samples, stat[i].nr_ignored); + } + return 0; +} #endif static const struct nvme_debugfs_attr nvme_mpath_debugfs_attrs[] = { @@ -192,11 +311,15 @@ static const struct nvme_debugfs_attr nvme_mpath_debugfs_attrs[] = { nvme_latency_ewma_shift_store}, {"latency_batch_timeout", 0600, nvme_latency_batch_timeout_show, nvme_latency_batch_timeout_store}, + {"latency_stat", 0400, .seq_ops = &nvme_mpath_latency_stat_seq_ops}, #endif {}, }; static const struct nvme_debugfs_attr nvme_ns_debugfs_attrs[] = { +#ifdef CONFIG_NVME_MULTIPATH + {"latency_stat", 0400, nvme_ns_latency_stat_show}, +#endif {}, }; -- 2.53.0