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_tmp[20]; /* NFSv2 call counts ([0] == 18) */
29 static unsigned int cltproc2info[20], cltproc2info_tmp[20]; /* NFSv2 call counts ([0] == 18) */
30 static unsigned int srvproc3info[24], srvproc3info_tmp[24]; /* NFSv3 call counts ([0] == 22) */
31 static unsigned int cltproc3info[24], cltproc3info_tmp[24]; /* NFSv3 call counts ([0] == 22) */
32 static unsigned int srvproc4info[4], srvproc4info_tmp[4]; /* NFSv4 call counts ([0] == 2) */
33 static unsigned int cltproc4info[37], cltproc4info_tmp[37]; /* NFSv4 call counts ([0] == 35) */
34 static unsigned int srvproc4opsinfo[42], srvproc4opsinfo_tmp[42]; /* NFSv4 call counts ([0] == 40) */
35 static unsigned int srvnetinfo[5], srvnetinfo_tmp[5]; /* 0 # of received packets
40 static unsigned int cltnetinfo[5], cltnetinfo_tmp[5]; /* 0 # of received packets
46 static unsigned int srvrpcinfo[6], srvrpcinfo_tmp[6]; /* 0 total # of RPC calls
47 * 1 total # of bad calls
49 * 3 authentication failed
52 static unsigned int cltrpcinfo[4], cltrpcinfo_tmp[4]; /* 0 total # of RPC calls
53 * 1 retransmitted calls
57 static unsigned int srvrcinfo[9], srvrcinfo_tmp[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_tmp[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 typedef struct statinfo {
119 unsigned int * valptr;
123 * We now build the arrays of statinfos using macros, which will make it easier
124 * to add new variables for --diff-stat.
125 * e.g., SRV(net) expands into the struct statinfo: { "net", 5, srvnetinfo }
127 #define ARRAYSIZE(x) sizeof(x)/sizeof(*x)
128 #define STATINFO(k, t, s...) { #t, ARRAYSIZE(k##t##info##s), k##t##info##s }
129 #define SRV(t, s...) STATINFO(srv, t, s)
130 #define CLT(t, s...) STATINFO(clt, t, s)
131 #define DECLARE_SRV(n, s...) static statinfo n##s[] = { \
142 #define DECLARE_CLT(n, s...) static statinfo n##s[] = { \
150 DECLARE_SRV(srvinfo);
151 DECLARE_SRV(srvinfo, _tmp);
152 DECLARE_CLT(cltinfo);
153 DECLARE_CLT(cltinfo, _tmp);
155 static void print_numbers(const char *, unsigned int *,
157 static void print_callstats(const char *, const char **,
158 unsigned int *, unsigned int);
159 static int parse_statfile(const char *, struct statinfo *);
161 static statinfo *get_stat_info(const char *, struct statinfo *);
163 static int mounts(const char *);
165 static void get_stats(const char *, statinfo *, int *, int, const char *);
166 static int has_stats(const unsigned int *);
167 static void copy_stats(statinfo *, statinfo *);
168 static void diff_stats(statinfo *, statinfo *);
169 static void unpause(int);
171 static time_t starttime;
173 #define PRNT_CALLS 0x0001
174 #define PRNT_RPC 0x0002
175 #define PRNT_NET 0x0004
176 #define PRNT_FH 0x0008
177 #define PRNT_RC 0x0010
178 #define PRNT_AUTO 0x1000
179 #define PRNT_V2 0x2000
180 #define PRNT_V3 0x4000
181 #define PRNT_V4 0x8000
182 #define PRNT_ALL 0x0fff
190 void usage(char *name)
192 printf("Usage: %s [OPTION]...\n\
194 -m, --mounted\t\tShow statistics on mounted NFS filesystems\n\
195 -c, --client\t\tShow NFS client statistics\n\
196 -s, --server\t\tShow NFS server statistics\n\
197 -2\t\t\tShow NFS version 2 statistics\n\
198 -3\t\t\tShow NFS version 3 statistics\n\
199 -4\t\t\tShow NFS version 4 statistics\n\
200 -o [facility]\t\tShow statistics on particular facilities.\n\
201 nfs\tNFS protocol information\n\
202 rpc\tGeneral RPC information\n\
203 net\tNetwork layer statistics\n\
204 fh\t\tUsage information on the server's file handle cache\n\
205 rc\t\tUsage information on the server's request reply cache\n\
206 all\tSelect all of the above\n\
207 -v, --verbose, --all\tSame as '-o all'\n\
208 -r, --rpc\t\tShow RPC statistics\n\
209 -n, --nfs\t\tShow NFS statistics\n\
210 -Z, --sleep\tSaves stats, pauses, diffs current and saved\n\
211 --version\t\tShow program version\n\
212 --help\t\tWhat you just did\n\
217 static struct option longopts[] =
219 { "acl", 0, 0, 'a' },
220 { "all", 0, 0, 'v' },
221 { "auto", 0, 0, '\3' },
222 { "client", 0, 0, 'c' },
223 { "mounts", 0, 0, 'm' },
224 { "nfs", 0, 0, 'n' },
225 { "rpc", 0, 0, 'r' },
226 { "server", 0, 0, 's' },
227 { "verbose", 0, 0, 'v' },
228 { "zero", 0, 0, 'z' },
229 { "help", 0, 0, '\1' },
230 { "version", 0, 0, '\2' },
231 { "sleep", 0, 0, 'Z' },
236 main(int argc, char **argv)
246 struct sigaction act = {
247 .sa_handler = unpause,
248 .sa_flags = SA_ONESHOT,
251 if ((progname = strrchr(argv[0], '/')))
256 while ((c = getopt_long(argc, argv, "234acmno:Zvrsz\1\2", longopts, NULL)) != EOF) {
259 fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
265 opt_prt |= PRNT_CALLS;
268 if (!strcmp(optarg, "nfs"))
269 opt_prt |= PRNT_CALLS;
270 else if (!strcmp(optarg, "rpc"))
272 else if (!strcmp(optarg, "net"))
274 else if (!strcmp(optarg, "rc"))
276 else if (!strcmp(optarg, "fh"))
278 else if (!strcmp(optarg, "all"))
279 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
281 fprintf(stderr, "nfsstat: unknown category: "
292 opt_prt |= versions[c - '2'];
298 opt_prt |= PRNT_AUTO;
307 fprintf(stderr, "nfsstat: zeroing of nfs statistics "
308 "not yet supported\n");
311 return mounts(MOUNTSFILE);
316 fprintf(stdout, "nfsstat: " VERSION "\n");
319 printf("Try `%s --help' for more information.\n", progname);
325 opt_srv = opt_clt = 1;
328 if (!(opt_srv + opt_clt))
329 opt_srv = opt_clt = 1;
330 if (!(opt_prt & 0xfff)) {
331 opt_prt |= PRNT_CALLS + PRNT_RPC;
333 if (!(opt_prt & 0xe000)) {
334 opt_prt |= PRNT_AUTO;
336 if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
338 "You requested file handle or request cache "
339 "statistics while using the -c option.\n"
340 "This information is available only for the NFS "
345 get_stats(NFSSRVSTAT, srvinfo, &opt_srv, opt_clt, "Server");
347 get_stats(NFSCLTSTAT, cltinfo, &opt_clt, opt_srv, "Client");
349 /* save stat snapshots; wait for signal; then diff current and saved stats */
351 starttime = time(NULL);
352 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
354 copy_stats(srvinfo_tmp, srvinfo);
356 copy_stats(cltinfo_tmp, cltinfo);
357 if (sigaction(SIGINT, &act, NULL) != 0) {
358 fprintf(stderr, "Error: couldn't register for signal and pause.\n");
363 get_stats(NFSSRVSTAT, srvinfo, &opt_srv, opt_clt, "Server");
364 diff_stats(srvinfo, srvinfo_tmp);
367 get_stats(NFSCLTSTAT, cltinfo, &opt_clt, opt_srv, "Client");
368 diff_stats(cltinfo, cltinfo_tmp);
373 if (opt_prt & PRNT_NET) {
375 "Server packet stats:\n"
376 "packets udp tcp tcpconn\n",
381 if (opt_prt & PRNT_RPC) {
383 "Server rpc stats:\n"
384 "calls badcalls badauth badclnt xdrcall\n",
389 if (opt_prt & PRNT_RC) {
391 "Server reply cache:\n"
392 "hits misses nocache\n",
399 * 2.2 puts all fh-related info after the 'rc' header
400 * 2.4 puts all fh-related info after the 'fh' header, but relocates
401 * 'stale' to the start and swaps dir and nondir :-(
402 * We preseve the 2.2 order
404 if (opt_prt & PRNT_FH) {
405 if (get_stat_info("fh", srvinfo)) { /* >= 2.4 */
406 int t = srvfhinfo[3];
407 srvfhinfo[3]=srvfhinfo[4];
410 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
413 "Server file handle cache:\n"
414 "lookup anon ncachedir ncachedir stale\n",
418 "Server file handle cache:\n"
419 "lookup anon ncachedir ncachedir stale\n",
423 if (opt_prt & PRNT_CALLS) {
424 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
427 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
429 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
432 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
434 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
437 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
440 "Server nfs v4 operations:\n",
441 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
448 if (opt_prt & PRNT_NET) {
450 "Client packet stats:\n"
451 "packets udp tcp tcpconn\n",
456 if (opt_prt & PRNT_RPC) {
458 "Client rpc stats:\n"
459 "calls retrans authrefrsh\n",
464 if (opt_prt & PRNT_CALLS) {
465 if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
468 nfsv2name, cltproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
470 if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
473 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
475 if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
478 nfscltproc4name, cltproc4info + 1, sizeof(nfscltproc4name)/sizeof(char *)
487 get_stat_info(const char *sp, struct statinfo *statp)
491 for (ip = statp; ip->tag; ip++) {
492 if (!strcmp(sp, ip->tag))
500 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
505 for (i = 0; i < nr; i++)
506 printf("%s%-8d", i? " " : "", info[i]);
511 print_callstats(const char *hdr, const char **names,
512 unsigned int *info, unsigned int nr)
514 unsigned long long total;
515 unsigned long long pct;
519 for (i = 0, total = 0; i < nr; i++)
523 for (i = 0; i < nr; i += 6) {
524 for (j = 0; j < 6 && i + j < nr; j++)
525 printf("%-13s", names[i+j]);
527 for (j = 0; j < 6 && i + j < nr; j++) {
528 pct = ((unsigned long long) info[i+j]*100)/total;
529 printf("%-8d%3llu%% ", info[i+j], pct);
538 parse_statfile(const char *name, struct statinfo *statp)
540 char buffer[4096], *next;
543 /* Being unable to read e.g. the nfsd stats file shouldn't
544 * be a fatal error -- it usually means the module isn't loaded.
546 if ((fp = fopen(name, "r")) == NULL) {
547 // fprintf(stderr, "Warning: %s: %m\n", name);
551 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
553 char *sp, *line = buffer;
555 unsigned int total = 0;
557 if ((next = strchr(line, '\n')) != NULL)
559 if (!(sp = strtok(line, " \t")))
562 ip = get_stat_info(sp, statp);
568 for (i = 0; i < cnt; i++) {
569 if (!(sp = strtok(NULL, " \t")))
571 ip->valptr[i] = atoi(sp);
572 total += ip->valptr[i];
574 ip->valptr[cnt - 1] = total;
582 mounts(const char *name)
584 char buffer[4096], *next;
587 /* Being unable to read e.g. the nfsd stats file shouldn't
588 * be a fatal error -- it usually means the module isn't loaded.
590 if ((fp = fopen(name, "r")) == NULL) {
591 fprintf(stderr, "Warning: %s: %m\n", name);
595 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
597 char *device, *mount, *type, *flags;
599 if ((next = strchr(line, '\n')) != NULL)
602 if (!(device = strtok(line, " \t")))
605 if (!(mount = strtok(NULL, " \t")))
608 if (!(type = strtok(NULL, " \t")))
611 if (strcmp(type, "nfs")) {
615 if (!(flags = strtok(NULL, " \t")))
618 printf("%s from %s\n", mount, device);
619 printf(" Flags:\t%s\n", flags);
630 get_stats(const char *file, statinfo *info, int *opt, int other_opt, const char *label)
632 if (!parse_statfile(file, info)) {
634 fprintf(stderr, "Warning: No %s Stats (%s: %m). \n", label, file);
642 * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
643 * denotes the number of subsequent entries. statinfo value arrays contain an additional
644 * field at the end which contains the sum of all previous elements in the array -- so,
645 * there are stats if the sum's greater than the entry-count.
648 has_stats(const unsigned int *info)
650 return (info[0] && info[info[0] + 1] > info[0]);
653 /* clone 'src' to 'dest' */
655 copy_stats(struct statinfo *dest, struct statinfo *src)
659 for (i = 0; src[i].tag; i++) {
660 dest[i].tag = src[i].tag;
661 dest[i].nrvals = src[i].nrvals;
662 for (j = 0; j < dest[i].nrvals; j++)
663 dest[i].valptr[j] = src[i].valptr[j];
668 * take the difference of each individual stat value in 'new' and 'old'
669 * and store the results back into 'new'
672 diff_stats(struct statinfo *new, struct statinfo *old)
674 int i, j, is_srv, nodiff_first_index, should_diff;
677 * Different stat types have different formats in the /proc
678 * files: for the proc2/3/4-type stats, the first entry has
679 * the total number of subsequent entries; one does not want
680 * to diff that first entry. The other stat types aren't like
681 * this. So, we diff a given entry if it's not of one of the
682 * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
683 * the first entry ("j" > 0).
685 is_srv = (new == srvinfo);
686 nodiff_first_index = 2 + (2 * is_srv);
688 for (i = 0; old[i].tag; i++) {
689 for (j = 0; j < new[i].nrvals; j++) {
690 should_diff = (i < nodiff_first_index || j > 0);
692 new[i].valptr[j] -= old[i].valptr[j];
696 * Make sure that the "totals" entry (last value in
697 * each stat array) for the procX-type stats has the
698 * "numentries" entry's (first value in procX-type
699 * stat arrays) constant value added-back after the
700 * diff -- i.e., it should always be included in the
703 if (!strncmp("proc", new[i].tag, 4))
704 new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
712 int minutes, seconds;
715 endtime = time(NULL);
716 time_diff = difftime(endtime, starttime);
717 minutes = time_diff / 60;
718 seconds = (int)time_diff % 60;
719 printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);