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 void diff_stats(struct statinfo *, struct statinfo *, int);
188 static void unpause(int);
189 static void update_old_counters(struct statinfo *, struct statinfo *);
191 static time_t starttime;
193 #define PRNT_CALLS 0x0001
194 #define PRNT_RPC 0x0002
195 #define PRNT_NET 0x0004
196 #define PRNT_FH 0x0008
197 #define PRNT_RC 0x0010
198 #define PRNT_AUTO 0x1000
199 #define PRNT_V2 0x2000
200 #define PRNT_V3 0x4000
201 #define PRNT_V4 0x8000
202 #define PRNT_ALL 0x0fff
210 void usage(char *name)
212 printf("Usage: %s [OPTION]...\n\
214 -m, --mounts Show statistics on mounted NFS filesystems\n\
215 -c, --client Show NFS client statistics\n\
216 -s, --server Show NFS server statistics\n\
217 -2 Show NFS version 2 statistics\n\
218 -3 Show NFS version 3 statistics\n\
219 -4 Show NFS version 4 statistics\n\
220 -o [facility] Show statistics on particular facilities.\n\
221 nfs NFS protocol information\n\
222 rpc General RPC information\n\
223 net Network layer statistics\n\
224 fh Usage information on the server's file handle cache\n\
225 rc Usage information on the server's request reply cache\n\
226 all Select all of the above\n\
227 -v, --verbose, --all Same as '-o all'\n\
228 -r, --rpc Show RPC statistics\n\
229 -n, --nfs Show NFS statistics\n\
230 -Z[#], --sleep[=#] Collects stats until interrupted.\n\
231 Cumulative stats are then printed\n\
232 If # is provided, stats will be output every\n\
234 -S, --since file Shows difference between current stats and those in 'file'\n\
235 --version Show program version\n\
236 --help What you just did\n\
241 static struct option longopts[] =
243 { "acl", 0, 0, 'a' },
244 { "all", 0, 0, 'v' },
245 { "auto", 0, 0, '\3' },
246 { "client", 0, 0, 'c' },
247 { "mounted", 0, 0, 'm' },
248 { "nfs", 0, 0, 'n' },
249 { "rpc", 0, 0, 'r' },
250 { "server", 0, 0, 's' },
251 { "verbose", 0, 0, 'v' },
252 { "zero", 0, 0, 'z' },
253 { "help", 0, 0, '\1' },
254 { "version", 0, 0, '\2' },
255 { "sleep", 2, 0, 'Z' },
256 { "since", 1, 0, 'S' },
261 main(int argc, char **argv)
272 *serverfile = NFSSRVSTAT,
273 *clientfile = NFSCLTSTAT;
275 struct statinfo *serverinfo = srvinfo,
276 *serverinfo_tmp = srvinfo_old,
277 *clientinfo = cltinfo,
278 *clientinfo_tmp = cltinfo_old;
280 struct sigaction act = {
281 .sa_handler = unpause,
282 .sa_flags = SA_ONESHOT,
285 if ((progname = strrchr(argv[0], '/')))
290 while ((c = getopt_long(argc, argv, "234acmno:Z::S:vrsz\1\2", longopts, NULL)) != EOF) {
293 fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
299 opt_prt |= PRNT_CALLS;
302 if (!strcmp(optarg, "nfs"))
303 opt_prt |= PRNT_CALLS;
304 else if (!strcmp(optarg, "rpc"))
306 else if (!strcmp(optarg, "net"))
308 else if (!strcmp(optarg, "rc"))
310 else if (!strcmp(optarg, "fh"))
312 else if (!strcmp(optarg, "all"))
313 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
315 fprintf(stderr, "nfsstat: unknown category: "
323 sleep_time = atoi(optarg);
334 opt_prt |= versions[c - '2'];
340 opt_prt |= PRNT_AUTO;
349 fprintf(stderr, "nfsstat: zeroing of nfs statistics "
350 "not yet supported\n");
353 return mounts(MOUNTSFILE);
358 fprintf(stdout, "nfsstat: " VERSION "\n");
361 printf("Try `%s --help' for more information.\n", progname);
367 opt_srv = opt_clt = 1;
370 if (!(opt_srv + opt_clt))
371 opt_srv = opt_clt = 1;
372 if (!(opt_prt & 0xfff)) {
373 opt_prt |= PRNT_CALLS + PRNT_RPC;
375 if (!(opt_prt & 0xe000)) {
376 opt_prt |= PRNT_AUTO;
378 if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
380 "You requested file handle or request cache "
381 "statistics while using the -c option.\n"
382 "This information is available only for the NFS "
386 if (opt_since || opt_sleep) {
387 serverinfo = srvinfo_old;
388 serverinfo_tmp = srvinfo;
389 clientinfo = cltinfo_old;
390 clientinfo_tmp = cltinfo;
394 get_stats(serverfile, serverinfo, &opt_srv, opt_clt, 1);
396 get_stats(clientfile, clientinfo, &opt_clt, opt_srv, 0);
398 if (opt_sleep && !sleep_time) {
399 starttime = time(NULL);
400 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
401 if (sigaction(SIGINT, &act, NULL) != 0) {
402 fprintf(stderr, "Error: couldn't register for signal and pause.\n");
408 if (opt_since || opt_sleep) {
410 get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
411 diff_stats(serverinfo_tmp, serverinfo, 1);
414 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
415 diff_stats(clientinfo_tmp, clientinfo, 0);
421 get_stats(NFSSRVSTAT, serverinfo_tmp , &opt_srv, opt_clt, 1);
422 diff_stats(serverinfo_tmp, serverinfo, 1);
425 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
426 diff_stats(clientinfo_tmp, clientinfo, 0);
428 print_all_stats(opt_srv, opt_clt, opt_prt);
431 update_old_counters(clientinfo_tmp, clientinfo);
435 print_all_stats(opt_srv, opt_clt, opt_prt);
442 print_all_stats (int opt_srv, int opt_clt, int opt_prt)
444 print_server_stats(opt_srv, opt_prt);
445 print_client_stats(opt_clt, opt_prt);
449 print_server_stats(int opt_srv, int opt_prt)
452 if (opt_prt & PRNT_NET) {
455 "packets udp tcp tcpconn\n",
460 if (opt_prt & PRNT_RPC) {
463 "calls badcalls badauth badclnt xdrcall\n",
468 if (opt_prt & PRNT_RC) {
471 "hits misses nocache\n",
478 * 2.2 puts all fh-related info after the 'rc' header
479 * 2.4 puts all fh-related info after the 'fh' header, but relocates
480 * 'stale' to the start and swaps dir and nondir :-(
481 * We preseve the 2.2 order
483 if (opt_prt & PRNT_FH) {
484 if (get_stat_info("fh", srvinfo)) { /* >= 2.4 */
485 int t = srvfhinfo[3];
486 srvfhinfo[3]=srvfhinfo[4];
489 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
493 "lookup anon ncachedir ncachedir stale\n",
498 "lookup anon ncachedir ncachedir stale\n",
502 if (opt_prt & PRNT_CALLS) {
503 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
506 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
508 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
511 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
513 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
516 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
520 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
527 print_client_stats(int opt_clt, int opt_prt)
530 if (opt_prt & PRNT_NET) {
533 "packets udp tcp tcpconn\n",
538 if (opt_prt & PRNT_RPC) {
541 "calls retrans authrefrsh\n",
546 if (opt_prt & PRNT_CALLS) {
547 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
550 nfsv2name, cltproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
552 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
555 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
557 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
560 nfscltproc4name, cltproc4info + 1, sizeof(nfscltproc4name)/sizeof(char *)
567 get_stat_info(const char *sp, struct statinfo *statp)
571 for (ip = statp; ip->tag; ip++) {
572 if (!strcmp(sp, ip->tag))
580 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
585 for (i = 0; i < nr; i++)
586 printf("%s%-8u", i? " " : "", info[i]);
591 print_callstats(const char *hdr, const char **names,
592 unsigned int *info, unsigned int nr)
594 unsigned long long total;
595 unsigned long long pct;
599 for (i = 0, total = 0; i < nr; i++)
603 for (i = 0; i < nr; i += 6) {
604 for (j = 0; j < 6 && i + j < nr; j++)
605 printf("%-13s", names[i+j]);
607 for (j = 0; j < 6 && i + j < nr; j++) {
608 pct = ((unsigned long long) info[i+j]*100)/total;
609 printf("%-8u%3llu%% ", info[i+j], pct);
616 /* returns 0 on success, 1 otherwise */
618 parse_raw_statfile(const char *name, struct statinfo *statp)
620 char buffer[4096], *next;
623 /* Being unable to read e.g. the nfsd stats file shouldn't
624 * be a fatal error -- it usually means the module isn't loaded.
626 if ((fp = fopen(name, "r")) == NULL) {
627 // fprintf(stderr, "Warning: %s: %m\n", name);
631 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
633 char *sp, *line = buffer;
635 unsigned int total = 0;
637 if ((next = strchr(line, '\n')) != NULL)
639 if (!(sp = strtok(line, " \t")))
642 ip = get_stat_info(sp, statp);
648 for (i = 0; i < cnt; i++) {
649 if (!(sp = strtok(NULL, " \t")))
651 ip->valptr[i] = (unsigned int) strtoul(sp, NULL, 0);
652 total += ip->valptr[i];
654 ip->valptr[cnt - 1] = total;
661 /* returns 0 on success, 1 otherwise */
663 parse_pretty_statfile(const char *filename, struct statinfo *info)
665 int numvals, curindex, numconsumed, n, err = 1;
667 char buf[4096], *bufp, *fmt, is_proc;
671 if ((fp = fopen(filename, "r")) == NULL)
672 //err(2, "Unable to open statfile '%s'.\n", filename);
675 while (fgets(buf, sizeof(buf), fp) != NULL) {
676 for (ip = info; ip->tag; ip++) {
677 if (strcmp(buf, ip->label))
681 numvals = ip->nrvals - 1;
682 is_proc = strncmp("proc", ip->tag, 4) ? 0 : 1;
684 fmt = " %u %*u%% %n";
692 /* get (and skip) header */
693 if (fgets(buf, sizeof(buf), fp) == NULL) {
694 fprintf(stderr, "Failed to locate header after "
695 "label for '%s' in %s.\n",
699 /* no header -- done with this "tag" */
701 ip->valptr[numvals] = sum;
705 if (fgets(buf, sizeof(buf), fp) == NULL) {
706 fprintf(stderr, "Failed to locate stats after "
707 "header for '%s' in %s.\n",
712 for (; curindex < numvals; curindex++) {
713 n = sscanf(bufp, fmt, &ip->valptr[curindex],
721 sum += ip->valptr[curindex];
735 mounts(const char *name)
737 char buffer[4096], *next;
740 /* Being unable to read e.g. the nfsd stats file shouldn't
741 * be a fatal error -- it usually means the module isn't loaded.
743 if ((fp = fopen(name, "r")) == NULL) {
744 fprintf(stderr, "Warning: %s: %m\n", name);
748 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
750 char *device, *mount, *type, *flags;
752 if ((next = strchr(line, '\n')) != NULL)
755 if (!(device = strtok(line, " \t")))
758 if (!(mount = strtok(NULL, " \t")))
761 if (!(type = strtok(NULL, " \t")))
764 if (strcmp(type, "nfs") && strcmp(type,"nfs4")) {
768 if (!(flags = strtok(NULL, " \t")))
771 printf("%s from %s\n", mount, device);
772 printf(" Flags:\t%s\n", flags);
783 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
789 char *label = is_srv ? "Server" : "Client";
791 /* try to guess what type of stat file we're dealing with */
792 if ((fp = fopen(file, "r")) == NULL)
794 if (fgets(buf, 10, fp) == NULL)
796 if (!strncmp(buf, "net ", 4)) {
797 /* looks like raw client stats */
799 fprintf(stderr, "Warning: no server info present in "
800 "raw client stats file.\n");
803 err = parse_raw_statfile(file, info);
804 } else if (!strncmp(buf, "rc ", 3)) {
805 /* looks like raw server stats */
807 fprintf(stderr, "Warning: no client info present in "
808 "raw server stats file.\n");
811 err = parse_raw_statfile(file, info);
813 /* looks like pretty client and server stats */
814 err = parse_pretty_statfile(file, info);
820 fprintf(stderr, "Error: No %s Stats (%s: %m). \n",
829 * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
830 * denotes the number of subsequent entries. statinfo value arrays contain an additional
831 * field at the end which contains the sum of all previous elements in the array -- so,
832 * there are stats if the sum's greater than the entry-count.
835 has_stats(const unsigned int *info)
837 return (info[0] && info[info[0] + 1] > info[0]);
841 * take the difference of each individual stat value in 'new' and 'old'
842 * and store the results back into 'new'
845 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
847 int i, j, nodiff_first_index, should_diff;
850 * Different stat types have different formats in the /proc
851 * files: for the proc2/3/4-type stats, the first entry has
852 * the total number of subsequent entries; one does not want
853 * to diff that first entry. The other stat types aren't like
854 * this. So, we diff a given entry if it's not of one of the
855 * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
856 * the first entry ("j" > 0).
858 nodiff_first_index = 2 + (2 * is_srv);
860 for (i = 0; old[i].tag; i++) {
861 for (j = 0; j < new[i].nrvals; j++) {
862 should_diff = (i < nodiff_first_index || j > 0);
864 new[i].valptr[j] -= old[i].valptr[j];
868 * Make sure that the "totals" entry (last value in
869 * each stat array) for the procX-type stats has the
870 * "numentries" entry's (first value in procX-type
871 * stat arrays) constant value added-back after the
872 * diff -- i.e., it should always be included in the
875 if (!strncmp("proc", new[i].tag, 4) && old[i].valptr[0])
876 new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
884 int minutes, seconds;
887 endtime = time(NULL);
888 time_diff = difftime(endtime, starttime);
889 minutes = time_diff / 60;
890 seconds = (int)time_diff % 60;
891 printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);
895 update_old_counters(struct statinfo *new, struct statinfo *old)
898 for (z = 0; old[z].tag; z++)
899 for (i = 0; i <= old[z].nrvals; i++)
900 old[z].valptr[i] += new[z].valptr[i];