nfsstat: Add -S/--since flag.
[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_raw_statfile(const char *, struct statinfo *);
175 static int              parse_pretty_statfile(const char *, struct statinfo *);
176
177 static statinfo         *get_stat_info(const char *, struct statinfo *);
178
179 static int              mounts(const char *);
180
181 static void             get_stats(const char *, struct statinfo *, int *, int,
182                                         int);
183 static int              has_stats(const unsigned int *);
184 static void             diff_stats(struct statinfo *, struct statinfo *, int);
185 static void             unpause(int);
186
187 static time_t           starttime;
188
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
199
200 int versions[] = {
201         PRNT_V2,
202         PRNT_V3,
203         PRNT_V4
204 };
205
206 void usage(char *name)
207 {
208         printf("Usage: %s [OPTION]...\n\
209 \n\
210   -m, --mounted\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\
230 \n", name);
231         exit(0);
232 }
233
234 static struct option longopts[] =
235 {
236         { "acl", 0, 0, 'a' },
237         { "all", 0, 0, 'v' },
238         { "auto", 0, 0, '\3' },
239         { "client", 0, 0, 'c' },
240         { "mounts", 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' },
250         { NULL, 0, 0, 0 }
251 };
252
253 int
254 main(int argc, char **argv)
255 {
256         int             opt_all = 0,
257                         opt_srv = 0,
258                         opt_clt = 0,
259                         opt_prt = 0,
260                         opt_sleep = 0,
261                         opt_since = 0;
262         int             c;
263         char           *progname,
264                        *serverfile = NFSSRVSTAT,
265                        *clientfile = NFSCLTSTAT;
266
267         struct statinfo *serverinfo = srvinfo,
268                         *serverinfo_tmp = srvinfo_old,
269                         *clientinfo = cltinfo,
270                         *clientinfo_tmp = cltinfo_old;
271
272         struct sigaction act = {
273                 .sa_handler = unpause,
274                 .sa_flags = SA_ONESHOT,
275         };
276
277         if ((progname = strrchr(argv[0], '/')))
278                 progname++;
279         else
280                 progname = argv[0];
281
282         while ((c = getopt_long(argc, argv, "234acmno:ZS:vrsz\1\2", longopts, NULL)) != EOF) {
283                 switch (c) {
284                 case 'a':
285                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
286                         return -1;
287                 case 'c':
288                         opt_clt = 1;
289                         break;
290                 case 'n':
291                         opt_prt |= PRNT_CALLS;
292                         break;
293                 case 'o':
294                         if (!strcmp(optarg, "nfs"))
295                                 opt_prt |= PRNT_CALLS;
296                         else if (!strcmp(optarg, "rpc"))
297                                 opt_prt |= PRNT_RPC;
298                         else if (!strcmp(optarg, "net"))
299                                 opt_prt |= PRNT_NET;
300                         else if (!strcmp(optarg, "rc"))
301                                 opt_prt |= PRNT_RC;
302                         else if (!strcmp(optarg, "fh"))
303                                 opt_prt |= PRNT_FH;
304                         else if (!strcmp(optarg, "all"))
305                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
306                         else {
307                                 fprintf(stderr, "nfsstat: unknown category: "
308                                                 "%s\n", optarg);
309                                 return 2;
310                         }
311                         break;
312                 case 'Z':
313                         opt_sleep = 1;
314                         break;
315                 case 'S':
316                         opt_since = 1;
317                         serverfile = optarg;
318                         clientfile = optarg;
319                         break;
320                 case '2':
321                 case '3':
322                 case '4':
323                         opt_prt |= versions[c - '2'];
324                         break;
325                 case 'v':
326                         opt_all = 1;
327                         break;
328                 case '\3':
329                         opt_prt |= PRNT_AUTO;
330                         break;
331                 case 'r':
332                         opt_prt |= PRNT_RPC;
333                         break;
334                 case 's':
335                         opt_srv = 1;
336                         break;
337                 case 'z':
338                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
339                                         "not yet supported\n");
340                         return 2;
341                 case 'm':
342                         return mounts(MOUNTSFILE);
343                 case '\1':
344                         usage(progname);
345                         return 0;
346                 case '\2':
347                         fprintf(stdout, "nfsstat: " VERSION "\n");
348                         return 0;
349                 default:
350                         printf("Try `%s --help' for more information.\n", progname);
351                         return -1;
352                 }
353         }
354
355         if (opt_all) {
356                 opt_srv = opt_clt = 1;
357                 opt_prt |= PRNT_ALL;
358         }
359         if (!(opt_srv + opt_clt))
360                 opt_srv = opt_clt = 1;
361         if (!(opt_prt & 0xfff)) {
362                 opt_prt |= PRNT_CALLS + PRNT_RPC;
363         }
364         if (!(opt_prt & 0xe000)) {
365                 opt_prt |= PRNT_AUTO;
366         }
367         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
368                 fprintf(stderr,
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 "
372                         "server.\n");
373         }
374
375         if (opt_since || opt_sleep) {
376                 serverinfo = srvinfo_old;
377                 serverinfo_tmp = srvinfo;
378                 clientinfo = cltinfo_old;
379                 clientinfo_tmp = cltinfo;
380         }
381
382         if (opt_srv)
383                 get_stats(serverfile, serverinfo, &opt_srv, opt_clt, 1);
384         if (opt_clt)
385                 get_stats(clientfile, clientinfo, &opt_clt, opt_srv, 0);
386
387         if (opt_sleep) {
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");
392                         return 1;
393                 }
394                 pause();
395         }
396
397         if (opt_since || opt_sleep) {
398                 if (opt_srv) {
399                         get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
400                         diff_stats(serverinfo_tmp, serverinfo, 1);
401                 }
402                 if (opt_clt) {
403                         get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
404                         diff_stats(clientinfo_tmp, clientinfo, 0);
405                 }
406         }
407
408         if (opt_srv) {
409                 if (opt_prt & PRNT_NET) {
410                         print_numbers(
411                         LABEL_srvnet
412                         "packets    udp        tcp        tcpconn\n",
413                         srvnetinfo, 4
414                         );
415                         printf("\n");
416                 }
417                 if (opt_prt & PRNT_RPC) {
418                         print_numbers(
419                         LABEL_srvrpc
420                         "calls      badcalls   badauth    badclnt    xdrcall\n",
421                         srvrpcinfo, 5
422                         );
423                         printf("\n");
424                 }
425                 if (opt_prt & PRNT_RC) {
426                         print_numbers(
427                         LABEL_srvrc
428                         "hits       misses     nocache\n",
429                         srvrcinfo, 3
430                         );
431                         printf("\n");
432                 }
433
434                 /*
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
439                  */
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];
444                                 srvfhinfo[4]=t;
445                                 
446                                 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
447                                 
448                                 print_numbers(
449                                         LABEL_srvfh
450                                         "lookup     anon       ncachedir  ncachedir  stale\n",
451                                         srvfhinfo + 1, 5);
452                         } else                                  /* < 2.4 */
453                                 print_numbers(
454                                         LABEL_srvfh
455                                         "lookup     anon       ncachedir  ncachedir  stale\n",
456                                         srvrcinfo + 3, 5);
457                         printf("\n");
458                 }
459                 if (opt_prt & PRNT_CALLS) {
460                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
461                                 print_callstats(
462                                 LABEL_srvproc2,
463                                 nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
464                                 );
465                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
466                                 print_callstats(
467                                 LABEL_srvproc3,
468                                 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
469                                 );
470                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
471                                 print_callstats(
472                                 LABEL_srvproc4,
473                                 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
474                                 );
475                                 print_callstats(
476                                 LABEL_srvproc4ops,
477                                 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
478                                 );
479                         }
480                 }
481         }
482
483         if (opt_clt) {
484                 if (opt_prt & PRNT_NET) {
485                         print_numbers(
486                         LABEL_cltnet
487                         "packets    udp        tcp        tcpconn\n",
488                         cltnetinfo, 4
489                         );
490                         printf("\n");
491                 }
492                 if (opt_prt & PRNT_RPC) {
493                         print_numbers(
494                         LABEL_cltrpc
495                         "calls      retrans    authrefrsh\n",
496                         cltrpcinfo, 3
497                         );
498                         printf("\n");
499                 }
500                 if (opt_prt & PRNT_CALLS) {
501                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
502                                 print_callstats(
503                                 LABEL_cltproc2,
504                                 nfsv2name, cltproc2info + 1,  sizeof(nfsv2name)/sizeof(char *)
505                                 );
506                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
507                                 print_callstats(
508                                 LABEL_cltproc3,
509                                 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
510                                 );
511                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
512                                 print_callstats(
513                                 LABEL_cltproc4,
514                                 nfscltproc4name, cltproc4info + 1,  sizeof(nfscltproc4name)/sizeof(char *)
515                                 );
516                 }
517         }
518
519         return 0;
520 }
521
522 static statinfo *
523 get_stat_info(const char *sp, struct statinfo *statp)
524 {
525         struct statinfo *ip;
526
527         for (ip = statp; ip->tag; ip++) {
528                 if (!strcmp(sp, ip->tag))
529                         return ip;
530         }
531
532         return NULL;
533 }
534
535 static void
536 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
537 {
538         unsigned int    i;
539
540         fputs(hdr, stdout);
541         for (i = 0; i < nr; i++)
542                 printf("%s%-8d", i? "   " : "", info[i]);
543         printf("\n");
544 }
545
546 static void
547 print_callstats(const char *hdr, const char **names,
548                                  unsigned int *info, unsigned int nr)
549 {
550         unsigned long long      total;
551         unsigned long long      pct;
552         int             i, j;
553
554         fputs(hdr, stdout);
555         for (i = 0, total = 0; i < nr; i++)
556                 total += info[i];
557         if (!total)
558                 total = 1;
559         for (i = 0; i < nr; i += 6) {
560                 for (j = 0; j < 6 && i + j < nr; j++)
561                         printf("%-13s", names[i+j]);
562                 printf("\n");
563                 for (j = 0; j < 6 && i + j < nr; j++) {
564                         pct = ((unsigned long long) info[i+j]*100)/total;
565                         printf("%-8d%3llu%% ", info[i+j], pct);
566                 }
567                 printf("\n");
568         }
569         printf("\n");
570 }
571
572 /* returns 0 on success, 1 otherwise */
573 static int
574 parse_raw_statfile(const char *name, struct statinfo *statp)
575 {
576         char    buffer[4096], *next;
577         FILE    *fp;
578
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.
581          */
582         if ((fp = fopen(name, "r")) == NULL) {
583                 // fprintf(stderr, "Warning: %s: %m\n", name);
584                 return 1;
585         }
586
587         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
588                 struct statinfo *ip;
589                 char            *sp, *line = buffer;
590                 unsigned int    i, cnt;
591                 unsigned int    total = 0;
592
593                 if ((next = strchr(line, '\n')) != NULL)
594                         *next++ = '\0';
595                 if (!(sp = strtok(line, " \t")))
596                         continue;
597
598                 ip = get_stat_info(sp, statp);
599                 if (!ip)
600                         continue;
601
602                 cnt = ip->nrvals;
603
604                 for (i = 0; i < cnt; i++) {
605                         if (!(sp = strtok(NULL, " \t")))
606                                 break;
607                         ip->valptr[i] = atoi(sp);
608                         total += ip->valptr[i];
609                 }
610                 ip->valptr[cnt - 1] = total;
611         }
612
613         fclose(fp);
614         return 0;
615 }
616
617 /* returns 0 on success, 1 otherwise */
618 static int
619 parse_pretty_statfile(const char *filename, struct statinfo *info)
620 {
621         int numvals, curindex, numconsumed, n, sum, err = 1;
622         char buf[4096], *bufp, *fmt, is_proc;
623         FILE *fp = NULL;
624         struct statinfo *ip;
625
626         if ((fp = fopen(filename, "r")) == NULL)
627                 //err(2, "Unable to open statfile '%s'.\n", filename);
628                 goto out;
629
630         while (fgets(buf, sizeof(buf), fp) != NULL) {
631                 for (ip = info; ip->tag; ip++) {
632                         if (strcmp(buf, ip->label))
633                                 continue;
634
635                         sum = 0;
636                         numvals = ip->nrvals - 1;
637                         is_proc = strncmp("proc", ip->tag, 4) ? 0 : 1;
638                         if (is_proc) {
639                                 fmt = " %u %*u%% %n";
640                                 curindex = 1;
641                                 ip->valptr[0] = 0;
642                         } else {
643                                 fmt = " %u %n";
644                                 curindex = 0;
645                         }
646 more_stats:
647                         /* get (and skip) header */
648                         if (fgets(buf, sizeof(buf), fp) == NULL) {
649                                 fprintf(stderr, "Failed to locate header after "
650                                                 "label for '%s' in %s.\n",
651                                                 ip->tag, filename);
652                                 goto out;
653                         }
654                         /* no header -- done with this "tag" */
655                         if (*buf == '\n') {
656                                 ip->valptr[numvals] = sum;
657                                 break;
658                         }
659                         /* get stats */
660                         if (fgets(buf, sizeof(buf), fp) == NULL) {
661                                 fprintf(stderr, "Failed to locate stats after "
662                                                 "header for '%s' in %s.\n",
663                                                 ip->tag, filename);
664                                 goto out;
665                         }
666                         bufp = buf;
667                         for (; curindex < numvals; curindex++) {
668                                 n = sscanf(bufp, fmt, &ip->valptr[curindex],
669                                                 &numconsumed);
670                                 if (n != 1)
671                                         break;
672                                 if (is_proc) {
673                                         ip->valptr[0]++;
674                                         sum++;
675                                 }
676                                 sum += ip->valptr[curindex];
677                                 bufp += numconsumed;
678                         }
679                         goto more_stats;
680                 }
681         }
682         err = 0;
683 out:
684         if (fp)
685                 fclose(fp);
686         return err;
687 }
688
689 static int
690 mounts(const char *name)
691 {
692         char    buffer[4096], *next;
693         FILE    *fp;
694
695         /* Being unable to read e.g. the nfsd stats file shouldn't
696          * be a fatal error -- it usually means the module isn't loaded.
697          */
698         if ((fp = fopen(name, "r")) == NULL) {
699                 fprintf(stderr, "Warning: %s: %m\n", name);
700                 return 0;
701         }
702
703         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
704                 char          *line = buffer;
705                 char          *device, *mount, *type, *flags;
706
707                 if ((next = strchr(line, '\n')) != NULL)
708                         *next = '\0';
709
710                 if (!(device = strtok(line, " \t")))
711                         continue;
712
713                 if (!(mount = strtok(NULL, " \t")))
714                         continue;
715
716                 if (!(type = strtok(NULL, " \t")))
717                         continue;
718
719                 if (strcmp(type, "nfs")) {
720                     continue;
721                 }
722
723                 if (!(flags = strtok(NULL, " \t")))
724                         continue;
725
726                 printf("%s from %s\n", mount, device);
727                 printf(" Flags:\t%s\n", flags);
728                 printf("\n");
729
730                 continue;
731         }
732
733         fclose(fp);
734         return 1;
735 }
736
737 static void
738 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
739                 int is_srv)
740 {
741         FILE *fp;
742         char buf[10];
743         int err = 1;
744         char *label = is_srv ? "Server" : "Client";
745
746         /* try to guess what type of stat file we're dealing with */
747         if ((fp = fopen(file, "r")) == NULL)
748                 goto out;
749         if (fgets(buf, 10, fp) == NULL)
750                 goto out;
751         if (!strncmp(buf, "net ", 4)) {
752                 /* looks like raw client stats */
753                 if (is_srv) {
754                         fprintf(stderr, "Warning: no server info present in "
755                                         "raw client stats file.\n");
756                         *opt = 0;
757                 } else
758                         err = parse_raw_statfile(file, info);
759         } else if (!strncmp(buf, "rc ", 3)) {
760                 /* looks like raw server stats */
761                 if (!is_srv) {
762                         fprintf(stderr, "Warning: no client info present in "
763                                         "raw server stats file.\n");
764                         *opt = 0;
765                 } else
766                         err = parse_raw_statfile(file, info);
767         } else
768                 /* looks like pretty client and server stats */
769                 err = parse_pretty_statfile(file, info);
770 out:
771         if (fp)
772                 fclose(fp);
773         if (err) {
774                 if (!other_opt) {
775                         fprintf(stderr, "Error: No %s Stats (%s: %m). \n",
776                                         label, file);
777                         exit(2);
778                 }
779                 *opt = 0;
780         }
781 }
782
783 /*
784  * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
785  * denotes the number of subsequent entries.  statinfo value arrays contain an additional
786  * field at the end which contains the sum of all previous elements in the array -- so,
787  * there are stats if the sum's greater than the entry-count.
788  */
789 static int
790 has_stats(const unsigned int *info)
791 {
792         return (info[0] && info[info[0] + 1] > info[0]);
793 }
794
795 /*
796  * take the difference of each individual stat value in 'new' and 'old'
797  * and store the results back into 'new'
798  */
799 static void
800 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
801 {
802         int i, j, nodiff_first_index, should_diff;
803
804         /*
805          * Different stat types have different formats in the /proc
806          * files: for the proc2/3/4-type stats, the first entry has
807          * the total number of subsequent entries; one does not want
808          * to diff that first entry.  The other stat types aren't like
809          * this.  So, we diff a given entry if it's not of one of the
810          * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
811          * the first entry ("j" > 0).
812          */
813         nodiff_first_index = 2 + (2 * is_srv);
814
815         for (i = 0; old[i].tag; i++) {
816                 for (j = 0; j < new[i].nrvals; j++) {
817                         should_diff = (i < nodiff_first_index || j > 0);
818                         if (should_diff)
819                                 new[i].valptr[j] -= old[i].valptr[j];
820                 }
821
822                 /*
823                  * Make sure that the "totals" entry (last value in
824                  * each stat array) for the procX-type stats has the
825                  * "numentries" entry's (first value in procX-type
826                  * stat arrays) constant value added-back after the
827                  * diff -- i.e., it should always be included in the
828                  * total.
829                  */
830                 if (!strncmp("proc", new[i].tag, 4) && old[i].valptr[0])
831                         new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
832         }
833 }
834
835 static void
836 unpause(int sig)
837 {
838         double time_diff;
839         int minutes, seconds;
840         time_t endtime;
841
842         endtime = time(NULL);
843         time_diff = difftime(endtime, starttime);
844         minutes = time_diff / 60;
845         seconds = (int)time_diff % 60;
846         printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);
847 }