]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nfsstat/nfsstat.c
nfsstat: Output headings mislabled
[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 enum {
29         SRVPROC2_SZ = 18,
30         CLTPROC2_SZ = 18,
31         SRVPROC3_SZ = 22,
32         CLTPROC3_SZ = 22,
33         SRVPROC4_SZ = 2,
34         CLTPROC4_SZ = 49,
35         SRVPROC4OPS_SZ = 59,
36 };
37
38 static unsigned int     srvproc2info[SRVPROC2_SZ+2],
39                         srvproc2info_old[SRVPROC2_SZ+2];        /* NFSv2 call counts ([0] == 18) */
40 static unsigned int     cltproc2info[CLTPROC2_SZ+2],
41                         cltproc2info_old[CLTPROC2_SZ+2];        /* NFSv2 call counts ([0] == 18) */
42 static unsigned int     srvproc3info[SRVPROC3_SZ+2],
43                         srvproc3info_old[SRVPROC3_SZ+2];        /* NFSv3 call counts ([0] == 22) */
44 static unsigned int     cltproc3info[CLTPROC3_SZ+2],
45                         cltproc3info_old[CLTPROC3_SZ+2];        /* NFSv3 call counts ([0] == 22) */
46 static unsigned int     srvproc4info[SRVPROC4_SZ+2],
47                         srvproc4info_old[SRVPROC4_SZ+2];        /* NFSv4 call counts ([0] == 2) */
48 static unsigned int     cltproc4info[CLTPROC4_SZ+2],
49                         cltproc4info_old[CLTPROC4_SZ+2];        /* NFSv4 call counts ([0] == 49) */
50 static unsigned int     srvproc4opsinfo[SRVPROC4OPS_SZ+2],
51                         srvproc4opsinfo_old[SRVPROC4OPS_SZ+2];  /* NFSv4 call counts ([0] == 59) */
52 static unsigned int     srvnetinfo[5], srvnetinfo_old[5];       /* 0  # of received packets
53                                                                  * 1  UDP packets
54                                                                  * 2  TCP packets
55                                                                  * 3  TCP connections
56                                                                  */
57 static unsigned int     cltnetinfo[5], cltnetinfo_old[5];       /* 0  # of received packets
58                                                                  * 1  UDP packets
59                                                                  * 2  TCP packets
60                                                                  * 3  TCP connections
61                                                                  */
62
63 static unsigned int     srvrpcinfo[6], srvrpcinfo_old[6];       /* 0  total # of RPC calls
64                                                                  * 1  total # of bad calls
65                                                                  * 2  bad format
66                                                                  * 3  authentication failed
67                                                                  * 4  unknown client
68                                                                  */
69 static unsigned int     cltrpcinfo[4], cltrpcinfo_old[4];       /* 0  total # of RPC calls
70                                                                  * 1  retransmitted calls
71                                                                  * 2  cred refreshs
72                                                                  */
73
74 static unsigned int     srvrcinfo[9], srvrcinfo_old[9];         /* 0  repcache hits
75                                                                  * 1  repcache hits
76                                                                  * 2  uncached reqs
77                                                                  * (for pre-2.4 kernels:)
78                                                                  * 3  FH lookups
79                                                                  * 4  'anon' FHs
80                                                                  * 5  noncached non-directories
81                                                                  * 6  noncached directories
82                                                                  * 7  stale
83                                                                  */
84
85 static unsigned int     srvfhinfo[7], srvfhinfo_old[7];         /* (for kernels >= 2.4.0)
86                                                                  * 0  stale
87                                                                  * 1  FH lookups
88                                                                  * 2  'anon' FHs
89                                                                  * 3  noncached directories
90                                                                  * 4  noncached non-directories
91                                                                  * leave hole to relocate stale for order
92                                                                  *    compatability.
93                                                                  */
94
95 static const char *     nfsv2name[SRVPROC2_SZ] = {
96         "null", "getattr", "setattr", "root",   "lookup",  "readlink",
97         "read", "wrcache", "write",   "create", "remove",  "rename",
98         "link", "symlink", "mkdir",   "rmdir",  "readdir", "fsstat"
99 };
100
101 static const char *     nfsv3name[SRVPROC3_SZ] = {
102         "null",   "getattr", "setattr",  "lookup", "access",  "readlink",
103         "read",   "write",   "create",   "mkdir",  "symlink", "mknod",
104         "remove", "rmdir",   "rename",   "link",   "readdir", "readdirplus",
105         "fsstat", "fsinfo",  "pathconf", "commit"
106 };
107
108 static const char *     nfssrvproc4name[SRVPROC4_SZ] = {
109         "null",
110         "compound",
111 };
112
113 static const char *     nfscltproc4name[CLTPROC4_SZ] = {
114         "null",      "read",      "write",   "commit",      "open",        "open_conf",
115         "open_noat", "open_dgrd", "close",   "setattr",     "fsinfo",      "renew",
116         "setclntid", "confirm",   "lock",
117         "lockt",     "locku",     "access",  "getattr",     "lookup",      "lookup_root",
118         "remove",    "rename",    "link",    "symlink",     "create",      "pathconf",
119         "statfs",    "readlink",  "readdir", "server_caps", "delegreturn", "getacl",
120         "setacl",    "fs_locations",
121         "rel_lkowner",
122         /* nfsv4.1 client ops */
123         "exchange_id",
124         "create_ses",
125         "destroy_ses",
126         "sequence",
127         "get_lease_t",
128         "reclaim_comp",
129         "layoutget",
130         "layoutcommit",
131         "layoutreturn",
132         "getdevlist",
133         "getdevinfo",
134         /* nfsv4.1 pnfs client ops to data server only */
135         "ds_write",
136         "ds_commit",
137 };
138
139 static const char *     nfssrvproc4opname[SRVPROC4OPS_SZ] = {
140         "op0-unused",   "op1-unused", "op2-future",  "access",     "close",       "commit",
141         "create",       "delegpurge", "delegreturn", "getattr",    "getfh",       "link",
142         "lock",         "lockt",      "locku",       "lookup",     "lookup_root", "nverify",
143         "open",         "openattr",   "open_conf",   "open_dgrd",  "putfh",       "putpubfh",
144         "putrootfh",    "read",       "readdir",     "readlink",   "remove",      "rename",
145         "renew",        "restorefh",  "savefh",      "secinfo",    "setattr",     "setcltid",
146         "setcltidconf", "verify",     "write",       "rellockowner",
147         /* nfsv4.1 server ops */
148         "bc_ctl",
149         "bind_conn",
150         "exchange_id",
151         "create_ses",
152         "destroy_ses",
153         "free_stateid",
154         "getdirdeleg",
155         "getdevinfo",
156         "getdevlist",
157         "layoutcommit",
158         "layoutget",
159         "layoutreturn",
160         "secinfononam",
161         "sequence",
162         "set_ssv",
163         "test_stateid",
164         "want_deleg",
165         "destroy_clid",
166         "reclaim_comp",
167 };
168
169 #define LABEL_srvnet            "Server packet stats:\n"
170 #define LABEL_srvrpc            "Server rpc stats:\n"
171 #define LABEL_srvrc             "Server reply cache:\n"
172 #define LABEL_srvfh             "Server file handle cache:\n"
173 #define LABEL_srvproc2          "Server nfs v2:\n"
174 #define LABEL_srvproc3          "Server nfs v3:\n"
175 #define LABEL_srvproc4          "Server nfs v4:\n"
176 #define LABEL_srvproc4ops       "Server nfs v4 operations:\n"
177 #define LABEL_cltnet            "Client packet stats:\n"
178 #define LABEL_cltrpc            "Client rpc stats:\n"
179 #define LABEL_cltproc2          "Client nfs v2:\n"
180 #define LABEL_cltproc3          "Client nfs v3:\n"
181 #define LABEL_cltproc4          "Client nfs v4:\n"
182
183 typedef struct statinfo {
184         char            *tag;
185         char            *label;
186         int             nrvals;
187         unsigned int *  valptr;
188 } statinfo;
189
190 /*
191  * We now build the arrays of statinfos using macros, which will make it easier
192  * to add new variables for --sleep.  e.g., SRV(net) expands into the struct
193  * statinfo:  { "net", "Server packet stats:\n", 5, srvnetinfo }
194  */
195 #define ARRAYSIZE(x)            sizeof(x)/sizeof(*x)
196 #define STATINFO(k, t, s...)    { #t, LABEL_##k##t, ARRAYSIZE(k##t##info##s), k##t##info##s }
197 #define SRV(t, s...)            STATINFO(srv, t, s)
198 #define CLT(t, s...)            STATINFO(clt, t, s)
199 #define DECLARE_SRV(n, s...)    static statinfo n##s[] = { \
200                                         SRV(net,s), \
201                                         SRV(rpc,s), \
202                                         SRV(rc,s), \
203                                         SRV(fh,s), \
204                                         SRV(proc2,s), \
205                                         SRV(proc3,s),\
206                                         SRV(proc4,s), \
207                                         SRV(proc4ops,s),\
208                                         { NULL, NULL, 0, NULL }\
209                                 }
210 #define DECLARE_CLT(n, s...)    static statinfo n##s[] = { \
211                                         CLT(net,s), \
212                                         CLT(rpc,s), \
213                                         CLT(proc2,s),\
214                                         CLT(proc3,s), \
215                                         CLT(proc4,s),\
216                                         { NULL, NULL, 0, NULL }\
217                                 }
218 DECLARE_SRV(srvinfo);
219 DECLARE_SRV(srvinfo, _old);
220 DECLARE_CLT(cltinfo);
221 DECLARE_CLT(cltinfo, _old);
222
223 static void             print_all_stats(int, int, int);
224 static void             print_server_stats(int);
225 static void             print_client_stats(int);
226 static void             print_stats_list(int, int, int);
227 static void             print_numbers(const char *, unsigned int *,
228                                         unsigned int);
229 static void             print_callstats(const char *, const char **,
230                                         unsigned int *, unsigned int);
231 static void             print_callstats_list(const char *, const char **,
232                                         unsigned int *, unsigned int);
233 static int              parse_raw_statfile(const char *, struct statinfo *);
234 static int              parse_pretty_statfile(const char *, struct statinfo *);
235
236 static statinfo         *get_stat_info(const char *, struct statinfo *);
237
238 static int              mounts(const char *);
239
240 static void             get_stats(const char *, struct statinfo *, int *, int,
241                                         int);
242 static int              has_stats(const unsigned int *, int);
243 static int              has_rpcstats(const unsigned int *, int);
244 static void             diff_stats(struct statinfo *, struct statinfo *, int);
245 static void             unpause(int);
246 static void             update_old_counters(struct statinfo *, struct statinfo *);
247
248 static time_t           starttime;
249
250 #define PRNT_CALLS      0x0001
251 #define PRNT_RPC        0x0002
252 #define PRNT_NET        0x0004
253 #define PRNT_FH         0x0008
254 #define PRNT_RC         0x0010
255 #define PRNT_AUTO       0x1000
256 #define PRNT_V2         0x2000
257 #define PRNT_V3         0x4000
258 #define PRNT_V4         0x8000
259 #define PRNT_ALL        0x0fff
260
261 int versions[] = {
262         PRNT_V2,
263         PRNT_V3,
264         PRNT_V4
265 };
266
267 void usage(char *name)
268 {
269         printf("Usage: %s [OPTION]...\n\
270 \n\
271   -m, --mounts          Show statistics on mounted NFS filesystems\n\
272   -c, --client          Show NFS client statistics\n\
273   -s, --server          Show NFS server statistics\n\
274   -2                    Show NFS version 2 statistics\n\
275   -3                    Show NFS version 3 statistics\n\
276   -4                    Show NFS version 4 statistics\n\
277   -o [facility]         Show statistics on particular facilities.\n\
278      nfs                NFS protocol information\n\
279      rpc                General RPC information\n\
280      net                Network layer statistics\n\
281      fh                 Usage information on the server's file handle cache\n\
282      rc                 Usage information on the server's request reply cache\n\
283      all                Select all of the above\n\
284   -v, --verbose, --all  Same as '-o all'\n\
285   -r, --rpc             Show RPC statistics\n\
286   -n, --nfs             Show NFS statistics\n\
287   -Z[#], --sleep[=#]    Collects stats until interrupted.\n\
288                             Cumulative stats are then printed\n\
289                             If # is provided, stats will be output every\n\
290                             # seconds.\n\
291   -S, --since file      Shows difference between current stats and those in 'file'\n\
292   -l, --list            Prints stats in list format\n\
293   --version             Show program version\n\
294   --help                What you just did\n\
295 \n", name);
296         exit(0);
297 }
298
299 static struct option longopts[] =
300 {
301         { "acl", 0, 0, 'a' },
302         { "all", 0, 0, 'v' },
303         { "auto", 0, 0, '\3' },
304         { "client", 0, 0, 'c' },
305         { "mounted", 0, 0, 'm' },
306         { "nfs", 0, 0, 'n' },
307         { "rpc", 0, 0, 'r' },
308         { "server", 0, 0, 's' },
309         { "verbose", 0, 0, 'v' },
310         { "zero", 0, 0, 'z' },
311         { "help", 0, 0, '\1' },
312         { "version", 0, 0, '\2' },
313         { "sleep", 2, 0, 'Z' },
314         { "since", 1, 0, 'S' },
315         { "list", 0, 0, 'l' },
316         { NULL, 0, 0, 0 }
317 };
318 int opt_sleep;
319
320 int
321 main(int argc, char **argv)
322 {
323         int             opt_all = 0,
324                         opt_srv = 0,
325                         opt_clt = 0,
326                         opt_prt = 0,
327                         sleep_time = 0,
328                         opt_list =0,
329                         opt_since = 0;
330         int             c;
331         char           *progname,
332                        *serverfile = NFSSRVSTAT,
333                        *clientfile = NFSCLTSTAT;
334
335         struct statinfo *serverinfo = srvinfo,
336                         *serverinfo_tmp = srvinfo_old,
337                         *clientinfo = cltinfo,
338                         *clientinfo_tmp = cltinfo_old;
339
340         struct sigaction act = {
341                 .sa_handler = unpause,
342                 .sa_flags = SA_ONESHOT,
343         };
344
345         if ((progname = strrchr(argv[0], '/')))
346                 progname++;
347         else
348                 progname = argv[0];
349
350         while ((c = getopt_long(argc, argv, "234acmno:Z::S:vrslz\1\2", longopts, NULL)) != EOF) {
351                 switch (c) {
352                 case 'a':
353                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
354                         return -1;
355                 case 'c':
356                         opt_clt = 1;
357                         break;
358                 case 'n':
359                         opt_prt |= PRNT_CALLS;
360                         break;
361                 case 'o':
362                         if (!strcmp(optarg, "nfs"))
363                                 opt_prt |= PRNT_CALLS;
364                         else if (!strcmp(optarg, "rpc"))
365                                 opt_prt |= PRNT_RPC;
366                         else if (!strcmp(optarg, "net"))
367                                 opt_prt |= PRNT_NET;
368                         else if (!strcmp(optarg, "rc"))
369                                 opt_prt |= PRNT_RC;
370                         else if (!strcmp(optarg, "fh"))
371                                 opt_prt |= PRNT_FH;
372                         else if (!strcmp(optarg, "all"))
373                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
374                         else {
375                                 fprintf(stderr, "nfsstat: unknown category: "
376                                                 "%s\n", optarg);
377                                 return 2;
378                         }
379                         break;
380                 case 'Z':
381                         opt_sleep = 1;
382                         if (optarg) {
383                                 sleep_time = atoi(optarg);
384                         }
385                         break;
386                 case 'S':
387                         opt_since = 1;
388                         serverfile = optarg;
389                         clientfile = optarg;
390                         break;
391                 case '2':
392                 case '3':
393                 case '4':
394                         opt_prt |= versions[c - '2'];
395                         break;
396                 case 'v':
397                         opt_all = 1;
398                         break;
399                 case '\3':
400                         opt_prt |= PRNT_AUTO;
401                         break;
402                 case 'r':
403                         opt_prt |= PRNT_RPC;
404                         break;
405                 case 's':
406                         opt_srv = 1;
407                         break;
408                 case 'l':
409                         opt_list = 1;
410                         break;
411                 case 'z':
412                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
413                                         "not yet supported\n");
414                         return 2;
415                 case 'm':
416                         return mounts(MOUNTSFILE);
417                 case '\1':
418                         usage(progname);
419                         return 0;
420                 case '\2':
421                         fprintf(stdout, "nfsstat: " VERSION "\n");
422                         return 0;
423                 default:
424                         printf("Try `%s --help' for more information.\n", progname);
425                         return -1;
426                 }
427         }
428
429         if (opt_all) {
430                 opt_srv = opt_clt = 1;
431                 opt_prt |= PRNT_ALL;
432         }
433         if (!(opt_srv + opt_clt))
434                 opt_srv = opt_clt = 1;
435         if (!(opt_prt & 0xfff)) {
436                 opt_prt |= PRNT_CALLS + PRNT_RPC;
437         }
438         if (!(opt_prt & 0xe000)) {
439                 opt_prt |= PRNT_AUTO;
440         }
441         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
442                 fprintf(stderr,
443                         "You requested file handle or request cache "
444                         "statistics while using the -c option.\n"
445                         "This information is available only for the NFS "
446                         "server.\n");
447         }
448
449         if (opt_since || opt_sleep) {
450                 serverinfo = srvinfo_old;
451                 serverinfo_tmp = srvinfo;
452                 clientinfo = cltinfo_old;
453                 clientinfo_tmp = cltinfo;
454         }
455
456         if (opt_srv)
457                 get_stats(serverfile, serverinfo, &opt_srv, opt_clt, 1);
458         if (opt_clt)
459                 get_stats(clientfile, clientinfo, &opt_clt, opt_srv, 0);
460
461         if (opt_sleep && !sleep_time) {
462                 starttime = time(NULL);
463                 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
464                 if (sigaction(SIGINT, &act, NULL) != 0) {
465                         fprintf(stderr, "Error: couldn't register for signal and pause.\n");
466                         return 1;
467                 }
468                 pause();
469         }
470
471         if (opt_since || (opt_sleep && !sleep_time)) {
472                 if (opt_srv) {
473                         get_stats(NFSSRVSTAT, serverinfo_tmp, &opt_srv, opt_clt, 1);
474                         diff_stats(serverinfo_tmp, serverinfo, 1);
475                 }
476                 if (opt_clt) {
477                         get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
478                         diff_stats(clientinfo_tmp, clientinfo, 0);
479                 }
480         }
481         if(sleep_time) {
482                 while(1) {
483                         if (opt_srv) {
484                                 get_stats(NFSSRVSTAT, serverinfo_tmp , &opt_srv, opt_clt, 1);
485                                 diff_stats(serverinfo_tmp, serverinfo, 1);
486                         }
487                         if (opt_clt) {
488                                 get_stats(NFSCLTSTAT, clientinfo_tmp, &opt_clt, opt_srv, 0);
489                                 diff_stats(clientinfo_tmp, clientinfo, 0);
490                         }
491                         if (opt_list) {
492                                 print_stats_list(opt_srv, opt_clt, opt_prt);
493                         } else {
494                                 print_all_stats(opt_srv, opt_clt, opt_prt);
495                         }
496                         fflush(stdout);
497
498                         if (opt_srv)
499                                 update_old_counters(serverinfo_tmp, serverinfo);
500                         if (opt_clt)
501                                 update_old_counters(clientinfo_tmp, clientinfo);
502
503                         sleep(sleep_time);
504                 }       
505         } else {
506                 if (opt_list) {
507                         print_stats_list(opt_srv, opt_clt, opt_prt);
508                 } else {
509                         print_all_stats(opt_srv, opt_clt, opt_prt);
510                 }
511         }
512
513         return 0;
514 }
515
516 static void
517 print_all_stats (int opt_srv, int opt_clt, int opt_prt)
518 {
519         if (opt_srv)
520                 print_server_stats(opt_prt);
521
522         if (opt_clt)
523                 print_client_stats(opt_prt);
524 }
525
526 static void 
527 print_server_stats(int opt_prt) 
528 {
529         if (opt_prt & PRNT_NET) {
530                 if (opt_sleep && !has_rpcstats(srvnetinfo, 4)) {
531                 } else {
532                         print_numbers( LABEL_srvnet
533                                 "packets    udp        tcp        tcpconn\n",
534                         srvnetinfo, 4);
535                         printf("\n");
536                 }
537         }
538         if (opt_prt & PRNT_RPC) {
539                 if (opt_sleep && !has_rpcstats(srvrpcinfo, 5)) {
540                         ;
541                 } else {
542                         print_numbers(LABEL_srvrpc
543                                 "calls      badcalls   badclnt    badauth    xdrcall\n",
544                                 srvrpcinfo, 5);
545                         printf("\n");
546                 }
547         }
548         if (opt_prt & PRNT_RC) {
549                 if (opt_sleep && !has_rpcstats(srvrcinfo, 3)) {
550                         ;
551                 } else {
552                         print_numbers(LABEL_srvrc
553                                 "hits       misses     nocache\n",
554                                 srvrcinfo, 3);
555                         printf("\n");
556                 }
557         }
558
559         /*
560          * 2.2 puts all fh-related info after the 'rc' header
561          * 2.4 puts all fh-related info after the 'fh' header, but relocates
562          *     'stale' to the start and swaps dir and nondir :-(  
563          *     We preseve the 2.2 order
564          */
565         if (opt_prt & PRNT_FH) {
566                 if (get_stat_info("fh", srvinfo)) {     /* >= 2.4 */
567                         int t = srvfhinfo[3];
568                         srvfhinfo[3]=srvfhinfo[4];
569                         srvfhinfo[4]=t;
570                         
571                         srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
572                         
573                         print_numbers(
574                                 LABEL_srvfh
575                                 "lookup     anon       ncachedir  ncachedir  stale\n",
576                                 srvfhinfo + 1, 5);
577                 } else                                  /* < 2.4 */
578                         print_numbers(
579                                 LABEL_srvfh
580                                 "lookup     anon       ncachedir  ncachedir  stale\n",
581                                 srvrcinfo + 3, 5);
582                 printf("\n");
583         }
584         if (opt_prt & PRNT_CALLS) {
585                 int has_v2_stats = has_stats(srvproc2info, SRVPROC2_SZ+2);
586                 int has_v3_stats = has_stats(srvproc3info, SRVPROC3_SZ+2);
587                 int has_v4_stats = has_stats(srvproc4info, SRVPROC4_SZ+2);
588
589                 if ((opt_prt & PRNT_V2) || 
590                                 ((opt_prt & PRNT_AUTO) && has_v2_stats)) {
591                         if (!opt_sleep || has_v2_stats) {
592                                 print_callstats(LABEL_srvproc2,
593                                         nfsv2name, srvproc2info + 1, 
594                                         sizeof(nfsv2name)/sizeof(char *));
595                         }
596                 }
597                 if ((opt_prt & PRNT_V3) || 
598                                 ((opt_prt & PRNT_AUTO) && has_v3_stats)) {
599                         if (!opt_sleep || has_v3_stats) {
600                                 print_callstats(LABEL_srvproc3,
601                                         nfsv3name, srvproc3info + 1, 
602                                         sizeof(nfsv3name)/sizeof(char *));
603                         }
604                 }
605                 if ((opt_prt & PRNT_V4) || 
606                                 ((opt_prt & PRNT_AUTO) && has_v4_stats)) {
607                         if (!opt_sleep || has_v4_stats) {
608                                 print_callstats( LABEL_srvproc4,
609                                         nfssrvproc4name, srvproc4info + 1, 
610                                         sizeof(nfssrvproc4name)/sizeof(char *));
611                                 print_callstats(LABEL_srvproc4ops,
612                                         nfssrvproc4opname, srvproc4opsinfo + 1, 
613                                         sizeof(nfssrvproc4opname)/sizeof(char *));
614                         }
615                 }
616         }
617 }
618 static void
619 print_client_stats(int opt_prt) 
620 {
621         if (opt_prt & PRNT_NET) {
622                 if (opt_sleep && !has_rpcstats(cltnetinfo, 4)) {
623                         ;
624                 } else { 
625                         print_numbers(LABEL_cltnet
626                                 "packets    udp        tcp        tcpconn\n",
627                                 cltnetinfo, 4);
628                         printf("\n");
629                 }
630         }
631         if (opt_prt & PRNT_RPC) {
632                 if (opt_sleep && !has_rpcstats(cltrpcinfo, 3)) {
633                         ;
634                 } else {
635                         print_numbers(LABEL_cltrpc
636                                 "calls      retrans    authrefrsh\n",
637                                 cltrpcinfo, 3);
638                         printf("\n");
639                 }
640         }
641         if (opt_prt & PRNT_CALLS) {
642                 int has_v2_stats = has_stats(cltproc2info, CLTPROC2_SZ+2);
643                 int has_v3_stats = has_stats(cltproc3info, CLTPROC3_SZ+2);
644                 int has_v4_stats = has_stats(cltproc4info, CLTPROC4_SZ+2);
645                 if ((opt_prt & PRNT_V2) || 
646                                 ((opt_prt & PRNT_AUTO) && has_v2_stats)) {
647                         if (!opt_sleep || has_v2_stats) {
648                                 print_callstats(LABEL_cltproc2,
649                                         nfsv2name, cltproc2info + 1,  
650                                         sizeof(nfsv2name)/sizeof(char *));
651                         }
652                 }
653                 if ((opt_prt & PRNT_V3) || 
654                                 ((opt_prt & PRNT_AUTO) && has_v3_stats)) {
655                         if (!opt_sleep || has_v3_stats) {
656                                 print_callstats(LABEL_cltproc3,
657                                         nfsv3name, cltproc3info + 1, 
658                                         sizeof(nfsv3name)/sizeof(char *));
659                         }
660                 }
661                 if ((opt_prt & PRNT_V4) || 
662                                 ((opt_prt & PRNT_AUTO) && has_v4_stats)) {
663                         if (!opt_sleep || has_v4_stats) {
664                                 print_callstats(LABEL_cltproc4,
665                                         nfscltproc4name, cltproc4info + 1,  
666                                         sizeof(nfscltproc4name)/sizeof(char *));
667                         }
668                 }
669         }
670 }
671
672 static void
673 print_clnt_list(int opt_prt) 
674 {
675         if (opt_prt & PRNT_CALLS) {
676                 int has_v2_stats = has_stats(cltproc2info, CLTPROC2_SZ+2);
677                 int has_v3_stats = has_stats(cltproc3info, CLTPROC3_SZ+2);
678                 int has_v4_stats = has_stats(cltproc4info, CLTPROC4_SZ+2);
679                 if ((opt_prt & PRNT_V2) || 
680                                 ((opt_prt & PRNT_AUTO) && has_v2_stats)) {
681                         if (!opt_sleep || has_v2_stats) {
682                                 print_callstats_list("nfs v2 client",
683                                         nfsv2name, cltproc2info + 1,  
684                                         sizeof(nfsv2name)/sizeof(char *));
685                         }
686                 }
687                 if ((opt_prt & PRNT_V3) || 
688                                 ((opt_prt & PRNT_AUTO) && has_v3_stats)) {
689                         if (!opt_sleep || has_v3_stats) {
690                                 print_callstats_list("nfs v3 client",
691                                         nfsv3name, cltproc3info + 1, 
692                                         sizeof(nfsv3name)/sizeof(char *));
693                         }
694                 }
695                 if ((opt_prt & PRNT_V4) || 
696                                 ((opt_prt & PRNT_AUTO) && has_v4_stats)) {
697                         if (!opt_sleep || has_v4_stats) {
698                                 print_callstats_list("nfs v4 client",
699                                         nfscltproc4name, cltproc4info + 1,  
700                                         sizeof(nfscltproc4name)/sizeof(char *));
701                         }
702                 }
703         }
704 }
705 static void
706 print_serv_list(int opt_prt) 
707 {
708         if (opt_prt & PRNT_CALLS) {
709                 int has_v2_stats = has_stats(srvproc2info, SRVPROC2_SZ+2);
710                 int has_v3_stats = has_stats(srvproc3info, SRVPROC3_SZ+2);
711                 int has_v4_stats = has_stats(srvproc4info, SRVPROC4_SZ+2);
712                 if ((opt_prt & PRNT_V2) || 
713                                 ((opt_prt & PRNT_AUTO) && has_v2_stats)) {
714                         if (!opt_sleep || has_v2_stats) {
715                                 print_callstats_list("nfs v2 server",
716                                         nfsv2name, srvproc2info + 1, 
717                                         sizeof(nfsv2name)/sizeof(char *));
718                         }
719                 }
720                 if ((opt_prt & PRNT_V3) || 
721                                 ((opt_prt & PRNT_AUTO) && has_v3_stats)) {
722                         if (!opt_sleep || has_v3_stats) {
723                                 print_callstats_list("nfs v3 server",
724                                         nfsv3name, srvproc3info + 1, 
725                                         sizeof(nfsv3name)/sizeof(char *));
726                         }
727                 }
728                 if ((opt_prt & PRNT_V4) || 
729                                 ((opt_prt & PRNT_AUTO) && has_v4_stats)) {
730                         if (!opt_sleep || has_v4_stats) {
731                                 print_callstats_list("nfs v4 server",
732                                         nfssrvproc4name, srvproc4info + 1, 
733                                         sizeof(nfssrvproc4name)/sizeof(char *));
734                                 print_callstats_list("nfs v4 servop",
735                                         nfssrvproc4opname, srvproc4opsinfo + 1, 
736                                         sizeof(nfssrvproc4opname)/sizeof(char *));
737                         }
738                 }
739         }
740 }
741 static void
742 print_stats_list(int opt_srv, int opt_clt, int opt_prt) 
743 {
744         if (opt_srv)
745                 print_serv_list(opt_prt);
746
747         if (opt_clt)
748                 print_clnt_list(opt_prt);
749 }
750
751 static statinfo *
752 get_stat_info(const char *sp, struct statinfo *statp)
753 {
754         struct statinfo *ip;
755
756         for (ip = statp; ip->tag; ip++) {
757                 if (!strcmp(sp, ip->tag))
758                         return ip;
759         }
760
761         return NULL;
762 }
763
764 static void
765 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
766 {
767         unsigned int    i;
768
769         fputs(hdr, stdout);
770         for (i = 0; i < nr; i++)
771                 printf("%s%-8u", i? "   " : "", info[i]);
772         printf("\n");
773 }
774
775 static void
776 print_callstats(const char *hdr, const char **names,
777                                  unsigned int *info, unsigned int nr)
778 {
779         unsigned long long      total;
780         unsigned long long      pct;
781         unsigned int            i, j;
782
783         fputs(hdr, stdout);
784         for (i = 0, total = 0; i < nr; i++)
785                 total += info[i];
786         if (!total)
787                 total = 1;
788         for (i = 0; i < nr; i += 6) {
789                 for (j = 0; j < 6 && i + j < nr; j++)
790                         printf("%-13s", names[i+j]);
791                 printf("\n");
792                 for (j = 0; j < 6 && i + j < nr; j++) {
793                         pct = ((unsigned long long) info[i+j]*100)/total;
794                         printf("%-8u%3llu%% ", info[i+j], pct);
795                 }
796                 printf("\n");
797         }
798         printf("\n");
799 }
800
801 static void
802 print_callstats_list(const char *hdr, const char **names,
803                         unsigned int *callinfo, unsigned int nr)
804 {
805         unsigned long long      calltotal;
806         unsigned int                    i;
807
808         for (i = 0, calltotal = 0; i < nr; i++) {
809                 calltotal += callinfo[i];
810         }
811         if (!calltotal)
812                 return;
813         printf("%13s %13s %8llu \n", hdr, "total:", calltotal);
814         printf("------------- ------------- --------\n");
815         for (i = 0; i < nr; i++) {
816                         if (callinfo[i])
817                                 printf("%13s %12s: %8u \n", hdr, names[i], callinfo[i]);
818         }
819         printf("\n");
820                 
821 }
822
823
824 /* returns 0 on success, 1 otherwise */
825 static int
826 parse_raw_statfile(const char *name, struct statinfo *statp)
827 {
828         char    buffer[4096], *next;
829         FILE    *fp;
830
831         /* Being unable to read e.g. the nfsd stats file shouldn't
832          * be a fatal error -- it usually means the module isn't loaded.
833          */
834         if ((fp = fopen(name, "r")) == NULL) {
835                 // fprintf(stderr, "Warning: %s: %m\n", name);
836                 return 1;
837         }
838
839         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
840                 struct statinfo *ip;
841                 char            *sp, *line = buffer;
842                 unsigned int    i, cnt;
843                 unsigned int    total = 0;
844
845                 if ((next = strchr(line, '\n')) != NULL)
846                         *next++ = '\0';
847                 if (!(sp = strtok(line, " \t")))
848                         continue;
849
850                 ip = get_stat_info(sp, statp);
851                 if (!ip)
852                         continue;
853
854                 cnt = ip->nrvals;
855
856                 for (i = 0; i < cnt; i++) {
857                         if (!(sp = strtok(NULL, " \t")))
858                                 break;
859                         ip->valptr[i] = (unsigned int) strtoul(sp, NULL, 0);
860                         total += ip->valptr[i];
861                 }
862                 ip->valptr[cnt - 1] = total;
863         }
864
865         fclose(fp);
866         return 0;
867 }
868
869 /* returns 0 on success, 1 otherwise */
870 static int
871 parse_pretty_statfile(const char *filename, struct statinfo *info)
872 {
873         int numvals, curindex, numconsumed, n, err = 1;
874         unsigned int sum;
875         char buf[4096], *bufp, *fmt, is_proc;
876         FILE *fp = NULL;
877         struct statinfo *ip;
878
879         if ((fp = fopen(filename, "r")) == NULL)
880                 //err(2, "Unable to open statfile '%s'.\n", filename);
881                 goto out;
882
883         while (fgets(buf, sizeof(buf), fp) != NULL) {
884                 for (ip = info; ip->tag; ip++) {
885                         if (strcmp(buf, ip->label))
886                                 continue;
887
888                         sum = 0;
889                         numvals = ip->nrvals - 1;
890                         is_proc = strncmp("proc", ip->tag, 4) ? 0 : 1;
891                         if (is_proc) {
892                                 fmt = " %u %*u%% %n";
893                                 curindex = 1;
894                                 ip->valptr[0] = 0;
895                         } else {
896                                 fmt = " %u %n";
897                                 curindex = 0;
898                         }
899 more_stats:
900                         /* get (and skip) header */
901                         if (fgets(buf, sizeof(buf), fp) == NULL) {
902                                 fprintf(stderr, "Failed to locate header after "
903                                                 "label for '%s' in %s.\n",
904                                                 ip->tag, filename);
905                                 goto out;
906                         }
907                         /* no header -- done with this "tag" */
908                         if (*buf == '\n') {
909                                 ip->valptr[numvals] = sum;
910                                 break;
911                         }
912                         /* get stats */
913                         if (fgets(buf, sizeof(buf), fp) == NULL) {
914                                 fprintf(stderr, "Failed to locate stats after "
915                                                 "header for '%s' in %s.\n",
916                                                 ip->tag, filename);
917                                 goto out;
918                         }
919                         bufp = buf;
920                         for (; curindex < numvals; curindex++) {
921                                 n = sscanf(bufp, fmt, &ip->valptr[curindex],
922                                                 &numconsumed);
923                                 if (n != 1)
924                                         break;
925                                 if (is_proc) {
926                                         ip->valptr[0]++;
927                                         sum++;
928                                 }
929                                 sum += ip->valptr[curindex];
930                                 bufp += numconsumed;
931                         }
932                         goto more_stats;
933                 }
934         }
935         err = 0;
936 out:
937         if (fp)
938                 fclose(fp);
939         return err;
940 }
941
942 static int
943 mounts(const char *name)
944 {
945         char    buffer[4096], *next;
946         FILE    *fp;
947
948         /* Being unable to read e.g. the nfsd stats file shouldn't
949          * be a fatal error -- it usually means the module isn't loaded.
950          */
951         if ((fp = fopen(name, "r")) == NULL) {
952                 fprintf(stderr, "Warning: %s: %m\n", name);
953                 return 0;
954         }
955
956         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
957                 char          *line = buffer;
958                 char          *device, *mount, *type, *flags;
959
960                 if ((next = strchr(line, '\n')) != NULL)
961                         *next = '\0';
962
963                 if (!(device = strtok(line, " \t")))
964                         continue;
965
966                 if (!(mount = strtok(NULL, " \t")))
967                         continue;
968
969                 if (!(type = strtok(NULL, " \t")))
970                         continue;
971
972                 if (strcmp(type, "nfs") && strcmp(type,"nfs4")) {
973                     continue;
974                 }
975
976                 if (!(flags = strtok(NULL, " \t")))
977                         continue;
978
979                 printf("%s from %s\n", mount, device);
980                 printf(" Flags:\t%s\n", flags);
981                 printf("\n");
982
983                 continue;
984         }
985
986         fclose(fp);
987         return 1;
988 }
989
990 static void
991 get_stats(const char *file, struct statinfo *info, int *opt, int other_opt,
992                 int is_srv)
993 {
994         FILE *fp;
995         char buf[10];
996         int err = 1;
997         char *label = is_srv ? "Server" : "Client";
998
999         /* try to guess what type of stat file we're dealing with */
1000         if ((fp = fopen(file, "r")) == NULL)
1001                 goto out;
1002         if (fgets(buf, 10, fp) == NULL)
1003                 goto out;
1004         if (!strncmp(buf, "net ", 4)) {
1005                 /* looks like raw client stats */
1006                 if (is_srv) {
1007                         fprintf(stderr, "Warning: no server info present in "
1008                                         "raw client stats file.\n");
1009                         *opt = 0;
1010                 } else
1011                         err = parse_raw_statfile(file, info);
1012         } else if (!strncmp(buf, "rc ", 3)) {
1013                 /* looks like raw server stats */
1014                 if (!is_srv) {
1015                         fprintf(stderr, "Warning: no client info present in "
1016                                         "raw server stats file.\n");
1017                         *opt = 0;
1018                 } else
1019                         err = parse_raw_statfile(file, info);
1020         } else
1021                 /* looks like pretty client and server stats */
1022                 err = parse_pretty_statfile(file, info);
1023 out:
1024         if (fp)
1025                 fclose(fp);
1026         if (err) {
1027                 if (!other_opt) {
1028                         fprintf(stderr, "Error: No %s Stats (%s: %m). \n",
1029                                         label, file);
1030                         exit(2);
1031                 }
1032                 *opt = 0;
1033         }
1034 }
1035
1036 /*
1037  * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
1038  * denotes the number of subsequent entries.  statinfo value arrays contain an additional
1039  * field at the end which contains the sum of all previous elements in the array -- so,
1040  * there are stats if the sum's greater than the entry-count.
1041  */
1042 static int
1043 has_stats(const unsigned int *info, int nr)
1044 {
1045         return (info[0] && info[nr-1] > info[0]);
1046 }
1047 static int
1048 has_rpcstats(const unsigned int *info, int size)
1049 {
1050         int i, cnt;
1051
1052         for (i=0, cnt=0; i < size; i++)
1053                 cnt += info[i];
1054         return cnt;
1055 }
1056
1057 /*
1058  * take the difference of each individual stat value in 'new' and 'old'
1059  * and store the results back into 'new'
1060  */
1061 static void
1062 diff_stats(struct statinfo *new, struct statinfo *old, int is_srv)
1063 {
1064         int i, j, nodiff_first_index, should_diff;
1065
1066         /*
1067          * Different stat types have different formats in the /proc
1068          * files: for the proc2/3/4-type stats, the first entry has
1069          * the total number of subsequent entries; one does not want
1070          * to diff that first entry.  The other stat types aren't like
1071          * this.  So, we diff a given entry if it's not of one of the
1072          * procX types ("i" < 2 for clt, < 4 for srv), or if it's not
1073          * the first entry ("j" > 0).
1074          */
1075         nodiff_first_index = 2 + (2 * is_srv);
1076
1077         for (i = 0; old[i].tag; i++) {
1078                 for (j = 0; j < new[i].nrvals; j++) {
1079                         should_diff = (i < nodiff_first_index || j > 0);
1080                         if (should_diff)
1081                                 new[i].valptr[j] -= old[i].valptr[j];
1082                 }
1083
1084                 /*
1085                  * Make sure that the "totals" entry (last value in
1086                  * each stat array) for the procX-type stats has the
1087                  * "numentries" entry's (first value in procX-type
1088                  * stat arrays) constant value added-back after the
1089                  * diff -- i.e., it should always be included in the
1090                  * total.
1091                  */
1092                 if (!strncmp("proc", new[i].tag, 4) && old[i].valptr[0])
1093                         new[i].valptr[new[i].nrvals - 1] += new[i].valptr[0];
1094         }
1095 }
1096
1097 static void
1098 unpause(int sig)
1099 {
1100         double time_diff;
1101         int minutes, seconds;
1102         time_t endtime;
1103
1104         endtime = time(NULL);
1105         time_diff = difftime(endtime, starttime);
1106         minutes = time_diff / 60;
1107         seconds = (int)time_diff % 60;
1108         printf("Signal %d received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", sig, minutes, seconds);
1109 }
1110
1111 static void
1112 update_old_counters(struct statinfo *new, struct statinfo *old)
1113 {
1114         int z, i;
1115         for (z = 0; old[z].tag; z++) 
1116                 for (i = 0; i <= old[z].nrvals; i++) 
1117                         old[z].valptr[i] += new[z].valptr[i];
1118
1119 }