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