summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/nfsd/netlink.c5
-rw-r--r--fs/nfsd/netlink.h2
-rw-r--r--fs/nfsd/nfsctl.c222
3 files changed, 229 insertions, 0 deletions
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 ae5a7f1ad917..7731051105c9 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2331,6 +2331,228 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info)
return 0;
}
+/* Emit a single server-proc-entry nest: { op, count }. */
+static int nfsd_nl_put_proc_entry(struct sk_buff *skb, int attr,
+ u32 op, u64 count)
+{
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, attr);
+ if (!nest)
+ return -EMSGSIZE;
+ if (nla_put_u32(skb, NFSD_A_SERVER_PROC_ENTRY_OP, op) ||
+ 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;
+}
+
+/* Emit the scalar server-stats counters. Only ever called on a fresh skb. */
+static int nfsd_nl_server_stats_scalars(struct sk_buff *skb,
+ struct nfsd_net *nn,
+ struct svc_stat *statp)
+{
+ if (nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_HITS,
+ percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_HITS]),
+ NFSD_A_SERVER_STATS_PAD) ||
+ nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_MISSES,
+ percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_MISSES]),
+ NFSD_A_SERVER_STATS_PAD) ||
+ nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_NOCACHE,
+ percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_NOCACHE]),
+ NFSD_A_SERVER_STATS_PAD))
+ return -EMSGSIZE;
+
+ if (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))
+ return -EMSGSIZE;
+
+ if (nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_READ,
+ percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_READ]),
+ NFSD_A_SERVER_STATS_PAD) ||
+ nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_WRITE,
+ percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_WRITE]),
+ NFSD_A_SERVER_STATS_PAD))
+ return -EMSGSIZE;
+
+ if (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))
+ return -EMSGSIZE;
+
+ if (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))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+/*
+ * Emit per-version procedure counts for one NFS version, resuming at *idx.
+ * Returns 0 when the version has been fully emitted (or is not present), or
+ * -EMSGSIZE when @skb filled up, leaving *idx at the entry still to emit.
+ */
+static int nfsd_nl_server_stats_proc(struct sk_buff *skb,
+ struct svc_stat *statp,
+ struct svc_program *prog,
+ unsigned int ver, int attr, int *idx)
+{
+ unsigned long __percpu *counts;
+ unsigned int nproc;
+
+ if (!statp->vs_count || ver >= prog->pg_nvers ||
+ !prog->pg_vers[ver] || !statp->vs_count[ver])
+ return 0;
+
+ counts = statp->vs_count[ver];
+ nproc = prog->pg_vers[ver]->vs_nproc;
+
+ for (; *idx < nproc; (*idx)++) {
+ unsigned long count = 0;
+ int cpu;
+
+ for_each_possible_cpu(cpu)
+ count += per_cpu(counts[*idx], cpu);
+
+ if (!count)
+ continue;
+ if (nfsd_nl_put_proc_entry(skb, attr, *idx, count))
+ return -EMSGSIZE;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_NFSD_V4
+/*
+ * Emit NFSv4 per-operation counts, resuming at *idx. Same return convention
+ * as nfsd_nl_server_stats_proc().
+ */
+static int nfsd_nl_server_stats_nfs4ops(struct sk_buff *skb,
+ struct nfsd_net *nn, int *idx)
+{
+ for (; *idx <= LAST_NFS4_OP; (*idx)++) {
+ u64 cnt = percpu_counter_sum_positive(
+ &nn->counter[NFSD_STATS_NFS4_OP(*idx)]);
+
+ if (!cnt)
+ continue;
+ if (nfsd_nl_put_proc_entry(skb, NFSD_A_SERVER_STATS_PROC4OPS_OPS,
+ *idx, cnt))
+ return -EMSGSIZE;
+ }
+
+ return 0;
+}
+#endif
+
+/* Sections of the server-stats dump, emitted in order across messages. */
+enum {
+ NFSD_SERVER_STATS_SCALARS = 0,
+ NFSD_SERVER_STATS_PROC2,
+ NFSD_SERVER_STATS_PROC3,
+ NFSD_SERVER_STATS_PROC4,
+ NFSD_SERVER_STATS_PROC4OPS,
+ NFSD_SERVER_STATS_DONE,
+};
+
+/**
+ * nfsd_nl_server_stats_get_dumpit - dump NFS server statistics
+ * @skb: reply buffer
+ * @cb: netlink metadata and command arguments
+ *
+ * The server-stats object is emitted across one or more netlink messages.
+ * cb->args[0] tracks the current section and cb->args[1] the entry index
+ * within it, so a section that does not fit in the current message is resumed
+ * in the next one. The scalar counters are small and emitted once, in the
+ * first message; userspace merges the attributes from every 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 section = cb->args[0];
+ int idx = cb->args[1];
+ void *hdr;
+
+ if (section >= NFSD_SERVER_STATS_DONE)
+ return 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;
+
+ /* Scalar stats fit easily and are emitted in the first message. */
+ if (section == NFSD_SERVER_STATS_SCALARS) {
+ if (nfsd_nl_server_stats_scalars(skb, nn, statp))
+ goto err_cancel;
+ section = NFSD_SERVER_STATS_PROC2;
+ idx = 0;
+ }
+
+ /*
+ * Emit as many of the remaining sections as fit. A section returning
+ * -EMSGSIZE means the message is full: close it and resume from the
+ * same section/index on the next call with a fresh skb. Each entry is
+ * small enough to fit in a fresh skb, so forward progress is assured.
+ */
+ while (section < NFSD_SERVER_STATS_DONE) {
+ int ret = 0;
+
+ switch (section) {
+ case NFSD_SERVER_STATS_PROC2:
+ ret = nfsd_nl_server_stats_proc(skb, statp, prog, 2,
+ NFSD_A_SERVER_STATS_PROC2_OPS, &idx);
+ break;
+ case NFSD_SERVER_STATS_PROC3:
+ ret = nfsd_nl_server_stats_proc(skb, statp, prog, 3,
+ NFSD_A_SERVER_STATS_PROC3_OPS, &idx);
+ break;
+ case NFSD_SERVER_STATS_PROC4:
+ ret = nfsd_nl_server_stats_proc(skb, statp, prog, 4,
+ NFSD_A_SERVER_STATS_PROC4_OPS, &idx);
+ break;
+#ifdef CONFIG_NFSD_V4
+ case NFSD_SERVER_STATS_PROC4OPS:
+ ret = nfsd_nl_server_stats_nfs4ops(skb, nn, &idx);
+ break;
+#endif
+ }
+
+ if (ret == -EMSGSIZE)
+ goto out;
+ if (ret)
+ goto err_cancel;
+
+ section++;
+ idx = 0;
+ }
+
+out:
+ genlmsg_end(skb, hdr);
+ cb->args[0] = section;
+ cb->args[1] = idx;
+ return skb->len;
+
+err_cancel:
+ genlmsg_cancel(skb, hdr);
+ return -EMSGSIZE;
+}
+
int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type)
{
struct genlmsghdr *hdr;