[PATCH v2 1/4] sunrpc: add per-netns per-procedure call counts to svc_stat

From: Jeff Layton

Date: Mon May 25 2026 - 08:39:34 EST


The existing per-procedure call counts live in global
svc_version->vs_count[] arrays which are not network-namespace-aware.
Add per-netns equivalents in struct svc_stat so the upcoming netlink
stats interface can return namespace-scoped statistics.

Add a vs_count pointer array to struct svc_stat, along with
svc_stat_alloc_counts() and svc_stat_free_counts() helpers to manage
per-version percpu call count arrays.

Increment the per-net counter alongside the global one in
svc_generic_init_request(). Call the alloc/free helpers from
nfsd_net_init() and nfsd_net_exit().

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/nfsctl.c | 8 ++++++-
include/linux/sunrpc/stats.h | 6 +++++
net/sunrpc/stats.c | 54 ++++++++++++++++++++++++++++++++++++++++++++
net/sunrpc/svc.c | 7 ++++++
4 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 468aad8c3af9..3aaa67b322d5 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2445,9 +2445,12 @@ static __net_init int nfsd_net_init(struct net *net)

memset(&nn->nfsd_svcstats, 0, sizeof(nn->nfsd_svcstats));
nn->nfsd_svcstats.program = &nfsd_programs[0];
+ retval = svc_stat_alloc_counts(&nn->nfsd_svcstats);
+ if (retval)
+ goto out_proc_error;
if (!nfsd_proc_stat_init(net)) {
retval = -ENOMEM;
- goto out_proc_error;
+ goto out_svcstats_error;
}

for (i = 0; i < sizeof(nn->nfsd_versions); i++)
@@ -2465,6 +2468,8 @@ static __net_init int nfsd_net_init(struct net *net)
#endif
return 0;

+out_svcstats_error:
+ svc_stat_free_counts(&nn->nfsd_svcstats);
out_proc_error:
percpu_counter_destroy_many(nn->counter, NFSD_STATS_COUNTERS_NUM);
out_repcache_error:
@@ -2505,6 +2510,7 @@ static __net_exit void nfsd_net_exit(struct net *net)
kfree_sensitive(nn->fh_key);
nfsd_net_cb_shutdown(nn);
nfsd_proc_stat_shutdown(net);
+ svc_stat_free_counts(&nn->nfsd_svcstats);
percpu_counter_destroy_many(nn->counter, NFSD_STATS_COUNTERS_NUM);
nfsd_idmap_shutdown(net);
nfsd_export_shutdown(net);
diff --git a/include/linux/sunrpc/stats.h b/include/linux/sunrpc/stats.h
index 3ce1550d1beb..087ade905e29 100644
--- a/include/linux/sunrpc/stats.h
+++ b/include/linux/sunrpc/stats.h
@@ -37,9 +37,15 @@ struct svc_stat {
rpcbadfmt,
rpcbadauth,
rpcbadclnt;
+
+ /* Per-version per-procedure call counts (per-cpu, per-netns) */
+ unsigned long __percpu **vs_count;
};

struct net;
+int svc_stat_alloc_counts(struct svc_stat *statp);
+void svc_stat_free_counts(struct svc_stat *statp);
+
#ifdef CONFIG_PROC_FS
int rpc_proc_init(struct net *);
void rpc_proc_exit(struct net *);
diff --git a/net/sunrpc/stats.c b/net/sunrpc/stats.c
index 7093e18ac26c..5bcecd2919b1 100644
--- a/net/sunrpc/stats.c
+++ b/net/sunrpc/stats.c
@@ -116,6 +116,60 @@ void svc_seq_show(struct seq_file *seq, const struct svc_stat *statp)
}
EXPORT_SYMBOL_GPL(svc_seq_show);

+/**
+ * svc_stat_alloc_counts - allocate per-netns per-version call count arrays
+ * @statp: svc_stat whose vs_count arrays should be allocated
+ *
+ * statp->program must be set before calling this.
+ *
+ * Returns zero on success, or a negative errno otherwise.
+ */
+int svc_stat_alloc_counts(struct svc_stat *statp)
+{
+ struct svc_program *prog = statp->program;
+ unsigned int i;
+
+ statp->vs_count = kcalloc(prog->pg_nvers,
+ sizeof(unsigned long __percpu *),
+ GFP_KERNEL);
+ if (!statp->vs_count)
+ return -ENOMEM;
+
+ for (i = 0; i < prog->pg_nvers; i++) {
+ if (!prog->pg_vers[i])
+ continue;
+ statp->vs_count[i] = __alloc_percpu(prog->pg_vers[i]->vs_nproc *
+ sizeof(unsigned long),
+ sizeof(unsigned long));
+ if (!statp->vs_count[i])
+ goto err;
+ }
+ return 0;
+err:
+ svc_stat_free_counts(statp);
+ return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL(svc_stat_alloc_counts);
+
+/**
+ * svc_stat_free_counts - free per-netns per-version call count arrays
+ * @statp: svc_stat whose vs_count arrays should be freed
+ */
+void svc_stat_free_counts(struct svc_stat *statp)
+{
+ struct svc_program *prog = statp->program;
+ unsigned int i;
+
+ if (!statp->vs_count)
+ return;
+
+ for (i = 0; i < prog->pg_nvers; i++)
+ free_percpu(statp->vs_count[i]);
+ kfree(statp->vs_count);
+ statp->vs_count = NULL;
+}
+EXPORT_SYMBOL_GPL(svc_stat_free_counts);
+
/**
* rpc_alloc_iostats - allocate an rpc_iostats structure
* @clnt: RPC program, version, and xprt
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index ae9ec4bf34f7..bf7c57cbfa3d 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -1375,6 +1375,13 @@ svc_generic_init_request(struct svc_rqst *rqstp,
/* Bump per-procedure stats counter */
this_cpu_inc(versp->vs_count[rqstp->rq_proc]);

+ /* Bump per-net per-procedure stats counter */
+ if (rqstp->rq_server->sv_stats &&
+ rqstp->rq_server->sv_stats->vs_count &&
+ rqstp->rq_server->sv_stats->vs_count[rqstp->rq_vers])
+ this_cpu_inc(rqstp->rq_server->sv_stats->vs_count
+ [rqstp->rq_vers][rqstp->rq_proc]);
+
ret->dispatch = versp->vs_dispatch;
return rpc_success;
err_bad_vers:

--
2.54.0