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,