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_numbers(const char *, unsigned int *,
172 static void print_callstats(const char *, const char **,
173 unsigned int *, unsigned int);
174 static int parse_raw_statfile(const char *, struct statinfo *);
175 static int parse_pretty_statfile(const char *, struct statinfo *);
177 static statinfo *get_stat_info(const char *, struct statinfo *);
179 static int mounts(const char *);
181 static void get_stats(const char *, struct statinfo *, int *, int,
183 static int has_stats(const unsigned int *);
184 static void diff_stats(struct statinfo *, struct statinfo *, int);
185 static void unpause(int);
187 static time_t starttime;
189 #define PRNT_CALLS 0x0001
190 #define PRNT_RPC 0x0002
191 #define PRNT_NET 0x0004
192 #define PRNT_FH 0x0008
193 #define PRNT_RC 0x0010
194 #define PRNT_AUTO 0x1000
195 #define PRNT_V2 0x2000
196 #define PRNT_V3 0x4000
197 #define PRNT_V4 0x8000
198 #define PRNT_ALL 0x0fff
206 void usage(char *name)
208 printf("Usage: %s [OPTION]...\n\
210 -m, --mounts\t\tShow statistics on mounted NFS filesystems\n\
211 -c, --client\t\tShow NFS client statistics\n\
212 -s, --server\t\tShow NFS server statistics\n\
213 -2\t\t\tShow NFS version 2 statistics\n\
214 -3\t\t\tShow NFS version 3 statistics\n\
215 -4\t\t\tShow NFS version 4 statistics\n\
216 -o [facility]\t\tShow statistics on particular facilities.\n\
217 nfs\tNFS protocol information\n\
218 rpc\tGeneral RPC information\n\
219 net\tNetwork layer statistics\n\
220 fh\t\tUsage information on the server's file handle cache\n\
221 rc\t\tUsage information on the server's request reply cache\n\
222 all\tSelect all of the above\n\
223 -v, --verbose, --all\tSame as '-o all'\n\
224 -r, --rpc\t\tShow RPC statistics\n\
225 -n, --nfs\t\tShow NFS statistics\n\
226 -Z, --sleep\t\tSaves stats, pauses, diffs current and saved\n\
227 -S, --since file\tShows difference between current stats and those in 'file'\n\
228 --version\t\tShow program version\n\
229 --help\t\tWhat you just did\n\
234 static struct option longopts[] =
236 { "acl", 0, 0, 'a' },
237 { "all", 0, 0, 'v' },
238 { "auto", 0, 0, '\3' },
239 { "client", 0, 0, 'c' },
240 { "mounted", 0, 0, 'm' },
241 { "nfs", 0, 0, 'n' },
242 { "rpc", 0, 0, 'r' },
243 { "server", 0, 0, 's' },
244 { "verbose", 0, 0, 'v' },
245 { "zero", 0, 0, 'z' },
246 { "help", 0, 0, '\1' },
247 { "version", 0, 0, '\2' },
248 { "sleep", 0, 0, 'Z' },
249 { "since", 1, 0, 'S' },
254 main(int argc, char **argv)
264 *serverfile = NFSSRVSTAT,
265 *clientfile = NFSCLTSTAT;
267 struct statinfo *serverinfo = srvinfo,
268 *serverinfo_tmp = srvinfo_old,
269 *clientinfo = cltinfo,
270 *clientinfo_tmp = cltinfo_old;
272 struct sigaction act = {
273 .sa_handler = unpause,
274 .sa_flags = SA_ONESHOT,
277 if ((progname = strrchr(argv[0], '/')))
282 while ((c = getopt_long(argc, argv, "234acmno:ZS:vrsz\1\2", longopts, NULL)) != EOF) {
285 fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
291 opt_prt |= PRNT_CALLS;
294 if (!strcmp(optarg, "nfs"))
295 opt_prt |= PRNT_CALLS;
296 else if (!strcmp(optarg, "rpc"))
298 else if (!strcmp(optarg, "net"))
300 else if (!strcmp(optarg, "rc"))
302 else if (!strcmp(optarg, "fh"))
304 else if (!strcmp(optarg, "all"))
305 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
307 fprintf(stderr, "nfsstat: unknown category: "
323 opt_prt |= versions[c - '2'];
329 opt_prt |= PRNT_AUTO;
338 fprintf(stderr, "nfsstat: zeroing of nfs statistics "
339 "not yet supported\n");
342 return mounts(MOUNTSFILE);
347 fprintf(stdout, "nfsstat: " VERSION "\n");
350 printf("Try `%s --help' for more information.\n", progname);
356 opt_srv = opt_clt = 1;
359 if (!(opt_srv + opt_clt))
360 opt_srv = opt_clt = 1;
361 if (!(opt_prt & 0xfff)) {
362 opt_prt |= PRNT_CALLS + PRNT_RPC;
364 if (!(opt_prt & 0xe000)) {
365 opt_prt |= PRNT_AUTO;
367 if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
369 "You requested file handle or request cache "
370 "statistics while using the -c option.\n"
371 "This information is available only for the NFS "
375 if (opt_since || opt_sleep) {
376 serverinfo = srvinfo_old;
377 serverinfo_tmp = srvinfo;
378 clientinfo = cltinfo_old;
379 clientinfo_tmp = cltinfo;
383 get_stats(serverfile, serverinfo, &opt_srv, opt_clt, 1);
385 get_stats(clientfile, clientinfo, &opt_clt, opt_srv, 0);
388 starttime = time(NULL);
389 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
390 if (sigaction(SIGINT, &act, NULL) != 0) {
391 fprintf(stderr, "Error: couldn't register for signal and pause.\n");
397 if (opt_since || opt_sleep) {
399 get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
400 diff_stats(serverinfo_tmp, serverinfo, 1);
403 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
404 diff_stats(clientinfo_tmp, clientinfo, 0);
409 if (opt_prt & PRNT_NET) {
412 "packets udp tcp tcpconn\n",
417 if (opt_prt & PRNT_RPC) {
420 "calls badcalls badauth badclnt xdrcall\n",
425 if (opt_prt & PRNT_RC) {
428 "hits misses nocache\n",
435 * 2.2 puts all fh-related info after the 'rc' header
436 * 2.4 puts all fh-related info after the 'fh' header, but relocates
437 * 'stale' to the start and swaps dir and nondir :-(
438 * We preseve the 2.2 order
440 if (opt_prt & PRNT_FH) {
441 if (get_stat_info("fh", srvinfo)) { /* >= 2.4 */
442 int t = srvfhinfo[3];
443 srvfhinfo[3]=srvfhinfo[4];
446 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
450 "lookup anon ncachedir ncachedir stale\n",
455 "lookup anon ncachedir ncachedir stale\n",
459 if (opt_prt & PRNT_CALLS) {
460 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
463 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
465 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
468 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
470 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
473 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
477 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
484 if (opt_prt & PRNT_NET) {
487 "packets udp tcp tcpconn\n",
492 if (opt_prt & PRNT_RPC) {
495 "calls retrans authrefrsh\n",
500 if (opt_prt & PRNT_CALLS) {
501 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
504 nfsv2name, cltproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
506 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
509 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
511 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
514 nfscltproc4name, cltproc4info + 1, sizeof(nfscltproc4name)/sizeof(char *)
523 get_stat_info(const char *sp, struct statinfo *statp)
527 for (ip = statp; ip->tag; ip++) {
528 if (!strcmp(sp, ip->tag))
536 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
541 for (i = 0; i < nr; i++)
542 printf("%s%-8u", i? " " : "", info[i]);
547 print_callstats(const char *hdr, const char **names,
548 unsigned int *info, unsigned int nr)
550 unsigned long long total;
551 unsigned long long pct;
555 for (i = 0, total = 0; i < nr; i++)
559 for (i = 0; i < nr; i += 6) {
560 for (j = 0; j < 6 && i + j < nr; j++)
561 printf("%-13s", names[i+j]);
563 for (j = 0; j < 6 && i + j < nr; j++) {
564 pct = ((unsigned long long) info[i+j]*100)/total;
565 printf("%-8u%3llu%% ", info[i+j], pct);
572 /* returns 0 on success, 1 otherwise */
574 parse_raw_statfile(const char *name, struct statinfo *statp)
576 char buffer[4096], *next;
579 /* Being unable to read e.g. the nfsd stats file shouldn't
580 * be a fatal error -- it usually means the module isn't loaded.
582 if ((fp = fopen(name, "r")) == NULL) {
583 // fprintf(stderr, "Warning: %s: %m\n", name);
587 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
589 char *sp, *line = buffer;
591 unsigned int total = 0;
593 if ((next = strchr(line, '\n')) != NULL)
595 if (!(sp = strtok(line, " \t")))
598 ip = get_stat_info(sp, statp);
604 for (i = 0; i < cnt; i++) {
605 if (!(sp = strtok(NULL, " \t")))
607 ip->valptr[i] = (unsigned int) strtoul(sp, NULL, 0);
608 total += ip->valptr[i];
610 ip->valptr[cnt - 1] = total;
617 /* returns 0 on success, 1 otherwise */
619 parse_pretty_statfile(const char *filename, struct statinfo *info)
621 int numvals, curindex, numconsumed, n, err = 1;
623 char buf[4096], *bufp, *fmt, is_proc;
627 if ((fp = fopen(filename, "r")) == NULL)
628 //err(2, "Unable to open statfile '%s'.\n", filename);
631 while (fgets(buf, sizeof(buf), fp) != NULL) {
632 for (ip = info; ip->tag; ip++) {
633 if (strcmp(buf, ip->label))
637 numvals = ip->nrvals - 1;
638 is_proc = strncmp("proc", ip->tag, 4) ? 0 : 1;
640 fmt = " %u %*u%% %n";
648 /* get (and skip) header */
649 if (fgets(buf, sizeof(buf), fp) == NULL) {
650 fprintf(stderr, "Failed to locate header after "
651 "label for '%s' in %s.\n",
655 /* no header -- done with this "tag" */
657 ip->valptr[numvals] = sum;
661 if (fgets(buf, sizeof(buf), fp) == NULL) {
662 fprintf(stderr, "Failed to locate stats after "
663 "header for '%s' in %s.\n",
668 for (; curindex < numvals; curindex++) {
669 n = sscanf(bufp, fmt, &ip->valptr[curindex],
677 sum += ip->valptr[curindex];
691 mounts(const char *name)
693 char buffer[4096], *next;
696 /* Being unable to read e.g. the nfsd stats file shouldn't
697 * be a fatal error -- it usually means the module isn't loaded.
699 if ((fp = fopen(name, "r")) == NULL) {
700 fprintf(stderr, "Warning: %s: %m\n", name);
704 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
706 char *device, *mount, *type, *flags;
708 if ((next = strchr(line, '\n')) != NULL)
711 if (!(device = strtok(line, " \t")))
714 if (!(mount = strtok(NULL, " \t")))
717 if (!(type = strtok(NULL, " \t")))
720 if (strcmp(type, "nfs") && strcmp(type,"nfs4")) {
724 if (!(flags = strtok(NULL, " \t")))
727 printf("%s from %s\n", mount, device);
728 printf(" Flags:\t%s\n", flags);
739 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
745 char *label = is_srv ? "Server" : "Client";
747 /* try to guess what type of stat file we're dealing with */
748 if ((fp = fopen(file, "r")) == NULL)
750 if (fgets(buf, 10, fp) == NULL)
752 if (!strncmp(buf, "net ", 4)) {
753 /* looks like raw client stats */
755 fprintf(stderr, "Warning: no server info present in "
756 "raw client stats file.\n");
759 err = parse_raw_statfile(file, info);
760 } else if (!strncmp(buf, "rc ", 3)) {
761 /* looks like raw server stats */
763 fprintf(stderr, "Warning: no client info present in "
764 "raw server stats file.\n");
767 err = parse_raw_statfile(file, info);
769 /* looks like pretty client and server stats */
770 err = parse_pretty_statfile(file, info);
776 fprintf(stderr, "Error: No %s Stats (%s: %m). \n",
785 * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
786 * denotes the number of subsequent entries. statinfo value arrays contain an additional
787 * field at the end which contains the sum of all previous elements in the array -- so,
788 * there are stats if the sum's greater than the entry-count.
791 has_stats(const unsigned int *info)
793 return (info[0] && info[info[0] + 1] > info[0]);
797 * take the difference of each individual stat value in 'new' and 'old'
798 * and store the results back into 'new'
801 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
803 int i, j, nodiff_first_index, should_diff;
806 * Different stat types have different formats in the /proc
807 * files: for the proc2/3/4-type stats, the first entry has
808 * the total number of subsequent entries; one does not want
809 * to diff that first entry. The other stat types aren't like
810 * this. So, we diff a given entry if it's not of one of the
811 * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
812 * the first entry ("j" > 0).
814 nodiff_first_index = 2 + (2 * is_srv);
816 for (i = 0; old[i].tag; i++) {
817 for (j = 0; j < new[i].nrvals; j++) {
818 should_diff = (i < nodiff_first_index || j > 0);
820 new[i].valptr[j] -= old[i].valptr[j];
824 * Make sure that the "totals" entry (last value in
825 * each stat array) for the procX-type stats has the
826 * "numentries" entry's (first value in procX-type
827 * stat arrays) constant value added-back after the
828 * diff -- i.e., it should always be included in the
831 if (!strncmp("proc", new[i].tag, 4) && old[i].valptr[0])
832 new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
840 int minutes, seconds;
843 endtime = time(NULL);
844 time_diff = difftime(endtime, starttime);
845 minutes = time_diff / 60;
846 seconds = (int)time_diff % 60;
847 printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);