2 * nfsstat.c Output NFS statistics
4 * Copyright (C) 1995-2005 Olaf Kirch <okir@suse.de>
11 #define NFSSRVSTAT "/proc/net/rpc/nfsd"
12 #define NFSCLTSTAT "/proc/net/rpc/nfs"
14 #define MOUNTSFILE "/proc/mounts"
28 static unsigned int srvproc2info[20], srvproc2info_old[20]; /* NFSv2 call counts ([0] == 18) */
29 static unsigned int cltproc2info[20], cltproc2info_old[20]; /* NFSv2 call counts ([0] == 18) */
30 static unsigned int srvproc3info[24], srvproc3info_old[24]; /* NFSv3 call counts ([0] == 22) */
31 static unsigned int cltproc3info[24], cltproc3info_old[24]; /* NFSv3 call counts ([0] == 22) */
32 static unsigned int srvproc4info[4], srvproc4info_old[4]; /* NFSv4 call counts ([0] == 2) */
33 static unsigned int cltproc4info[37], cltproc4info_old[37]; /* NFSv4 call counts ([0] == 35) */
34 static unsigned int srvproc4opsinfo[42], srvproc4opsinfo_old[42]; /* NFSv4 call counts ([0] == 40) */
35 static unsigned int srvnetinfo[5], srvnetinfo_old[5]; /* 0 # of received packets
40 static unsigned int cltnetinfo[5], cltnetinfo_old[5]; /* 0 # of received packets
46 static unsigned int srvrpcinfo[6], srvrpcinfo_old[6]; /* 0 total # of RPC calls
47 * 1 total # of bad calls
49 * 3 authentication failed
52 static unsigned int cltrpcinfo[4], cltrpcinfo_old[4]; /* 0 total # of RPC calls
53 * 1 retransmitted calls
57 static unsigned int srvrcinfo[9], srvrcinfo_old[9]; /* 0 repcache hits
60 * (for pre-2.4 kernels:)
63 * 5 noncached non-directories
64 * 6 noncached directories
68 static unsigned int srvfhinfo[7], srvfhinfo_old[7]; /* (for kernels >= 2.4.0)
72 * 3 noncached directories
73 * 4 noncached non-directories
74 * leave hole to relocate stale for order
78 static const char * nfsv2name[18] = {
79 "null", "getattr", "setattr", "root", "lookup", "readlink",
80 "read", "wrcache", "write", "create", "remove", "rename",
81 "link", "symlink", "mkdir", "rmdir", "readdir", "fsstat"
84 static const char * nfsv3name[22] = {
85 "null", "getattr", "setattr", "lookup", "access", "readlink",
86 "read", "write", "create", "mkdir", "symlink", "mknod",
87 "remove", "rmdir", "rename", "link", "readdir", "readdirplus",
88 "fsstat", "fsinfo", "pathconf", "commit"
91 static const char * nfssrvproc4name[2] = {
96 static const char * nfscltproc4name[35] = {
97 "null", "read", "write", "commit", "open", "open_conf",
98 "open_noat", "open_dgrd", "close", "setattr", "fsinfo", "renew",
99 "setclntid", "confirm", "lock",
100 "lockt", "locku", "access", "getattr", "lookup", "lookup_root",
101 "remove", "rename", "link", "symlink", "create", "pathconf",
102 "statfs", "readlink", "readdir", "server_caps", "delegreturn", "getacl",
103 "setacl", "fs_locations"
106 static const char * nfssrvproc4opname[40] = {
107 "op0-unused", "op1-unused", "op2-future", "access", "close", "commit",
108 "create", "delegpurge", "delegreturn", "getattr", "getfh", "link",
109 "lock", "lockt", "locku", "lookup", "lookup_root", "nverify",
110 "open", "openattr", "open_conf", "open_dgrd", "putfh", "putpubfh",
111 "putrootfh", "read", "readdir", "readlink", "remove", "rename",
112 "renew", "restorefh", "savefh", "secinfo", "setattr", "setcltid",
113 "setcltidconf", "verify", "write", "rellockowner"
116 #define LABEL_srvnet "Server packet stats:\n"
117 #define LABEL_srvrpc "Server rpc stats:\n"
118 #define LABEL_srvrc "Server reply cache:\n"
119 #define LABEL_srvfh "Server file handle cache:\n"
120 #define LABEL_srvproc2 "Server nfs v2:\n"
121 #define LABEL_srvproc3 "Server nfs v3:\n"
122 #define LABEL_srvproc4 "Server nfs v4:\n"
123 #define LABEL_srvproc4ops "Server nfs v4 operations:\n"
124 #define LABEL_cltnet "Client packet stats:\n"
125 #define LABEL_cltrpc "Client rpc stats:\n"
126 #define LABEL_cltproc2 "Client nfs v2:\n"
127 #define LABEL_cltproc3 "Client nfs v3:\n"
128 #define LABEL_cltproc4 "Client nfs v4:\n"
130 typedef struct statinfo {
134 unsigned int * valptr;
138 * We now build the arrays of statinfos using macros, which will make it easier
139 * to add new variables for --sleep. e.g., SRV(net) expands into the struct
140 * statinfo: { "net", "Server packet stats:\n", 5, srvnetinfo }
142 #define ARRAYSIZE(x) sizeof(x)/sizeof(*x)
143 #define STATINFO(k, t, s...) { #t, LABEL_##k##t, ARRAYSIZE(k##t##info##s), k##t##info##s }
144 #define SRV(t, s...) STATINFO(srv, t, s)
145 #define CLT(t, s...) STATINFO(clt, t, s)
146 #define DECLARE_SRV(n, s...) static statinfo n##s[] = { \
155 { NULL, NULL, 0, NULL }\
157 #define DECLARE_CLT(n, s...) static statinfo n##s[] = { \
163 { NULL, NULL, 0, NULL }\
165 DECLARE_SRV(srvinfo);
166 DECLARE_SRV(srvinfo, _old);
167 DECLARE_CLT(cltinfo);
168 DECLARE_CLT(cltinfo, _old);
170 static void print_all_stats(int, int, int);
171 static void print_server_stats(int, int);
172 static void print_client_stats(int, int);
173 static void print_numbers(const char *, unsigned int *,
175 static void print_callstats(const char *, const char **,
176 unsigned int *, unsigned int);
177 static int parse_raw_statfile(const char *, struct statinfo *);
178 static int parse_pretty_statfile(const char *, struct statinfo *);
180 static statinfo *get_stat_info(const char *, struct statinfo *);
182 static int mounts(const char *);
184 static void get_stats(const char *, struct statinfo *, int *, int,
186 static int has_stats(const unsigned int *);
187 static int has_rpcstats(const unsigned int *, int);
188 static void diff_stats(struct statinfo *, struct statinfo *, int);
189 static void unpause(int);
190 static void update_old_counters(struct statinfo *, struct statinfo *);
192 static time_t starttime;
194 #define PRNT_CALLS 0x0001
195 #define PRNT_RPC 0x0002
196 #define PRNT_NET 0x0004
197 #define PRNT_FH 0x0008
198 #define PRNT_RC 0x0010
199 #define PRNT_AUTO 0x1000
200 #define PRNT_V2 0x2000
201 #define PRNT_V3 0x4000
202 #define PRNT_V4 0x8000
203 #define PRNT_ALL 0x0fff
211 void usage(char *name)
213 printf("Usage: %s [OPTION]...\n\
215 -m, --mounts Show statistics on mounted NFS filesystems\n\
216 -c, --client Show NFS client statistics\n\
217 -s, --server Show NFS server statistics\n\
218 -2 Show NFS version 2 statistics\n\
219 -3 Show NFS version 3 statistics\n\
220 -4 Show NFS version 4 statistics\n\
221 -o [facility] Show statistics on particular facilities.\n\
222 nfs NFS protocol information\n\
223 rpc General RPC information\n\
224 net Network layer statistics\n\
225 fh Usage information on the server's file handle cache\n\
226 rc Usage information on the server's request reply cache\n\
227 all Select all of the above\n\
228 -v, --verbose, --all Same as '-o all'\n\
229 -r, --rpc Show RPC statistics\n\
230 -n, --nfs Show NFS statistics\n\
231 -Z[#], --sleep[=#] Collects stats until interrupted.\n\
232 Cumulative stats are then printed\n\
233 If # is provided, stats will be output every\n\
235 -S, --since file Shows difference between current stats and those in 'file'\n\
236 --version Show program version\n\
237 --help What you just did\n\
242 static struct option longopts[] =
244 { "acl", 0, 0, 'a' },
245 { "all", 0, 0, 'v' },
246 { "auto", 0, 0, '\3' },
247 { "client", 0, 0, 'c' },
248 { "mounted", 0, 0, 'm' },
249 { "nfs", 0, 0, 'n' },
250 { "rpc", 0, 0, 'r' },
251 { "server", 0, 0, 's' },
252 { "verbose", 0, 0, 'v' },
253 { "zero", 0, 0, 'z' },
254 { "help", 0, 0, '\1' },
255 { "version", 0, 0, '\2' },
256 { "sleep", 2, 0, 'Z' },
257 { "since", 1, 0, 'S' },
263 main(int argc, char **argv)
273 *serverfile = NFSSRVSTAT,
274 *clientfile = NFSCLTSTAT;
276 struct statinfo *serverinfo = srvinfo,
277 *serverinfo_tmp = srvinfo_old,
278 *clientinfo = cltinfo,
279 *clientinfo_tmp = cltinfo_old;
281 struct sigaction act = {
282 .sa_handler = unpause,
283 .sa_flags = SA_ONESHOT,
286 if ((progname = strrchr(argv[0], '/')))
291 while ((c = getopt_long(argc, argv, "234acmno:Z::S:vrsz\1\2", longopts, NULL)) != EOF) {
294 fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
300 opt_prt |= PRNT_CALLS;
303 if (!strcmp(optarg, "nfs"))
304 opt_prt |= PRNT_CALLS;
305 else if (!strcmp(optarg, "rpc"))
307 else if (!strcmp(optarg, "net"))
309 else if (!strcmp(optarg, "rc"))
311 else if (!strcmp(optarg, "fh"))
313 else if (!strcmp(optarg, "all"))
314 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
316 fprintf(stderr, "nfsstat: unknown category: "
324 sleep_time = atoi(optarg);
335 opt_prt |= versions[c - '2'];
341 opt_prt |= PRNT_AUTO;
350 fprintf(stderr, "nfsstat: zeroing of nfs statistics "
351 "not yet supported\n");
354 return mounts(MOUNTSFILE);
359 fprintf(stdout, "nfsstat: " VERSION "\n");
362 printf("Try `%s --help' for more information.\n", progname);
368 opt_srv = opt_clt = 1;
371 if (!(opt_srv + opt_clt))
372 opt_srv = opt_clt = 1;
373 if (!(opt_prt & 0xfff)) {
374 opt_prt |= PRNT_CALLS + PRNT_RPC;
376 if (!(opt_prt & 0xe000)) {
377 opt_prt |= PRNT_AUTO;
379 if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
381 "You requested file handle or request cache "
382 "statistics while using the -c option.\n"
383 "This information is available only for the NFS "
387 if (opt_since || opt_sleep) {
388 serverinfo = srvinfo_old;
389 serverinfo_tmp = srvinfo;
390 clientinfo = cltinfo_old;
391 clientinfo_tmp = cltinfo;
395 get_stats(serverfile, serverinfo, &opt_srv, opt_clt, 1);
397 get_stats(clientfile, clientinfo, &opt_clt, opt_srv, 0);
399 if (opt_sleep && !sleep_time) {
400 starttime = time(NULL);
401 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
402 if (sigaction(SIGINT, &act, NULL) != 0) {
403 fprintf(stderr, "Error: couldn't register for signal and pause.\n");
409 if (opt_since || opt_sleep) {
411 get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
412 diff_stats(serverinfo_tmp, serverinfo, 1);
415 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
416 diff_stats(clientinfo_tmp, clientinfo, 0);
422 get_stats(NFSSRVSTAT, serverinfo_tmp , &opt_srv, opt_clt, 1);
423 diff_stats(serverinfo_tmp, serverinfo, 1);
426 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
427 diff_stats(clientinfo_tmp, clientinfo, 0);
429 print_all_stats(opt_srv, opt_clt, opt_prt);
432 update_old_counters(clientinfo_tmp, clientinfo);
436 print_all_stats(opt_srv, opt_clt, opt_prt);
443 print_all_stats (int opt_srv, int opt_clt, int opt_prt)
445 print_server_stats(opt_srv, opt_prt);
446 print_client_stats(opt_clt, opt_prt);
450 print_server_stats(int opt_srv, int opt_prt)
453 if (opt_prt & PRNT_NET) {
456 "packets udp tcp tcpconn\n",
461 if (opt_prt & PRNT_RPC) {
462 if (!opt_sleep && !has_rpcstats(srvrpcinfo, 5)) {
465 "calls badcalls badauth badclnt xdrcall\n",
471 if (opt_prt & PRNT_RC) {
474 "hits misses nocache\n",
481 * 2.2 puts all fh-related info after the 'rc' header
482 * 2.4 puts all fh-related info after the 'fh' header, but relocates
483 * 'stale' to the start and swaps dir and nondir :-(
484 * We preseve the 2.2 order
486 if (opt_prt & PRNT_FH) {
487 if (get_stat_info("fh", srvinfo)) { /* >= 2.4 */
488 int t = srvfhinfo[3];
489 srvfhinfo[3]=srvfhinfo[4];
492 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
496 "lookup anon ncachedir ncachedir stale\n",
501 "lookup anon ncachedir ncachedir stale\n",
505 if (opt_prt & PRNT_CALLS) {
506 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
509 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
511 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
514 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
516 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
519 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
523 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
530 print_client_stats(int opt_clt, int opt_prt)
533 if (opt_prt & PRNT_NET) {
536 "packets udp tcp tcpconn\n",
541 if (opt_prt & PRNT_RPC) {
542 if (!opt_sleep && !has_rpcstats(cltrpcinfo, 3)) {
545 "calls retrans authrefrsh\n",
551 if (opt_prt & PRNT_CALLS) {
552 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
555 nfsv2name, cltproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
557 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
560 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
562 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
565 nfscltproc4name, cltproc4info + 1, sizeof(nfscltproc4name)/sizeof(char *)
572 get_stat_info(const char *sp, struct statinfo *statp)
576 for (ip = statp; ip->tag; ip++) {
577 if (!strcmp(sp, ip->tag))
585 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
590 for (i = 0; i < nr; i++)
591 printf("%s%-8u", i? " " : "", info[i]);
596 print_callstats(const char *hdr, const char **names,
597 unsigned int *info, unsigned int nr)
599 unsigned long long total;
600 unsigned long long pct;
604 for (i = 0, total = 0; i < nr; i++)
608 for (i = 0; i < nr; i += 6) {
609 for (j = 0; j < 6 && i + j < nr; j++)
610 printf("%-13s", names[i+j]);
612 for (j = 0; j < 6 && i + j < nr; j++) {
613 pct = ((unsigned long long) info[i+j]*100)/total;
614 printf("%-8u%3llu%% ", info[i+j], pct);
621 /* returns 0 on success, 1 otherwise */
623 parse_raw_statfile(const char *name, struct statinfo *statp)
625 char buffer[4096], *next;
628 /* Being unable to read e.g. the nfsd stats file shouldn't
629 * be a fatal error -- it usually means the module isn't loaded.
631 if ((fp = fopen(name, "r")) == NULL) {
632 // fprintf(stderr, "Warning: %s: %m\n", name);
636 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
638 char *sp, *line = buffer;
640 unsigned int total = 0;
642 if ((next = strchr(line, '\n')) != NULL)
644 if (!(sp = strtok(line, " \t")))
647 ip = get_stat_info(sp, statp);
653 for (i = 0; i < cnt; i++) {
654 if (!(sp = strtok(NULL, " \t")))
656 ip->valptr[i] = (unsigned int) strtoul(sp, NULL, 0);
657 total += ip->valptr[i];
659 ip->valptr[cnt - 1] = total;
666 /* returns 0 on success, 1 otherwise */
668 parse_pretty_statfile(const char *filename, struct statinfo *info)
670 int numvals, curindex, numconsumed, n, err = 1;
672 char buf[4096], *bufp, *fmt, is_proc;
676 if ((fp = fopen(filename, "r")) == NULL)
677 //err(2, "Unable to open statfile '%s'.\n", filename);
680 while (fgets(buf, sizeof(buf), fp) != NULL) {
681 for (ip = info; ip->tag; ip++) {
682 if (strcmp(buf, ip->label))
686 numvals = ip->nrvals - 1;
687 is_proc = strncmp("proc", ip->tag, 4) ? 0 : 1;
689 fmt = " %u %*u%% %n";
697 /* get (and skip) header */
698 if (fgets(buf, sizeof(buf), fp) == NULL) {
699 fprintf(stderr, "Failed to locate header after "
700 "label for '%s' in %s.\n",
704 /* no header -- done with this "tag" */
706 ip->valptr[numvals] = sum;
710 if (fgets(buf, sizeof(buf), fp) == NULL) {
711 fprintf(stderr, "Failed to locate stats after "
712 "header for '%s' in %s.\n",
717 for (; curindex < numvals; curindex++) {
718 n = sscanf(bufp, fmt, &ip->valptr[curindex],
726 sum += ip->valptr[curindex];
740 mounts(const char *name)
742 char buffer[4096], *next;
745 /* Being unable to read e.g. the nfsd stats file shouldn't
746 * be a fatal error -- it usually means the module isn't loaded.
748 if ((fp = fopen(name, "r")) == NULL) {
749 fprintf(stderr, "Warning: %s: %m\n", name);
753 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
755 char *device, *mount, *type, *flags;
757 if ((next = strchr(line, '\n')) != NULL)
760 if (!(device = strtok(line, " \t")))
763 if (!(mount = strtok(NULL, " \t")))
766 if (!(type = strtok(NULL, " \t")))
769 if (strcmp(type, "nfs") && strcmp(type,"nfs4")) {
773 if (!(flags = strtok(NULL, " \t")))
776 printf("%s from %s\n", mount, device);
777 printf(" Flags:\t%s\n", flags);
788 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
794 char *label = is_srv ? "Server" : "Client";
796 /* try to guess what type of stat file we're dealing with */
797 if ((fp = fopen(file, "r")) == NULL)
799 if (fgets(buf, 10, fp) == NULL)
801 if (!strncmp(buf, "net ", 4)) {
802 /* looks like raw client stats */
804 fprintf(stderr, "Warning: no server info present in "
805 "raw client stats file.\n");
808 err = parse_raw_statfile(file, info);
809 } else if (!strncmp(buf, "rc ", 3)) {
810 /* looks like raw server stats */
812 fprintf(stderr, "Warning: no client info present in "
813 "raw server stats file.\n");
816 err = parse_raw_statfile(file, info);
818 /* looks like pretty client and server stats */
819 err = parse_pretty_statfile(file, info);
825 fprintf(stderr, "Error: No %s Stats (%s: %m). \n",
834 * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
835 * denotes the number of subsequent entries. statinfo value arrays contain an additional
836 * field at the end which contains the sum of all previous elements in the array -- so,
837 * there are stats if the sum's greater than the entry-count.
840 has_stats(const unsigned int *info)
842 return (info[0] && info[info[0] + 1] > info[0]);
845 has_rpcstats(const unsigned int *info, int size)
849 for (i=0, cnt=0; i < size; i++)
855 * take the difference of each individual stat value in 'new' and 'old'
856 * and store the results back into 'new'
859 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
861 int i, j, nodiff_first_index, should_diff;
864 * Different stat types have different formats in the /proc
865 * files: for the proc2/3/4-type stats, the first entry has
866 * the total number of subsequent entries; one does not want
867 * to diff that first entry. The other stat types aren't like
868 * this. So, we diff a given entry if it's not of one of the
869 * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
870 * the first entry ("j" > 0).
872 nodiff_first_index = 2 + (2 * is_srv);
874 for (i = 0; old[i].tag; i++) {
875 for (j = 0; j < new[i].nrvals; j++) {
876 should_diff = (i < nodiff_first_index || j > 0);
878 new[i].valptr[j] -= old[i].valptr[j];
882 * Make sure that the "totals" entry (last value in
883 * each stat array) for the procX-type stats has the
884 * "numentries" entry's (first value in procX-type
885 * stat arrays) constant value added-back after the
886 * diff -- i.e., it should always be included in the
889 if (!strncmp("proc", new[i].tag, 4) && old[i].valptr[0])
890 new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
898 int minutes, seconds;
901 endtime = time(NULL);
902 time_diff = difftime(endtime, starttime);
903 minutes = time_diff / 60;
904 seconds = (int)time_diff % 60;
905 printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);
909 update_old_counters(struct statinfo *new, struct statinfo *old)
912 for (z = 0; old[z].tag; z++)
913 for (i = 0; i <= old[z].nrvals; i++)
914 old[z].valptr[i] += new[z].valptr[i];