added version output control (-2 -3 -4)
[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 #include "config.h"
8
9 #define NFSSVCSTAT      "/proc/net/rpc/nfsd"
10 #define NFSCLTSTAT      "/proc/net/rpc/nfs"
11
12 #define MOUNTSFILE      "/proc/mounts"
13
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <unistd.h>
17 #include <getopt.h>
18 #include <string.h>
19 #include <fcntl.h>
20 #include <errno.h>
21
22 #define MAXNRVALS       32
23
24 static unsigned int     svcv2info[20];  /* NFSv2 call counts ([0] == 18) */
25 static unsigned int     cltv2info[20];  /* NFSv2 call counts ([0] == 18) */
26 static unsigned int     svcv3info[24];  /* NFSv3 call counts ([0] == 22) */
27 static unsigned int     cltv3info[24];  /* NFSv3 call counts ([0] == 22) */
28 static unsigned int     svcv4info[4];   /* NFSv4 call counts ([0] == 2) */
29 static unsigned int     cltv4info[34];  /* NFSv4 call counts ([0] == 32) */
30 static unsigned int     svcnetinfo[5];  /* 0  # of received packets
31                                          * 1  UDP packets
32                                          * 2  TCP packets
33                                          * 3  TCP connections
34                                          */
35 static unsigned int     cltnetinfo[5];  /* 0  # of received packets
36                                          * 1  UDP packets
37                                          * 2  TCP packets
38                                          * 3  TCP connections
39                                          */
40
41 static unsigned int     svcrpcinfo[6];  /* 0  total # of RPC calls
42                                          * 1  total # of bad calls
43                                          * 2  bad format
44                                          * 3  authentication failed
45                                          * 4  unknown client
46                                          */
47 static unsigned int     cltrpcinfo[4];  /* 0  total # of RPC calls
48                                          * 1  retransmitted calls
49                                          * 2  cred refreshs
50                                          */
51
52 static unsigned int     svcrcinfo[9];   /* 0  repcache hits
53                                          * 1  repcache hits
54                                          * 2  uncached reqs
55                                          * (for pre-2.4 kernels:)
56                                          * 3  FH lookups
57                                          * 4  'anon' FHs
58                                          * 5  noncached non-directories
59                                          * 6  noncached directories
60                                          * 7  stale
61                                          */
62
63 static unsigned int     svcfhinfo[7];   /* (for kernels >= 2.4.0)
64                                          * 0  stale
65                                          * 1  FH lookups
66                                          * 2  'anon' FHs
67                                          * 3  noncached directories
68                                          * 4  noncached non-directories
69                                          * leave hole to relocate stale for order
70                                          *    compatability.
71                                          */
72
73 static const char *     nfsv2name[18] = {
74         "null", "getattr", "setattr", "root",   "lookup",  "readlink",
75         "read", "wrcache", "write",   "create", "remove",  "rename",
76         "link", "symlink", "mkdir",   "rmdir",  "readdir", "fsstat"
77 };
78
79 static const char *     nfsv3name[22] = {
80         "null",   "getattr", "setattr",  "lookup", "access",  "readlink",
81         "read",   "write",   "create",   "mkdir",  "symlink", "mknod",
82         "remove", "rmdir",   "rename",   "link",   "readdir", "readdirplus",
83         "fsstat", "fsinfo",  "pathconf", "commit"
84 };
85
86 static const char *     nfssvrv4name[2] = {
87         "null",
88         "compound",
89 };
90
91 static const char *     nfscltv4name[32] = {
92         "null",      "read",      "write",   "commit",      "open",        "open_conf",
93         "open_noat", "open_dgrd", "close",   "setattr",     "fsinfo",      "renew",
94         "setclntid", "confirm",   "lock",
95         "lockt",     "locku",     "access",  "getattr",     "lookup",      "lookup_root",
96         "remove",    "rename",    "link",    "symlink",     "create",      "pathconf",
97         "statfs",    "readlink",  "readdir", "server_caps", "delegreturn",
98 };
99
100 typedef struct statinfo {
101         char            *tag;
102         int             nrvals;
103         unsigned int *  valptr;
104 } statinfo;
105
106 #define STRUCTSIZE(x)   sizeof(x)/sizeof(*x)
107
108 static statinfo         svcinfo[] = {
109         { "net",        STRUCTSIZE(svcnetinfo), svcnetinfo },
110         { "rpc",        STRUCTSIZE(svcrpcinfo), svcrpcinfo },
111         { "rc",         STRUCTSIZE(svcrcinfo),  svcrcinfo  },
112         { "fh",         STRUCTSIZE(svcfhinfo),  svcfhinfo  },
113         { "proc2",      STRUCTSIZE(svcv2info),  svcv2info  },
114         { "proc3",      STRUCTSIZE(svcv3info),  svcv3info  },
115         { "proc4",      STRUCTSIZE(svcv4info),  svcv4info  },
116         { NULL,         0,                      NULL       }
117 };
118
119 static statinfo         cltinfo[] = {
120         { "net",        STRUCTSIZE(cltnetinfo), cltnetinfo },
121         { "rpc",        STRUCTSIZE(cltrpcinfo), cltrpcinfo },
122         { "proc2",      STRUCTSIZE(cltv2info),  cltv2info  },
123         { "proc3",      STRUCTSIZE(cltv3info),  cltv3info  },
124         { "proc4",      STRUCTSIZE(cltv4info),  cltv4info  },
125         { NULL,         0,                      NULL       }
126 };
127
128 static void             print_numbers(const char *, unsigned int *,
129                                         unsigned int);
130 static void             print_callstats(const char *, const char **,
131                                         unsigned int *, unsigned int);
132 static int              parse_statfile(const char *, struct statinfo *);
133
134 static statinfo         *get_stat_info(const char *, struct statinfo *);
135
136 static int             mounts(const char *);
137
138 #define PRNT_CALLS      0x0001
139 #define PRNT_RPC        0x0002
140 #define PRNT_NET        0x0004
141 #define PRNT_FH         0x0008
142 #define PRNT_RC         0x0010
143 #define PRNT_AUTO       0x1000
144 #define PRNT_V2         0x2000
145 #define PRNT_V3         0x4000
146 #define PRNT_V4         0x8000
147 #define PRNT_ALL        0x0fff
148
149 int versions[] = {
150         PRNT_V2,
151         PRNT_V3,
152         PRNT_V4
153 };
154
155 void usage(char *name)
156 {
157         printf("Usage: %s [OPTION]...\n\
158 \n\
159   -m, --mounted\t\tShow statistics on mounted NFS filesystems\n\
160   -c, --client\t\tShow NFS client statistics\n\
161   -s, --server\t\tShow NFS server statistics\n\
162   -2\t\t\tShow NFS version 2 statistics\n\
163   -3\t\t\tShow NFS version 3 statistics\n\
164   -4\t\t\tShow NFS version 4 statistics\n\
165   -o [facility]\t\tShow statistics on particular facilities.\n\
166      nfs\tNFS protocol information\n\
167      rpc\tGeneral RPC information\n\
168      net\tNetwork layer statistics\n\
169      fh\t\tUsage information on the server's file handle cache\n\
170      rc\t\tUsage information on the server's request reply cache\n\
171      all\tSelect all of the above\n\
172   -v, --verbose, --all\tSame as '-o all'\n\
173   -r, --rpc\t\tShow RPC statistics\n\
174   -n, --nfs\t\tShow NFS statistics\n\
175   --version\t\tShow program version\n\
176   --help\t\tWhat you just did\n\
177 \n", name);
178         exit(0);
179 }
180
181 static struct option longopts[] =
182 {
183         { "acl", 0, 0, 'a' },
184         { "all", 0, 0, 'v' },
185         { "auto", 0, 0, '\3' },
186         { "client", 0, 0, 'c' },
187         { "mounts", 0, 0, 'm' },
188         { "nfs", 0, 0, 'n' },
189         { "rpc", 0, 0, 'r' },
190         { "server", 0, 0, 's' },
191         { "verbose", 0, 0, 'v' },
192         { "zero", 0, 0, 'z' },
193         { "help", 0, 0, '\1' },
194         { "version", 0, 0, '\2' },
195         { NULL, 0, 0, 0 }
196 };
197
198 int
199 main(int argc, char **argv)
200 {
201         int             opt_all = 0,
202                         opt_srv = 0,
203                         opt_clt = 0,
204                         srv_info = 0,
205                         clt_info = 0,
206                         opt_prt = 0;
207         int             c;
208         char           *progname;
209  
210         if ((progname = strrchr(argv[0], '/')))
211                 progname++;
212         else
213                 progname = argv[0];
214
215         while ((c = getopt_long(argc, argv, "234acmno:vrsz\1\2", longopts, NULL)) != EOF) {
216                 switch (c) {
217                 case 'a':
218                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
219                         return -1;
220                 case 'c':
221                         opt_clt = 1;
222                         break;
223                 case 'n':
224                         opt_prt |= PRNT_CALLS;
225                         break;
226                 case 'o':
227                         if (!strcmp(optarg, "nfs"))
228                                 opt_prt |= PRNT_CALLS;
229                         else if (!strcmp(optarg, "rpc"))
230                                 opt_prt |= PRNT_RPC;
231                         else if (!strcmp(optarg, "net"))
232                                 opt_prt |= PRNT_NET;
233                         else if (!strcmp(optarg, "rc"))
234                                 opt_prt |= PRNT_RC;
235                         else if (!strcmp(optarg, "fh"))
236                                 opt_prt |= PRNT_FH;
237                         else if (!strcmp(optarg, "all"))
238                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
239                         else {
240                                 fprintf(stderr, "nfsstat: unknown category: "
241                                                 "%s\n", optarg);
242                                 return 2;
243                         }
244                         break;
245                 case '2':
246                 case '3':
247                 case '4':
248                         opt_prt |= versions[c - '2'];
249                         break;
250                 case 'v':
251                         opt_all = 1;
252                         break;
253                 case '\3':
254                         opt_prt |= PRNT_AUTO;
255                         break;
256                 case 'r':
257                         opt_prt |= PRNT_RPC;
258                         break;
259                 case 's':
260                         opt_srv = 1;
261                         break;
262                 case 'z':
263                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
264                                         "not yet supported\n");
265                         return 2;
266                 case 'm':
267                         return mounts(MOUNTSFILE);
268                 case '\1':
269                         usage(progname);
270                         return 0;
271                 case '\2':
272                         fprintf(stdout, "nfsstat: " VERSION "\n");
273                         return 0;
274                 default:
275                         printf("Try `%s --help' for more information.\n", progname);
276                         return -1;
277                 }
278         }
279
280         if (opt_all) {
281                 opt_srv = opt_clt = 1;
282                 opt_prt |= PRNT_ALL;
283         }
284         if (!(opt_srv + opt_clt))
285                 opt_srv = opt_clt = 1;
286         if (!(opt_prt & 0xfff)) {
287                 opt_prt |= PRNT_CALLS + PRNT_RPC;
288         }
289         if (!(opt_prt & 0xe000)) {
290                 opt_prt |= PRNT_AUTO;
291         }
292         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
293                 fprintf(stderr,
294                         "You requested file handle or request cache "
295                         "statistics while using the -c option.\n"
296                         "This information is available only for the NFS "
297                         "server.\n");
298         }
299
300         if (opt_srv) {
301                 srv_info = parse_statfile(NFSSVCSTAT, svcinfo);
302                 if (srv_info == 0 && opt_clt == 0) {
303                         fprintf(stderr, "Warning: No Server Stats (%s: %m).\n", NFSSVCSTAT);
304                         return 2;
305                 }
306                 if (srv_info == 0)
307                         opt_srv = 0;
308         }
309
310         if (opt_clt) {
311                 clt_info = parse_statfile(NFSCLTSTAT, cltinfo);
312                 if (opt_srv == 0 && clt_info == 0) {
313                         fprintf(stderr, "Warning: No Client Stats (%s: %m).\n", NFSCLTSTAT);
314                         return 2;
315                 }
316                 if (clt_info == 0)
317                         opt_clt = 0;
318         }
319
320         if (opt_srv) {
321                 if (opt_prt & PRNT_NET) {
322                         print_numbers(
323                         "Server packet stats:\n"
324                         "packets    udp        tcp        tcpconn\n",
325                         svcnetinfo, 4
326                         );
327                         printf("\n");
328                 }
329                 if (opt_prt & PRNT_RPC) {
330                         print_numbers(
331                         "Server rpc stats:\n"
332                         "calls      badcalls   badauth    badclnt    xdrcall\n",
333                         svcrpcinfo, 5
334                         );
335                         printf("\n");
336                 }
337                 if (opt_prt & PRNT_RC) {
338                         print_numbers(
339                         "Server reply cache:\n"
340                         "hits       misses     nocache\n",
341                         svcrcinfo, 3
342                         );
343                         printf("\n");
344                 }
345
346                 /*
347                  * 2.2 puts all fh-related info after the 'rc' header
348                  * 2.4 puts all fh-related info after the 'fh' header, but relocates
349                  *     'stale' to the start and swaps dir and nondir :-(  
350                  *     We preseve the 2.2 order
351                  */
352                 if (opt_prt & PRNT_FH) {
353                         if (get_stat_info("fh", svcinfo)) {     /* >= 2.4 */
354                                 int t = svcfhinfo[3];
355                                 svcfhinfo[3]=svcfhinfo[4];
356                                 svcfhinfo[4]=t;
357                                 
358                                 svcfhinfo[5]=svcfhinfo[0]; /* relocate 'stale' */
359                                 
360                                 print_numbers(
361                                         "Server file handle cache:\n"
362                                         "lookup     anon       ncachedir  ncachedir  stale\n",
363                                         svcfhinfo + 1, 5);
364                         } else                                  /* < 2.4 */
365                                 print_numbers(
366                                         "Server file handle cache:\n"
367                                         "lookup     anon       ncachedir  ncachedir  stale\n",
368                                         svcrcinfo + 3, 5);
369                         printf("\n");
370                 }
371                 if (opt_prt & PRNT_CALLS) {
372                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && svcv2info[0] && svcv2info[svcv2info[0]+1] != svcv2info[0]))
373                                 print_callstats(
374                                 "Server nfs v2:\n",
375                                     nfsv2name, svcv2info + 1, sizeof(nfsv2name)/sizeof(char *)
376                                 );
377                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && svcv3info[0] && svcv3info[svcv3info[0]+1] != svcv3info[0]))
378                                 print_callstats(
379                                 "Server nfs v3:\n",
380                                 nfsv3name, svcv3info + 1, sizeof(nfsv3name)/sizeof(char *)
381                                 );
382                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && svcv4info[0] && svcv4info[svcv4info[0]+1] != svcv4info[0]))
383                                 print_callstats(
384                                 "Server nfs v4:\n",
385                                 nfssvrv4name, svcv4info + 1, sizeof(nfssvrv4name)/sizeof(char *)
386                                 );
387                 }
388         }
389
390         if (opt_clt) {
391                 if (opt_prt & PRNT_NET) {
392                         print_numbers(
393                         "Client packet stats:\n"
394                         "packets    udp        tcp        tcpconn\n",
395                         cltnetinfo, 4
396                         );
397                         printf("\n");
398                 }
399                 if (opt_prt & PRNT_RPC) {
400                         print_numbers(
401                         "Client rpc stats:\n"
402                         "calls      retrans    authrefrsh\n",
403                         cltrpcinfo, 3
404                         );
405                         printf("\n");
406                 }
407                 if (opt_prt & PRNT_CALLS) {
408                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && cltv2info[0] && cltv2info[cltv2info[0]+1] != cltv2info[0]))
409                                 print_callstats(
410                                 "Client nfs v2:\n",
411                                 nfsv2name, cltv2info + 1,  sizeof(nfsv2name)/sizeof(char *)
412                                 );
413                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && cltv3info[0] && cltv3info[cltv3info[0]+1] != cltv3info[0]))
414                                 print_callstats(
415                                 "Client nfs v3:\n",
416                                 nfsv3name, cltv3info + 1, sizeof(nfsv3name)/sizeof(char *)
417                                 );
418                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && cltv4info[0] && cltv4info[cltv4info[0]+1] != cltv4info[0]))
419                                 print_callstats(
420                                 "Client nfs v4:\n",
421                                 nfscltv4name, cltv4info + 1,  sizeof(nfscltv4name)/sizeof(char *)
422                                 );
423                 }
424         }
425
426         return 0;
427 }
428
429 static statinfo *
430 get_stat_info(const char *sp, struct statinfo *statp)
431 {
432         struct statinfo *ip;
433
434         for (ip = statp; ip->tag; ip++) {
435                 if (!strcmp(sp, ip->tag))
436                         return ip;
437         }
438
439         return NULL;
440 }
441
442 static void
443 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
444 {
445         unsigned int    i;
446
447         fputs(hdr, stdout);
448         for (i = 0; i < nr; i++)
449                 printf("%s%-8d", i? "   " : "", info[i]);
450         printf("\n");
451 }
452
453 static void
454 print_callstats(const char *hdr, const char **names,
455                                  unsigned int *info, unsigned int nr)
456 {
457         unsigned long long      total;
458         unsigned long long      pct;
459         int             i, j;
460
461         fputs(hdr, stdout);
462         for (i = 0, total = 0; i < nr; i++)
463                 total += info[i];
464         if (!total)
465                 total = 1;
466         for (i = 0; i < nr; i += 6) {
467                 for (j = 0; j < 6 && i + j < nr; j++)
468                         printf("%-13s", names[i+j]);
469                 printf("\n");
470                 for (j = 0; j < 6 && i + j < nr; j++) {
471                         pct = ((unsigned long long) info[i+j]*100)/total;
472                         printf("%-8d%3llu%% ", info[i+j], pct);
473                 }
474                 printf("\n");
475         }
476         printf("\n");
477 }
478
479
480 static int
481 parse_statfile(const char *name, struct statinfo *statp)
482 {
483         char    buffer[4096], *next;
484         FILE    *fp;
485
486         /* Being unable to read e.g. the nfsd stats file shouldn't
487          * be a fatal error -- it usually means the module isn't loaded.
488          */
489         if ((fp = fopen(name, "r")) == NULL) {
490                 // fprintf(stderr, "Warning: %s: %m\n", name);
491                 return 0;
492         }
493
494         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
495                 struct statinfo *ip;
496                 char            *sp, *line = buffer;
497                 unsigned int    i, cnt;
498                 unsigned int    total = 0;
499
500                 if ((next = strchr(line, '\n')) != NULL)
501                         *next++ = '\0';
502                 if (!(sp = strtok(line, " \t")))
503                         continue;
504
505                 ip = get_stat_info(sp, statp);
506                 if (!ip)
507                         continue;
508
509                 cnt = ip->nrvals;
510
511                 for (i = 0; i < cnt; i++) {
512                         if (!(sp = strtok(NULL, " \t")))
513                                 break;
514                         ip->valptr[i] = atoi(sp);
515                         total += ip->valptr[i];
516                 }
517                 ip->valptr[i] = total;
518         }
519
520         fclose(fp);
521         return 1;
522 }
523
524 static int
525 mounts(const char *name)
526 {
527         char    buffer[4096], *next;
528         FILE    *fp;
529
530         /* Being unable to read e.g. the nfsd stats file shouldn't
531          * be a fatal error -- it usually means the module isn't loaded.
532          */
533         if ((fp = fopen(name, "r")) == NULL) {
534                 fprintf(stderr, "Warning: %s: %m\n", name);
535                 return 0;
536         }
537
538         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
539                 char          *line = buffer;
540                 char          *device, *mount, *type, *flags;
541
542                 if ((next = strchr(line, '\n')) != NULL)
543                         *next = '\0';
544
545                 if (!(device = strtok(line, " \t")))
546                         continue;
547
548                 if (!(mount = strtok(NULL, " \t")))
549                         continue;
550
551                 if (!(type = strtok(NULL, " \t")))
552                         continue;
553
554                 if (strcmp(type, "nfs")) {
555                     continue;
556                 }
557
558                 if (!(flags = strtok(NULL, " \t")))
559                         continue;
560
561                 printf("%s from %s\n", mount, device);
562                 printf(" Flags:\t%s\n", flags);
563                 printf("\n");
564
565                 continue;
566         }
567
568         fclose(fp);
569         return 1;
570 }