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 --- 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 2b83f2836b0e..3971bcee4214 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 b9306a360c19..dcff16618edf 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 #include +#include #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 779ad3a61172..a8cf41e4dfea 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