From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E530F1E9B3D; Tue, 9 Jun 2026 16:16:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781021766; cv=none; b=APp4SEfHSvVSyG4HGLRH4E/rZ3ZK9fquru+PgJbHVZD/A/Ndptid8SegLGABJhniCcGvYzpifemFOvESeqE8O50n0PD1KBJhEW/YzzSRcwZ698Cap/GUTyyekeqnAoXH5E+7GxMBLiRykEf7/dlNydjNICnF40Bkl25cS0dCYg4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781021766; c=relaxed/simple; bh=y2zkUS94UNsBE45o87S6jI9UVFaTAR2JPkR6IJwE6fE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nmwTxWQLzaeywyJq8/KzKVUqOcabwQ3rs5pZUIDG7xNFqBAhBxp+YNadDOCfDcr7xK1bTDnnX/ihp8bmRhri1kh7TdEauD1jMVRVN5vPCc5NeWI3A8FE6huzkGs7E63zN2dRd2RiAQOSrxwhqiIa/jmJ+GtisQjAaHl78hdXUXQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=FheCmUDI; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="FheCmUDI" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DBB9F1F0089C; Tue, 9 Jun 2026 16:16:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1781021764; bh=fSMGCfXV0TcUjhTaUw1rEnpTb/AWw4pKxCmTahpo+uk=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=FheCmUDIvn2tzTVoY+JPKnkJbHmmHCcyPUnKZeimV/jj2Uek8FHW/Rs+K0janWA/n RdU2xnofLoAJ8sx9i7r8DkYpiQDgghJxa292W1asDRe6hUHwYJdXt3r824DGJke0sV No0pSmIJrXwVMKg/V1zbdnFm2l2bK78nrzjndlFQpzjiTi1ZbMqcenJwgrwAKHrQYG 4ItLhZsAwtbSLLwl5m+H2bmMxzPFGcfgNsRpCsWa70i9Z9YWKNnF3Qs9nyk3KPxLp5 c5vxcB2PGqFgQm2aNtG4F09RvuqtRAawqTnIGcpH/L9Y30PaUpD/qAbg372CbNMGCm n+EAb4pXNuxNg== From: Jeff Layton Date: Tue, 09 Jun 2026 12:15:56 -0400 Subject: [PATCH v3 3/4] nfsd: implement server-stats-get netlink handler Precedence: bulk X-Mailing-List: linux-nfs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260609-exportd-netlink-v3-3-aa5508a5bb1d@kernel.org> References: <20260609-exportd-netlink-v3-0-aa5508a5bb1d@kernel.org> In-Reply-To: <20260609-exportd-netlink-v3-0-aa5508a5bb1d@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey Cc: Trond Myklebust , Anna Schumaker , Steve Dickson , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13119; i=jlayton@kernel.org; h=from:subject:message-id; bh=y2zkUS94UNsBE45o87S6jI9UVFaTAR2JPkR6IJwE6fE=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqKDw/LCqQ9tOXMkmR7JqrSDmj9Pi4XCX8X/vGP uKtWsF5BNuJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCaig8PwAKCRAADmhBGVaC Fa71EAC/wRN+RjDTPod2EHac+D6wZYRXcXN0+imqKTTJDhH3rsURZ1hO/dAJxKgVRz7mdShgiEo Ot1i780WPUUk/ePXGRMQ81BXGhYJiunUgResMIQReWjUMFJaK6pF/gWM4AaT2lOxGKgNVtIXtRk 2EoNTDD6Pul7O33HNxApWT+KiG4T7Zr8q3Q7Sld1sRYCuLZRL6IjxpfuOhORN+nijgdAX/3ThUz XHGZ2IL/LUY4SEHZonBtQrzLHRHBJqPcl7qD/ce5tKULUTWXCpksxBsmYrPjZZMTjSuDc0st7uO UaVfa4QxE97F1SD1/yLTdBFcJRXMb3TJAVzjO/HZMesh4s26D66IrYBGLfFpgOtOZ80p2puTEFT vVnqY9y79oTtJmFwqp6wXjKgXM1gKCUWwBRAhiVe80+EFtRFdibiTvAAOzq0LWOZaq1wO/loOPi 2GKMSnTo1uJQu7jLI0KJGWqcKjjxyu7+OPoi2e21v5f4zILZRMA++Ep4zRP0yXnidXdWOkZ31PJ N2cyOIkeCeHAPtP66mwlW7GaynHCBzsAKBYxtr2Wc0QRbFOJt/NIs+0i7Hyn/DCAqvtnheyK6B7 uqbfJ4MaPyaHqTRXF3Np0A/ZyqXj1Od5wDi4MWoVxkggDxtasQG9Sx5F0UhIiZNbghmeFTsK9aT x23ojrBwJW/kaSA== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 Implement nfsd_nl_server_stats_get_dumpit() which exposes the NFS server statistics currently available via /proc/net/rpc/nfsd through the nfsd generic netlink family. The handler uses a dump operation to stream statistics across multiple netlink messages: - First message: all scalar stats (reply cache, filehandle, IO, network, RPC) plus per-version procedure counts (proc2/3/4-ops) using per-netns vs_count arrays. - Subsequent messages: NFSv4 per-operation counts (proc4ops-ops), one entry per message, using cb->args[0] to track the current operation index across dump calls. This allows nfsstat to retrieve server statistics via netlink with a procfs fallback for older kernels. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Jeff Layton --- Documentation/netlink/specs/nfsd.yaml | 105 +++++++++++++++++ fs/nfsd/netlink.c | 5 + fs/nfsd/netlink.h | 2 + fs/nfsd/nfsctl.c | 213 ++++++++++++++++++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 35 ++++++ 5 files changed, 360 insertions(+) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 8f36fadd68f7..2a89d355ee7b 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -330,6 +330,86 @@ attribute-sets: of which client holds the state. Intended for use after all clients have been unexported from a given path, enabling the underlying filesystem to be unmounted. + - + name: server-proc-entry + attributes: + - + name: op + type: u32 + - + name: count + type: u64 + - + name: pad + type: pad + - + name: server-stats + attributes: + - + name: rc-hits + type: u64 + - + name: rc-misses + type: u64 + - + name: rc-nocache + type: u64 + - + name: pad + type: pad + - + name: fh-stale + type: u64 + - + name: io-read + type: u64 + - + name: io-write + type: u64 + - + name: netcnt + type: u32 + - + name: netudpcnt + type: u32 + - + name: nettcpcnt + type: u32 + - + name: nettcpconn + type: u32 + - + name: rpccnt + type: u32 + - + name: rpcbadfmt + type: u32 + - + name: rpcbadauth + type: u32 + - + name: rpcbadclnt + type: u32 + - + name: proc2-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc3-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc4-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc4ops-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true operations: list: @@ -516,6 +596,31 @@ operations: request: attributes: - path + - + name: server-stats-get + doc: dump NFS server statistics + attribute-set: server-stats + dump: + reply: + attributes: + - rc-hits + - rc-misses + - rc-nocache + - fh-stale + - io-read + - io-write + - netcnt + - netudpcnt + - nettcpcnt + - nettcpconn + - rpccnt + - rpcbadfmt + - rpcbadauth + - rpcbadclnt + - proc2-ops + - proc3-ops + - proc4-ops + - proc4ops-ops mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index fbee3676d253..eba8b353f412 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -225,6 +225,11 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_UNLOCK_EXPORT_PATH, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_SERVER_STATS_GET, + .dumpit = nfsd_nl_server_stats_get_dumpit, + .flags = GENL_CMD_CAP_DUMP, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index af41aa0d4a65..027e2953db26 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -42,6 +42,8 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 7b802d335501..d60f2a0f07f0 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2252,6 +2252,219 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info) return 0; } +static int nfsd_nl_fill_proc_ops(struct sk_buff *skb, int attr, + unsigned long __percpu *counts, + unsigned int nproc) +{ + struct nlattr *nest; + unsigned int j; + int k; + + for (j = 0; j < nproc; j++) { + unsigned long count = 0; + + for_each_possible_cpu(k) + count += per_cpu(counts[j], k); + + nest = nla_nest_start(skb, attr); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(skb, NFSD_A_SERVER_PROC_ENTRY_OP, j) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_PROC_ENTRY_COUNT, + count, NFSD_A_SERVER_PROC_ENTRY_PAD)) { + nla_nest_cancel(skb, nest); + return -EMSGSIZE; + } + nla_nest_end(skb, nest); + } + + return 0; +} + +/** + * nfsd_nl_server_stats_get_dumpit - dump NFS server statistics + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * cb->args[0] tracks the current NFSv4 operation index for proc4ops. + * A value of 0 means we haven't started yet. We emit all scalar stats + * and per-version procedure counts in the first message, then emit + * proc4ops entries filling as many as will fit per message. + * + * Returns the size of the reply or a negative errno. + */ +int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct svc_stat *statp = &nn->nfsd_svcstats; + struct svc_program *prog = statp->program; + int start = cb->args[0]; + void *hdr; + int ret; + + /* + * cb->args[0] == 0: first call, emit scalar stats + procN counts + * cb->args[0] > 0: emit proc4ops entries starting from args[0] - 1 + * cb->args[0] < 0: done + */ + if (start < 0) + return 0; + + if (start == 0) { + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &nfsd_nl_family, + NLM_F_MULTI, NFSD_CMD_SERVER_STATS_GET); + if (!hdr) + return -ENOBUFS; + + /* Reply cache stats */ + { + u64 hits, misses, nocache; + + hits = percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_HITS]); + misses = percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_MISSES]); + nocache = percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_NOCACHE]); + ret = nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_HITS, + hits, NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_MISSES, + misses, NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_NOCACHE, + nocache, NFSD_A_SERVER_STATS_PAD); + } + if (ret) + goto err_cancel; + + /* Filehandle stats */ + ret = nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_FH_STALE, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_FH_STALE]), + NFSD_A_SERVER_STATS_PAD); + if (ret) + goto err_cancel; + + /* IO stats */ + { + u64 rd, wr; + + rd = percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_READ]); + wr = percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_WRITE]); + ret = nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_READ, + rd, NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_WRITE, + wr, NFSD_A_SERVER_STATS_PAD); + } + if (ret) + goto err_cancel; + + /* Network stats */ + ret = nla_put_u32(skb, NFSD_A_SERVER_STATS_NETCNT, + statp->netcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETUDPCNT, + statp->netudpcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETTCPCNT, + statp->nettcpcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETTCPCONN, + statp->nettcpconn); + if (ret) + goto err_cancel; + + /* RPC stats */ + ret = nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCCNT, + statp->rpccnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADFMT, + statp->rpcbadfmt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADAUTH, + statp->rpcbadauth) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADCLNT, + statp->rpcbadclnt); + if (ret) + goto err_cancel; + + /* Per-version procedure counts */ + if (statp->vs_count) { + static const int proc_attrs[] = { + [2] = NFSD_A_SERVER_STATS_PROC2_OPS, + [3] = NFSD_A_SERVER_STATS_PROC3_OPS, + [4] = NFSD_A_SERVER_STATS_PROC4_OPS, + }; + unsigned int i; + + for (i = 0; i < prog->pg_nvers && + i < ARRAY_SIZE(proc_attrs); i++) { + if (!prog->pg_vers[i] || + !statp->vs_count[i]) + continue; + if (!proc_attrs[i]) + continue; + ret = nfsd_nl_fill_proc_ops(skb, + proc_attrs[i], + statp->vs_count[i], + prog->pg_vers[i]->vs_nproc); + if (ret) + goto err_cancel; + } + } + + genlmsg_end(skb, hdr); + cb->args[0] = 1; + } + +#ifdef CONFIG_NFSD_V4 + /* NFSv4 individual operation counts */ + { + int i = (start > 0) ? start - 1 : 0; + + for (; i <= LAST_NFS4_OP; i++) { + struct percpu_counter *ctr; + struct nlattr *nest; + u64 cnt; + + ctr = &nn->counter[NFSD_STATS_NFS4_OP(i)]; + cnt = percpu_counter_sum_positive(ctr); + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + &nfsd_nl_family, NLM_F_MULTI, + NFSD_CMD_SERVER_STATS_GET); + if (!hdr) { + cb->args[0] = i + 1; + goto out; + } + + nest = nla_nest_start(skb, + NFSD_A_SERVER_STATS_PROC4OPS_OPS); + if (!nest) { + genlmsg_cancel(skb, hdr); + cb->args[0] = i + 1; + goto out; + } + if (nla_put_u32(skb, NFSD_A_SERVER_PROC_ENTRY_OP, + i) || + nla_put_u64_64bit(skb, + NFSD_A_SERVER_PROC_ENTRY_COUNT, + cnt, + NFSD_A_SERVER_PROC_ENTRY_PAD)) { + nla_nest_cancel(skb, nest); + genlmsg_cancel(skb, hdr); + cb->args[0] = i + 1; + goto out; + } + nla_nest_end(skb, nest); + genlmsg_end(skb, hdr); + } + } +#endif + + cb->args[0] = -1; +out: + return skb->len; + +err_cancel: + genlmsg_cancel(skb, hdr); + return ret; +} + int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type) { struct genlmsghdr *hdr; diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index f5b75d5caba9..3d076d173b1d 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -225,6 +225,40 @@ enum { NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1) }; +enum { + NFSD_A_SERVER_PROC_ENTRY_OP = 1, + NFSD_A_SERVER_PROC_ENTRY_COUNT, + NFSD_A_SERVER_PROC_ENTRY_PAD, + + __NFSD_A_SERVER_PROC_ENTRY_MAX, + NFSD_A_SERVER_PROC_ENTRY_MAX = (__NFSD_A_SERVER_PROC_ENTRY_MAX - 1) +}; + +enum { + NFSD_A_SERVER_STATS_RC_HITS = 1, + NFSD_A_SERVER_STATS_RC_MISSES, + NFSD_A_SERVER_STATS_RC_NOCACHE, + NFSD_A_SERVER_STATS_PAD, + NFSD_A_SERVER_STATS_FH_STALE, + NFSD_A_SERVER_STATS_IO_READ, + NFSD_A_SERVER_STATS_IO_WRITE, + NFSD_A_SERVER_STATS_NETCNT, + NFSD_A_SERVER_STATS_NETUDPCNT, + NFSD_A_SERVER_STATS_NETTCPCNT, + NFSD_A_SERVER_STATS_NETTCPCONN, + NFSD_A_SERVER_STATS_RPCCNT, + NFSD_A_SERVER_STATS_RPCBADFMT, + NFSD_A_SERVER_STATS_RPCBADAUTH, + NFSD_A_SERVER_STATS_RPCBADCLNT, + NFSD_A_SERVER_STATS_PROC2_OPS, + NFSD_A_SERVER_STATS_PROC3_OPS, + NFSD_A_SERVER_STATS_PROC4_OPS, + NFSD_A_SERVER_STATS_PROC4OPS_OPS, + + __NFSD_A_SERVER_STATS_MAX, + NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -244,6 +278,7 @@ enum { NFSD_CMD_UNLOCK_IP, NFSD_CMD_UNLOCK_FILESYSTEM, NFSD_CMD_UNLOCK_EXPORT, + NFSD_CMD_SERVER_STATS_GET, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) -- 2.54.0