Re: [PATCH nfs-utils] nfsstat: add netlink support for fetching server statistics

Steve Dickson <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>

On 5/25/26 8:45 AM, Jeff Layton wrote:
> Add Generic Netlink support to nfsstat for retrieving NFS server
> statistics, with automatic fallback to the existing /proc/net/rpc/nfsd
> parsing when netlink is unavailable (e.g., older kernels).
> 
> The new code sends NFSD_CMD_SERVER_STATS_GET as a dump command and
> parses the reply to populate the same stat arrays used by the proc
> path: reply cache (rc), filehandle (fh), IO, network, RPC, and
> per-version procedure call counts (proc2/3/4/proc4ops).
> 
> Signed-off-by: Jeff Layton <[email protected]>
Committed... (tag: nfs-utils-2-9-2-rc4)

steved
> ---
> This patch requires the corresponding kernel patches to add netlink
> support:
> 
> https://lore.kernel.org/linux-nfs/20260525-exportd-netlink-v2-0-40003fed450c-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org/
> ---
>   support/include/nfsd_netlink.h |  86 ++++++++-----
>   utils/nfsstat/Makefile.am      |   4 +-
>   utils/nfsstat/nfsstat.c        | 282 +++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 341 insertions(+), 31 deletions(-)
> 
> diff --git a/support/include/nfsd_netlink.h b/support/include/nfsd_netlink.h
> index a6a831866be8..3d076d173b1d 100644
> --- a/support/include/nfsd_netlink.h
> +++ b/support/include/nfsd_netlink.h
> @@ -128,27 +128,6 @@ enum {
>   	NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1)
>   };
>   
> -enum {
> -	NFSD_A_UNLOCK_IP_ADDRESS = 1,
> -
> -	__NFSD_A_UNLOCK_IP_MAX,
> -	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
> -};
> -
> -enum {
> -	NFSD_A_UNLOCK_FILESYSTEM_PATH = 1,
> -
> -	__NFSD_A_UNLOCK_FILESYSTEM_MAX,
> -	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
> -};
> -
> -enum {
> -	NFSD_A_UNLOCK_EXPORT_PATH = 1,
> -
> -	__NFSD_A_UNLOCK_EXPORT_MAX,
> -	NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1)
> -};
> -
>   enum {
>   	NFSD_A_FSLOCATION_HOST = 1,
>   	NFSD_A_FSLOCATION_PATH,
> @@ -172,15 +151,6 @@ enum {
>   	NFSD_A_AUTH_FLAVOR_MAX = (__NFSD_A_AUTH_FLAVOR_MAX - 1)
>   };
>   
> -enum {
> -	NFSD_A_SVC_EXPORT_REQ_SEQNO = 1,
> -	NFSD_A_SVC_EXPORT_REQ_CLIENT,
> -	NFSD_A_SVC_EXPORT_REQ_PATH,
> -
> -	__NFSD_A_SVC_EXPORT_REQ_MAX,
> -	NFSD_A_SVC_EXPORT_REQ_MAX = (__NFSD_A_SVC_EXPORT_REQ_MAX - 1)
> -};
> -
>   enum {
>   	NFSD_A_SVC_EXPORT_SEQNO = 1,
>   	NFSD_A_SVC_EXPORT_CLIENT,
> @@ -234,6 +204,61 @@ enum {
>   	NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1)
>   };
>   
> +enum {
> +	NFSD_A_UNLOCK_IP_ADDRESS = 1,
> +
> +	__NFSD_A_UNLOCK_IP_MAX,
> +	NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1)
> +};
> +
> +enum {
> +	NFSD_A_UNLOCK_FILESYSTEM_PATH = 1,
> +
> +	__NFSD_A_UNLOCK_FILESYSTEM_MAX,
> +	NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
> +};
> +
> +enum {
> +	NFSD_A_UNLOCK_EXPORT_PATH = 1,
> +
> +	__NFSD_A_UNLOCK_EXPORT_MAX,
> +	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,
> @@ -253,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)
> diff --git a/utils/nfsstat/Makefile.am b/utils/nfsstat/Makefile.am
> index d1555a7d82d3..8b121c48c35c 100644
> --- a/utils/nfsstat/Makefile.am
> +++ b/utils/nfsstat/Makefile.am
> @@ -5,8 +5,10 @@ EXTRA_DIST	= $(man8_MANS)
>   
>   sbin_PROGRAMS	= nfsstat
>   nfsstat_SOURCES = nfsstat.c
> +nfsstat_CFLAGS = $(LIBNL3_CFLAGS) $(LIBNLGENL3_CFLAGS)
>   nfsstat_LDADD = ../../support/export/libexport.a \
>   	      	../../support/nfs/libnfs.la \
> -		../../support/misc/libmisc.a
> +		../../support/misc/libmisc.a \
> +		$(LIBNL3_LIBS) $(LIBNLGENL3_LIBS)
>   
>   MAINTAINERCLEANFILES = Makefile.in
> diff --git a/utils/nfsstat/nfsstat.c b/utils/nfsstat/nfsstat.c
> index ca845325f0dc..f09e1d6a447f 100644
> --- a/utils/nfsstat/nfsstat.c
> +++ b/utils/nfsstat/nfsstat.c
> @@ -23,6 +23,17 @@
>   #include <signal.h>
>   #include <time.h>
>   
> +#include <netlink/genl/genl.h>
> +#include <netlink/genl/ctrl.h>
> +#include <netlink/msg.h>
> +#include <netlink/attr.h>
> +
> +#ifdef USE_SYSTEM_NFSD_NETLINK_H
> +#include <linux/nfsd_netlink.h>
> +#else
> +#include "nfsd_netlink.h"
> +#endif
> +
>   #define MAXNRVALS	32
>   
>   enum {
> @@ -271,6 +282,7 @@ static statinfo		*get_stat_info(const char *, struct statinfo *);
>   
>   static int		mounts(const char *);
>   
> +static int		get_stats_netlink(struct statinfo *);
>   static void		get_stats(const char *, struct statinfo *, int *, int,
>   					int);
>   static int		has_stats(const unsigned int *, int);
> @@ -1051,6 +1063,272 @@ mounts(const char *name)
>   	return 1;
>   }
>   
> +/*
> + * Netlink helpers for fetching server stats via Generic Netlink.
> + */
> +static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
> +			    void *arg)
> +{
> +	int *ret = arg;
> +
> +	*ret = err->error;
> +	return NL_SKIP;
> +}
> +
> +static int nl_finish_handler(struct nl_msg *msg, void *arg)
> +{
> +	int *ret = arg;
> +
> +	*ret = 0;
> +	return NL_SKIP;
> +}
> +
> +static int nl_ack_handler(struct nl_msg *msg, void *arg)
> +{
> +	int *ret = arg;
> +
> +	*ret = 0;
> +	return NL_STOP;
> +}
> +
> +static void parse_one_proc_entry(struct nlattr *nest, unsigned int *info,
> +				 unsigned int max_ops)
> +{
> +	struct nlattr *tb[NFSD_A_SERVER_PROC_ENTRY_MAX + 1];
> +	unsigned int op, count;
> +
> +	nla_parse_nested(tb, NFSD_A_SERVER_PROC_ENTRY_MAX, nest, NULL);
> +	if (!tb[NFSD_A_SERVER_PROC_ENTRY_OP] ||
> +	    !tb[NFSD_A_SERVER_PROC_ENTRY_COUNT])
> +		return;
> +
> +	op = nla_get_u32(tb[NFSD_A_SERVER_PROC_ENTRY_OP]);
> +	count = (unsigned int)nla_get_u64(tb[NFSD_A_SERVER_PROC_ENTRY_COUNT]);
> +	if (op < max_ops) {
> +		info[0] = max_ops;
> +		info[op + 1] = count;
> +	}
> +}
> +
> +static int stats_nl_handler(struct nl_msg *msg, void *arg)
> +{
> +	struct statinfo *info = arg;
> +	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
> +	struct nlattr *attr;
> +	statinfo *si;
> +	int rem;
> +
> +	nla_for_each_attr(attr, genlmsg_attrdata(gnlh, 0),
> +			  genlmsg_attrlen(gnlh, 0), rem) {
> +		int type = nla_type(attr);
> +
> +		switch (type) {
> +		/* Reply cache */
> +		case NFSD_A_SERVER_STATS_RC_HITS:
> +			si = get_stat_info("rc", info);
> +			if (si)
> +				si->valptr[0] = nla_get_u64(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_RC_MISSES:
> +			si = get_stat_info("rc", info);
> +			if (si)
> +				si->valptr[1] = nla_get_u64(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_RC_NOCACHE:
> +			si = get_stat_info("rc", info);
> +			if (si)
> +				si->valptr[2] = nla_get_u64(attr);
> +			break;
> +
> +		/* Filehandle */
> +		case NFSD_A_SERVER_STATS_FH_STALE:
> +			si = get_stat_info("fh", info);
> +			if (si)
> +				si->valptr[0] = nla_get_u64(attr);
> +			break;
> +
> +		/* IO */
> +		case NFSD_A_SERVER_STATS_IO_READ:
> +			si = get_stat_info("io", info);
> +			if (si)
> +				si->valptr[0] = nla_get_u64(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_IO_WRITE:
> +			si = get_stat_info("io", info);
> +			if (si)
> +				si->valptr[1] = nla_get_u64(attr);
> +			break;
> +
> +		/* Network */
> +		case NFSD_A_SERVER_STATS_NETCNT:
> +			si = get_stat_info("net", info);
> +			if (si)
> +				si->valptr[0] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_NETUDPCNT:
> +			si = get_stat_info("net", info);
> +			if (si)
> +				si->valptr[1] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_NETTCPCNT:
> +			si = get_stat_info("net", info);
> +			if (si)
> +				si->valptr[2] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_NETTCPCONN:
> +			si = get_stat_info("net", info);
> +			if (si)
> +				si->valptr[3] = nla_get_u32(attr);
> +			break;
> +
> +		/* RPC */
> +		case NFSD_A_SERVER_STATS_RPCCNT:
> +			si = get_stat_info("rpc", info);
> +			if (si)
> +				si->valptr[0] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_RPCBADFMT:
> +			si = get_stat_info("rpc", info);
> +			if (si)
> +				si->valptr[2] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_RPCBADAUTH:
> +			si = get_stat_info("rpc", info);
> +			if (si)
> +				si->valptr[3] = nla_get_u32(attr);
> +			break;
> +		case NFSD_A_SERVER_STATS_RPCBADCLNT:
> +			si = get_stat_info("rpc", info);
> +			if (si)
> +				si->valptr[4] = nla_get_u32(attr);
> +			break;
> +
> +		/* Per-version procedure counts (multi-attr) */
> +		case NFSD_A_SERVER_STATS_PROC2_OPS:
> +			si = get_stat_info("proc2", info);
> +			if (si)
> +				parse_one_proc_entry(attr, si->valptr,
> +						     SRVPROC2_SZ);
> +			break;
> +		case NFSD_A_SERVER_STATS_PROC3_OPS:
> +			si = get_stat_info("proc3", info);
> +			if (si)
> +				parse_one_proc_entry(attr, si->valptr,
> +						     SRVPROC3_SZ);
> +			break;
> +		case NFSD_A_SERVER_STATS_PROC4_OPS:
> +			si = get_stat_info("proc4", info);
> +			if (si)
> +				parse_one_proc_entry(attr, si->valptr,
> +						     SRVPROC4_SZ);
> +			break;
> +		case NFSD_A_SERVER_STATS_PROC4OPS_OPS:
> +			si = get_stat_info("proc4ops", info);
> +			if (si)
> +				parse_one_proc_entry(attr, si->valptr,
> +						     SRVPROC4OPS_SZ);
> +			break;
> +		}
> +	}
> +
> +	return NL_OK;
> +}
> +
> +/*
> + * Fetch server stats via Generic Netlink.
> + * Returns 0 on success, -1 on failure.
> + */
> +static int
> +get_stats_netlink(struct statinfo *info)
> +{
> +	struct nl_sock *sock;
> +	struct nl_msg *msg;
> +	struct nl_cb *cb;
> +	int family, ret;
> +	statinfo *si;
> +
> +	sock = nl_socket_alloc();
> +	if (!sock)
> +		return -1;
> +
> +	if (genl_connect(sock)) {
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	family = genl_ctrl_resolve(sock, NFSD_FAMILY_NAME);
> +	if (family < 0) {
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	msg = nlmsg_alloc();
> +	if (!msg) {
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	if (!genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0,
> +			 NLM_F_DUMP, NFSD_CMD_SERVER_STATS_GET, 0)) {
> +		nlmsg_free(msg);
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	cb = nl_cb_alloc(NL_CB_CUSTOM);
> +	if (!cb) {
> +		nlmsg_free(msg);
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	ret = nl_send_auto(sock, msg);
> +	if (ret < 0) {
> +		nl_cb_put(cb);
> +		nlmsg_free(msg);
> +		nl_socket_free(sock);
> +		return -1;
> +	}
> +
> +	ret = 1;
> +	nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &ret);
> +	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &ret);
> +	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &ret);
> +	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, stats_nl_handler, info);
> +
> +	while (ret > 0)
> +		nl_recvmsgs(sock, cb);
> +
> +	nl_cb_put(cb);
> +	nlmsg_free(msg);
> +	nl_socket_free(sock);
> +
> +	if (ret < 0)
> +		return -1;
> +
> +	/*
> +	 * Compute derived fields. The proc file emits "rpc rpccnt
> +	 * badcalls badfmt badauth badclnt" where badcalls is the sum
> +	 * of badfmt+badauth+badclnt. The netlink interface sends the
> +	 * components individually, so recompute the sum here.
> +	 */
> +	si = get_stat_info("rpc", info);
> +	if (si)
> +		si->valptr[1] = si->valptr[2] + si->valptr[3] + si->valptr[4];
> +
> +	/* Compute totals for each stat category */
> +	for (si = info; si->tag; si++) {
> +		unsigned int total = 0;
> +		int i;
> +
> +		for (i = 0; i < si->nrvals - 1; i++)
> +			total += si->valptr[i];
> +		si->valptr[si->nrvals - 1] = total;
> +	}
> +
> +	return 0;
> +}
> +
>   static void
>   get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
>   		int is_srv)
> @@ -1060,6 +1338,10 @@ get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
>   	int err = 1;
>   	char *label = is_srv ? "Server" : "Client";
>   
> +	/* Try netlink first for server stats */
> +	if (is_srv && get_stats_netlink(info) == 0)
> +		return;
> +
>   	/* try to guess what type of stat file we're dealing with */
>   	if ((fp = fopen(file, "r")) == NULL)
>   		goto out;
> 
> ---
> base-commit: a806c9d65662ecf5d40c00d60a514e13ada8d76e
> change-id: 20260514-nfsstat-nl-16cabe1c80ae
> 
> Best regards,
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.