nfsstat: #define stat header labels.
[nfs-utils.git] / utils / nfsstat / nfsstat.c
1 /*
2  * nfsstat.c            Output NFS statistics
3  *
4  * Copyright (C) 1995-2005 Olaf Kirch <okir@suse.de>
5  */
6
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10
11 #define NFSSRVSTAT      "/proc/net/rpc/nfsd"
12 #define NFSCLTSTAT      "/proc/net/rpc/nfs"
13
14 #define MOUNTSFILE      "/proc/mounts"
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <getopt.h>
20 #include <string.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <signal.h>
24 #include <time.h>
25
26 #define MAXNRVALS       32
27
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
36                                                                  * 1  UDP packets
37                                                                  * 2  TCP packets
38                                                                  * 3  TCP connections
39                                                                  */
40 static unsigned int     cltnetinfo[5], cltnetinfo_old[5];       /* 0  # of received packets
41                                                                  * 1  UDP packets
42                                                                  * 2  TCP packets
43                                                                  * 3  TCP connections
44                                                                  */
45
46 static unsigned int     srvrpcinfo[6], srvrpcinfo_old[6];       /* 0  total # of RPC calls
47                                                                  * 1  total # of bad calls
48                                                                  * 2  bad format
49                                                                  * 3  authentication failed
50                                                                  * 4  unknown client
51                                                                  */
52 static unsigned int     cltrpcinfo[4], cltrpcinfo_old[4];       /* 0  total # of RPC calls
53                                                                  * 1  retransmitted calls
54                                                                  * 2  cred refreshs
55                                                                  */
56
57 static unsigned int     srvrcinfo[9], srvrcinfo_old[9];         /* 0  repcache hits
58                                                                  * 1  repcache hits
59                                                                  * 2  uncached reqs
60                                                                  * (for pre-2.4 kernels:)
61                                                                  * 3  FH lookups
62                                                                  * 4  'anon' FHs
63                                                                  * 5  noncached non-directories
64                                                                  * 6  noncached directories
65                                                                  * 7  stale
66                                                                  */
67
68 static unsigned int     srvfhinfo[7], srvfhinfo_old[7];         /* (for kernels >= 2.4.0)
69                                                                  * 0  stale
70                                                                  * 1  FH lookups
71                                                                  * 2  'anon' FHs
72                                                                  * 3  noncached directories
73                                                                  * 4  noncached non-directories
74                                                                  * leave hole to relocate stale for order
75                                                                  *    compatability.
76                                                                  */
77
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"
82 };
83
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"
89 };
90
91 static const char *     nfssrvproc4name[2] = {
92         "null",
93         "compound",
94 };
95
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"
104 };
105
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"
114 };
115
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"
129
130 typedef struct statinfo {
131         char            *tag;
132         char            *label;
133         int             nrvals;
134         unsigned int *  valptr;
135 } statinfo;
136
137 /*
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 }
141  */
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[] = { \
147                                         SRV(net,s), \
148                                         SRV(rpc,s), \
149                                         SRV(rc,s), \
150                                         SRV(fh,s), \
151                                         SRV(proc2,s), \
152                                         SRV(proc3,s),\
153                                         SRV(proc4,s), \
154                                         SRV(proc4ops,s),\
155                                         { NULL, NULL, 0, NULL }\
156                                 }
157 #define DECLARE_CLT(n, s...)    static statinfo n##s[] = { \
158                                         CLT(net,s), \
159                                         CLT(rpc,s), \
160                                         CLT(proc2,s),\
161                                         CLT(proc3,s), \
162                                         CLT(proc4,s),\
163                                         { NULL, NULL, 0, NULL }\
164                                 }
165 DECLARE_SRV(srvinfo);
166 DECLARE_SRV(srvinfo, _old);
167 DECLARE_CLT(cltinfo);
168 DECLARE_CLT(cltinfo, _old);
169
170 static void             print_numbers(const char *, unsigned int *,
171                                         unsigned int);
172 static void             print_callstats(const char *, const char **,
173                                         unsigned int *, unsigned int);
174 static int              parse_statfile(const char *, struct statinfo *);
175
176 static statinfo         *get_stat_info(const char *, struct statinfo *);
177
178 static int              mounts(const char *);
179
180 static void             get_stats(const char *, struct statinfo *, int *, int,
181                                         int);
182 static int              has_stats(const unsigned int *);
183 static void             diff_stats(struct statinfo *, struct statinfo *, int);
184 static void             unpause(int);
185
186 static time_t           starttime;
187
188 #define PRNT_CALLS      0x0001
189 #define PRNT_RPC        0x0002
190 #define PRNT_NET        0x0004
191 #define PRNT_FH         0x0008
192 #define PRNT_RC         0x0010
193 #define PRNT_AUTO       0x1000
194 #define PRNT_V2         0x2000
195 #define PRNT_V3         0x4000
196 #define PRNT_V4         0x8000
197 #define PRNT_ALL        0x0fff
198
199 int versions[] = {
200         PRNT_V2,
201         PRNT_V3,
202         PRNT_V4
203 };
204
205 void usage(char *name)
206 {
207         printf("Usage: %s [OPTION]...\n\
208 \n\
209   -m, --mounted\t\tShow statistics on mounted NFS filesystems\n\
210   -c, --client\t\tShow NFS client statistics\n\
211   -s, --server\t\tShow NFS server statistics\n\
212   -2\t\t\tShow NFS version 2 statistics\n\
213   -3\t\t\tShow NFS version 3 statistics\n\
214   -4\t\t\tShow NFS version 4 statistics\n\
215   -o [facility]\t\tShow statistics on particular facilities.\n\
216      nfs\tNFS protocol information\n\
217      rpc\tGeneral RPC information\n\
218      net\tNetwork layer statistics\n\
219      fh\t\tUsage information on the server's file handle cache\n\
220      rc\t\tUsage information on the server's request reply cache\n\
221      all\tSelect all of the above\n\
222   -v, --verbose, --all\tSame as '-o all'\n\
223   -r, --rpc\t\tShow RPC statistics\n\
224   -n, --nfs\t\tShow NFS statistics\n\
225   -Z, --sleep\tSaves stats, pauses, diffs current and saved\n\
226   --version\t\tShow program version\n\
227   --help\t\tWhat you just did\n\
228 \n", name);
229         exit(0);
230 }
231
232 static struct option longopts[] =
233 {
234         { "acl", 0, 0, 'a' },
235         { "all", 0, 0, 'v' },
236         { "auto", 0, 0, '\3' },
237         { "client", 0, 0, 'c' },
238         { "mounts", 0, 0, 'm' },
239         { "nfs", 0, 0, 'n' },
240         { "rpc", 0, 0, 'r' },
241         { "server", 0, 0, 's' },
242         { "verbose", 0, 0, 'v' },
243         { "zero", 0, 0, 'z' },
244         { "help", 0, 0, '\1' },
245         { "version", 0, 0, '\2' },
246         { "sleep", 0, 0, 'Z' },
247         { NULL, 0, 0, 0 }
248 };
249
250 int
251 main(int argc, char **argv)
252 {
253         int             opt_all = 0,
254                         opt_srv = 0,
255                         opt_clt = 0,
256                         opt_prt = 0,
257                         opt_sleep = 0;
258         int             c;
259         char           *progname;
260
261         struct statinfo *serverinfo = srvinfo,
262                         *serverinfo_tmp = srvinfo_old,
263                         *clientinfo = cltinfo,
264                         *clientinfo_tmp = cltinfo_old;
265
266         struct sigaction act = {
267                 .sa_handler = unpause,
268                 .sa_flags = SA_ONESHOT,
269         };
270
271         if ((progname = strrchr(argv[0], '/')))
272                 progname++;
273         else
274                 progname = argv[0];
275
276         while ((c = getopt_long(argc, argv, "234acmno:Zvrsz\1\2", longopts, NULL)) != EOF) {
277                 switch (c) {
278                 case 'a':
279                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
280                         return -1;
281                 case 'c':
282                         opt_clt = 1;
283                         break;
284                 case 'n':
285                         opt_prt |= PRNT_CALLS;
286                         break;
287                 case 'o':
288                         if (!strcmp(optarg, "nfs"))
289                                 opt_prt |= PRNT_CALLS;
290                         else if (!strcmp(optarg, "rpc"))
291                                 opt_prt |= PRNT_RPC;
292                         else if (!strcmp(optarg, "net"))
293                                 opt_prt |= PRNT_NET;
294                         else if (!strcmp(optarg, "rc"))
295                                 opt_prt |= PRNT_RC;
296                         else if (!strcmp(optarg, "fh"))
297                                 opt_prt |= PRNT_FH;
298                         else if (!strcmp(optarg, "all"))
299                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
300                         else {
301                                 fprintf(stderr, "nfsstat: unknown category: "
302                                                 "%s\n", optarg);
303                                 return 2;
304                         }
305                         break;
306                 case 'Z':
307                         opt_sleep = 1;
308                         break;
309                 case '2':
310                 case '3':
311                 case '4':
312                         opt_prt |= versions[c - '2'];
313                         break;
314                 case 'v':
315                         opt_all = 1;
316                         break;
317                 case '\3':
318                         opt_prt |= PRNT_AUTO;
319                         break;
320                 case 'r':
321                         opt_prt |= PRNT_RPC;
322                         break;
323                 case 's':
324                         opt_srv = 1;
325                         break;
326                 case 'z':
327                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
328                                         "not yet supported\n");
329                         return 2;
330                 case 'm':
331                         return mounts(MOUNTSFILE);
332                 case '\1':
333                         usage(progname);
334                         return 0;
335                 case '\2':
336                         fprintf(stdout, "nfsstat: " VERSION "\n");
337                         return 0;
338                 default:
339                         printf("Try `%s --help' for more information.\n", progname);
340                         return -1;
341                 }
342         }
343
344         if (opt_all) {
345                 opt_srv = opt_clt = 1;
346                 opt_prt |= PRNT_ALL;
347         }
348         if (!(opt_srv + opt_clt))
349                 opt_srv = opt_clt = 1;
350         if (!(opt_prt & 0xfff)) {
351                 opt_prt |= PRNT_CALLS + PRNT_RPC;
352         }
353         if (!(opt_prt & 0xe000)) {
354                 opt_prt |= PRNT_AUTO;
355         }
356         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
357                 fprintf(stderr,
358                         "You requested file handle or request cache "
359                         "statistics while using the -c option.\n"
360                         "This information is available only for the NFS "
361                         "server.\n");
362         }
363
364         if (opt_sleep) {
365                 serverinfo = srvinfo_old;
366                 serverinfo_tmp = srvinfo;
367                 clientinfo = cltinfo_old;
368                 clientinfo_tmp = cltinfo;
369         }
370
371         if (opt_srv)
372                 get_stats(NFSSRVSTAT, serverinfo, &opt_srv, opt_clt, 1);
373         if (opt_clt)
374                 get_stats(NFSCLTSTAT, clientinfo, &opt_clt, opt_srv, 0);
375
376         /* save stat snapshots; wait for signal; then diff current and saved stats */
377         if (opt_sleep) {
378                 starttime = time(NULL);
379                 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
380                 if (sigaction(SIGINT, &act, NULL) != 0) {
381                         fprintf(stderr, "Error: couldn't register for signal and pause.\n");
382                         return 1;
383                 }
384                 pause();
385                 if (opt_srv) {
386                         get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
387                         diff_stats(serverinfo_tmp, serverinfo, 1);
388                 }
389                 if (opt_clt) {
390                         get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
391                         diff_stats(clientinfo_tmp, clientinfo, 0);
392                 }
393         }
394
395         if (opt_srv) {
396                 if (opt_prt & PRNT_NET) {
397                         print_numbers(
398                         LABEL_srvnet
399                         "packets    udp        tcp        tcpconn\n",
400                         srvnetinfo, 4
401                         );
402                         printf("\n");
403                 }
404                 if (opt_prt & PRNT_RPC) {
405                         print_numbers(
406                         LABEL_srvrpc
407                         "calls      badcalls   badauth    badclnt    xdrcall\n",
408                         srvrpcinfo, 5
409                         );
410                         printf("\n");
411                 }
412                 if (opt_prt & PRNT_RC) {
413                         print_numbers(
414                         LABEL_srvrc
415                         "hits       misses     nocache\n",
416                         srvrcinfo, 3
417                         );
418                         printf("\n");
419                 }
420
421                 /*
422                  * 2.2 puts all fh-related info after the 'rc' header
423                  * 2.4 puts all fh-related info after the 'fh' header, but relocates
424                  *     'stale' to the start and swaps dir and nondir :-(  
425                  *     We preseve the 2.2 order
426                  */
427                 if (opt_prt & PRNT_FH) {
428                         if (get_stat_info("fh", srvinfo)) {     /* >= 2.4 */
429                                 int t = srvfhinfo[3];
430                                 srvfhinfo[3]=srvfhinfo[4];
431                                 srvfhinfo[4]=t;
432                                 
433                                 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
434                                 
435                                 print_numbers(
436                                         LABEL_srvfh
437                                         "lookup     anon       ncachedir  ncachedir  stale\n",
438                                         srvfhinfo + 1, 5);
439                         } else                                  /* < 2.4 */
440                                 print_numbers(
441                                         LABEL_srvfh
442                                         "lookup     anon       ncachedir  ncachedir  stale\n",
443                                         srvrcinfo + 3, 5);
444                         printf("\n");
445                 }
446                 if (opt_prt & PRNT_CALLS) {
447                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
448                                 print_callstats(
449                                 LABEL_srvproc2,
450                                 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
451                                 );
452                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
453                                 print_callstats(
454                                 LABEL_srvproc3,
455                                 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
456                                 );
457                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
458                                 print_callstats(
459                                 LABEL_srvproc4,
460                                 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
461                                 );
462                                 print_callstats(
463                                 LABEL_srvproc4ops,
464                                 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
465                                 );
466                         }
467                 }
468         }
469
470         if (opt_clt) {
471                 if (opt_prt & PRNT_NET) {
472                         print_numbers(
473                         LABEL_cltnet
474                         "packets    udp        tcp        tcpconn\n",
475                         cltnetinfo, 4
476                         );
477                         printf("\n");
478                 }
479                 if (opt_prt & PRNT_RPC) {
480                         print_numbers(
481                         LABEL_cltrpc
482                         "calls      retrans    authrefrsh\n",
483                         cltrpcinfo, 3
484                         );
485                         printf("\n");
486                 }
487                 if (opt_prt & PRNT_CALLS) {
488                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
489                                 print_callstats(
490                                 LABEL_cltproc2,
491                                 nfsv2name, cltproc2info + 1,  sizeof(nfsv2name)/sizeof(char *)
492                                 );
493                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
494                                 print_callstats(
495                                 LABEL_cltproc3,
496                                 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
497                                 );
498                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
499                                 print_callstats(
500                                 LABEL_cltproc4,
501                                 nfscltproc4name, cltproc4info + 1,  sizeof(nfscltproc4name)/sizeof(char *)
502                                 );
503                 }
504         }
505
506         return 0;
507 }
508
509 static statinfo *
510 get_stat_info(const char *sp, struct statinfo *statp)
511 {
512         struct statinfo *ip;
513
514         for (ip = statp; ip->tag; ip++) {
515                 if (!strcmp(sp, ip->tag))
516                         return ip;
517         }
518
519         return NULL;
520 }
521
522 static void
523 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
524 {
525         unsigned int    i;
526
527         fputs(hdr, stdout);
528         for (i = 0; i < nr; i++)
529                 printf("%s%-8d", i? "   " : "", info[i]);
530         printf("\n");
531 }
532
533 static void
534 print_callstats(const char *hdr, const char **names,
535                                  unsigned int *info, unsigned int nr)
536 {
537         unsigned long long      total;
538         unsigned long long      pct;
539         int             i, j;
540
541         fputs(hdr, stdout);
542         for (i = 0, total = 0; i < nr; i++)
543                 total += info[i];
544         if (!total)
545                 total = 1;
546         for (i = 0; i < nr; i += 6) {
547                 for (j = 0; j < 6 && i + j < nr; j++)
548                         printf("%-13s", names[i+j]);
549                 printf("\n");
550                 for (j = 0; j < 6 && i + j < nr; j++) {
551                         pct = ((unsigned long long) info[i+j]*100)/total;
552                         printf("%-8d%3llu%% ", info[i+j], pct);
553                 }
554                 printf("\n");
555         }
556         printf("\n");
557 }
558
559
560 static int
561 parse_statfile(const char *name, struct statinfo *statp)
562 {
563         char    buffer[4096], *next;
564         FILE    *fp;
565
566         /* Being unable to read e.g. the nfsd stats file shouldn't
567          * be a fatal error -- it usually means the module isn't loaded.
568          */
569         if ((fp = fopen(name, "r")) == NULL) {
570                 // fprintf(stderr, "Warning: %s: %m\n", name);
571                 return 0;
572         }
573
574         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
575                 struct statinfo *ip;
576                 char            *sp, *line = buffer;
577                 unsigned int    i, cnt;
578                 unsigned int    total = 0;
579
580                 if ((next = strchr(line, '\n')) != NULL)
581                         *next++ = '\0';
582                 if (!(sp = strtok(line, " \t")))
583                         continue;
584
585                 ip = get_stat_info(sp, statp);
586                 if (!ip)
587                         continue;
588
589                 cnt = ip->nrvals;
590
591                 for (i = 0; i < cnt; i++) {
592                         if (!(sp = strtok(NULL, " \t")))
593                                 break;
594                         ip->valptr[i] = atoi(sp);
595                         total += ip->valptr[i];
596                 }
597                 ip->valptr[cnt - 1] = total;
598         }
599
600         fclose(fp);
601         return 1;
602 }
603
604 static int
605 mounts(const char *name)
606 {
607         char    buffer[4096], *next;
608         FILE    *fp;
609
610         /* Being unable to read e.g. the nfsd stats file shouldn't
611          * be a fatal error -- it usually means the module isn't loaded.
612          */
613         if ((fp = fopen(name, "r")) == NULL) {
614                 fprintf(stderr, "Warning: %s: %m\n", name);
615                 return 0;
616         }
617
618         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
619                 char          *line = buffer;
620                 char          *device, *mount, *type, *flags;
621
622                 if ((next = strchr(line, '\n')) != NULL)
623                         *next = '\0';
624
625                 if (!(device = strtok(line, " \t")))
626                         continue;
627
628                 if (!(mount = strtok(NULL, " \t")))
629                         continue;
630
631                 if (!(type = strtok(NULL, " \t")))
632                         continue;
633
634                 if (strcmp(type, "nfs")) {
635                     continue;
636                 }
637
638                 if (!(flags = strtok(NULL, " \t")))
639                         continue;
640
641                 printf("%s from %s\n", mount, device);
642                 printf(" Flags:\t%s\n", flags);
643                 printf("\n");
644
645                 continue;
646         }
647
648         fclose(fp);
649         return 1;
650 }
651
652 static void
653 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
654                 int is_srv)
655 {
656         const char *label = is_srv ? "Server" : "Client";
657
658         if (!parse_statfile(file, info)) {
659                 if (!other_opt) {
660                         fprintf(stderr, "Warning: No %s Stats (%s: %m). \n", label, file);
661                         exit(2);
662                 }
663                 *opt = 0;
664         }
665 }
666
667 /*
668  * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
669  * denotes the number of subsequent entries.  statinfo value arrays contain an additional
670  * field at the end which contains the sum of all previous elements in the array -- so,
671  * there are stats if the sum's greater than the entry-count.
672  */
673 static int
674 has_stats(const unsigned int *info)
675 {
676         return (info[0] && info[info[0] + 1] > info[0]);
677 }
678
679 /*
680  * take the difference of each individual stat value in 'new' and 'old'
681  * and store the results back into 'new'
682  */
683 static void
684 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
685 {
686         int i, j, nodiff_first_index, should_diff;
687
688         /*
689          * Different stat types have different formats in the /proc
690          * files: for the proc2/3/4-type stats, the first entry has
691          * the total number of subsequent entries; one does not want
692          * to diff that first entry.  The other stat types aren't like
693          * this.  So, we diff a given entry if it's not of one of the
694          * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
695          * the first entry ("j" > 0).
696          */
697         nodiff_first_index = 2 + (2 * is_srv);
698
699         for (i = 0; old[i].tag; i++) {
700                 for (j = 0; j < new[i].nrvals; j++) {
701                         should_diff = (i < nodiff_first_index || j > 0);
702                         if (should_diff)
703                                 new[i].valptr[j] -= old[i].valptr[j];
704                 }
705
706                 /*
707                  * Make sure that the "totals" entry (last value in
708                  * each stat array) for the procX-type stats has the
709                  * "numentries" entry's (first value in procX-type
710                  * stat arrays) constant value added-back after the
711                  * diff -- i.e., it should always be included in the
712                  * total.
713                  */
714                 if (!strncmp("proc", new[i].tag, 4))
715                         new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
716         }
717 }
718
719 static void
720 unpause(int sig)
721 {
722         double time_diff;
723         int minutes, seconds;
724         time_t endtime;
725
726         endtime = time(NULL);
727         time_diff = difftime(endtime, starttime);
728         minutes = time_diff / 60;
729         seconds = (int)time_diff % 60;
730         printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);
731 }