[PATCH 5/6] NFSD: Count the storage reads of each export's READs
From: Jeff Layton
Date: Tue Oct 27 2026 - 12:49:12 EST
The page cache miss ratio of an export is the best indicator of the IO
mode that suits it, but NFSD reports only the bytes that READ returned.
Comparing that with the reads of the block device is approximate: the
device can hold other exports or local data, the count includes every
user of the device, and btrfs can span devices.
submit_bio() charges read bytes to the submitting task. Take a snapshot
of current->ioac.read_bytes when a READ starts, and add the difference
to a new io_read_storage counter of the export when it finishes. This
includes the readahead that the READ started and the metadata that it
needed, and works the same for buffered, dontcache, direct and splice
reads. export_stats shows the counter only with
CONFIG_TASK_IO_ACCOUNTING, so a missing counter means it is not
available.
Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/export.c | 4 ++++
fs/nfsd/export.h | 1 +
fs/nfsd/stats.h | 33 +++++++++++++++++++++++++++++++++
fs/nfsd/vfs.c | 20 ++++++++++++++------
4 files changed, 52 insertions(+), 6 deletions(-)
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 640973d02b6c..ac2d71a9c562 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -1675,6 +1675,10 @@ static int svc_export_show(struct seq_file *m,
export_stats_sum(stats, EXP_STATS_IO_READ));
seq_printf(m, "\tio_write: %lld\n",
export_stats_sum(stats, EXP_STATS_IO_WRITE));
+ if (IS_ENABLED(CONFIG_TASK_IO_ACCOUNTING))
+ seq_printf(m, "\tio_read_storage: %lld\n",
+ export_stats_sum(stats,
+ EXP_STATS_IO_READ_STORAGE));
seq_putc(m, '\n');
return 0;
}
diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
index 70842782629f..8ecd43894bec 100644
--- a/fs/nfsd/export.h
+++ b/fs/nfsd/export.h
@@ -53,6 +53,7 @@ enum {
EXP_STATS_FH_STALE,
EXP_STATS_IO_READ,
EXP_STATS_IO_WRITE,
+ EXP_STATS_IO_READ_STORAGE,
EXP_STATS_COUNTERS_NUM
};
diff --git a/fs/nfsd/stats.h b/fs/nfsd/stats.h
index aabfbb1a9c71..d88d2229a4c7 100644
--- a/fs/nfsd/stats.h
+++ b/fs/nfsd/stats.h
@@ -9,6 +9,7 @@
#include <uapi/linux/nfsd/stats.h>
#include <linux/percpu_counter.h>
+#include <linux/sched.h>
#include "export.h"
#include "netns.h"
@@ -97,6 +98,38 @@ static inline void nfsd_stats_io_write_add(struct nfsd_net *nn,
amount);
}
+/**
+ * nfsd_storage_read_bytes - bytes the current task has read from storage
+ *
+ * submit_bio() charges read bytes to the submitting task, so the change
+ * across a READ is what that READ, including the readahead it starts,
+ * read from storage instead of the page cache.
+ *
+ * Return: the task's storage read byte count, or zero without
+ * CONFIG_TASK_IO_ACCOUNTING.
+ */
+static inline u64 nfsd_storage_read_bytes(void)
+{
+#ifdef CONFIG_TASK_IO_ACCOUNTING
+ return current->ioac.read_bytes;
+#else
+ return 0;
+#endif
+}
+
+/**
+ * nfsd_stats_io_read_storage_add - Count storage bytes read for an NFS READ
+ * @exp: target export
+ * @amount: byte count
+ */
+static inline void nfsd_stats_io_read_storage_add(struct svc_export *exp,
+ s64 amount)
+{
+ if (exp && exp->ex_stats && amount > 0)
+ percpu_counter_add_local(&exp->ex_stats->counter[EXP_STATS_IO_READ_STORAGE],
+ amount);
+}
+
static inline void nfsd_stats_payload_misses_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_PAYLOAD_MISSES]);
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 15613b6df290..50ef96292003 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1113,8 +1113,11 @@ static u32 nfsd_eof_on_read(struct file *file, loff_t offset, ssize_t len,
static __be32 nfsd_finish_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct file *file, loff_t offset,
- unsigned long *count, u32 *eof, ssize_t host_err)
+ unsigned long *count, u32 *eof, ssize_t host_err,
+ u64 storage_start)
{
+ nfsd_stats_io_read_storage_add(fhp->fh_export,
+ nfsd_storage_read_bytes() - storage_start);
if (host_err >= 0) {
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
@@ -1145,12 +1148,14 @@ __be32 nfsd_splice_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct file *file, loff_t offset, unsigned long *count,
u32 *eof)
{
+ u64 storage_start = nfsd_storage_read_bytes();
ssize_t host_err;
trace_nfsd_read_splice(rqstp, fhp, offset, *count);
host_err = vfs_splice_to_actor(file, offset, *count,
nfsd_direct_splice_actor, rqstp);
- return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err);
+ return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err,
+ storage_start);
}
/*
@@ -1168,7 +1173,7 @@ __be32 nfsd_splice_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
static noinline_for_stack __be32
nfsd_direct_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset, unsigned long *count,
- struct page **page, u32 *eof)
+ struct page **page, u32 *eof, u64 storage_start)
{
u64 dio_start, dio_end;
unsigned long v, total;
@@ -1226,7 +1231,7 @@ nfsd_direct_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
}
return nfsd_finish_read(rqstp, fhp, nf->nf_file, offset, count,
- eof, host_err);
+ eof, host_err, storage_start);
}
static bool nfsd_direct_read_ok(struct svc_rqst *rqstp, struct svc_fh *fhp,
@@ -1324,6 +1329,7 @@ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct page **page = buf->pages + (buf->page_len >> PAGE_SHIFT);
unsigned int base = buf->page_len & ~PAGE_MASK;
struct file *file = nf->nf_file;
+ u64 storage_start = nfsd_storage_read_bytes();
unsigned long v, total;
struct iov_iter iter;
struct kiocb kiocb;
@@ -1338,7 +1344,8 @@ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
case NFSD_IO_DIRECT:
if (nfsd_direct_read_ok(rqstp, fhp, nf, offset, *count))
return nfsd_direct_read(rqstp, fhp, nf, offset,
- count, page, eof);
+ count, page, eof,
+ storage_start);
fallthrough;
case NFSD_IO_DONTCACHE:
if (file->f_op->fop_flags & FOP_DONTCACHE)
@@ -1367,7 +1374,8 @@ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
kiocb.ki_flags);
iov_iter_bvec(&iter, ITER_DEST, rqstp->rq_bvec, v, *count - total);
host_err = vfs_iocb_iter_read(file, &kiocb, &iter);
- return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err);
+ return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err,
+ storage_start);
}
/*
--
2.56.0