7e822a7a1efeeccb920c699e327e4ea371175c5c
[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 static int              has_stats(const unsigned int *);
153
154 #define PRNT_CALLS      0x0001
155 #define PRNT_RPC        0x0002
156 #define PRNT_NET        0x0004
157 #define PRNT_FH         0x0008
158 #define PRNT_RC         0x0010
159 #define PRNT_AUTO       0x1000
160 #define PRNT_V2         0x2000
161 #define PRNT_V3         0x4000
162 #define PRNT_V4         0x8000
163 #define PRNT_ALL        0x0fff
164
165 int versions[] = {
166         PRNT_V2,
167         PRNT_V3,
168         PRNT_V4
169 };
170
171 void usage(char *name)
172 {
173         printf("Usage: %s [OPTION]...\n\
174 \n\
175   -m, --mounted\t\tShow statistics on mounted NFS filesystems\n\
176   -c, --client\t\tShow NFS client statistics\n\
177   -s, --server\t\tShow NFS server statistics\n\
178   -2\t\t\tShow NFS version 2 statistics\n\
179   -3\t\t\tShow NFS version 3 statistics\n\
180   -4\t\t\tShow NFS version 4 statistics\n\
181   -o [facility]\t\tShow statistics on particular facilities.\n\
182      nfs\tNFS protocol information\n\
183      rpc\tGeneral RPC information\n\
184      net\tNetwork layer statistics\n\
185      fh\t\tUsage information on the server's file handle cache\n\
186      rc\t\tUsage information on the server's request reply cache\n\
187      all\tSelect all of the above\n\
188   -v, --verbose, --all\tSame as '-o all'\n\
189   -r, --rpc\t\tShow RPC statistics\n\
190   -n, --nfs\t\tShow NFS statistics\n\
191   --version\t\tShow program version\n\
192   --help\t\tWhat you just did\n\
193 \n", name);
194         exit(0);
195 }
196
197 static struct option longopts[] =
198 {
199         { "acl", 0, 0, 'a' },
200         { "all", 0, 0, 'v' },
201         { "auto", 0, 0, '\3' },
202         { "client", 0, 0, 'c' },
203         { "mounts", 0, 0, 'm' },
204         { "nfs", 0, 0, 'n' },
205         { "rpc", 0, 0, 'r' },
206         { "server", 0, 0, 's' },
207         { "verbose", 0, 0, 'v' },
208         { "zero", 0, 0, 'z' },
209         { "help", 0, 0, '\1' },
210         { "version", 0, 0, '\2' },
211         { NULL, 0, 0, 0 }
212 };
213
214 int
215 main(int argc, char **argv)
216 {
217         int             opt_all = 0,
218                         opt_srv = 0,
219                         opt_clt = 0,
220                         srv_info = 0,
221                         clt_info = 0,
222                         opt_prt = 0;
223         int             c;
224         char           *progname;
225  
226         if ((progname = strrchr(argv[0], '/')))
227                 progname++;
228         else
229                 progname = argv[0];
230
231         while ((c = getopt_long(argc, argv, "234acmno:vrsz\1\2", longopts, NULL)) != EOF) {
232                 switch (c) {
233                 case 'a':
234                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
235                         return -1;
236                 case 'c':
237                         opt_clt = 1;
238                         break;
239                 case 'n':
240                         opt_prt |= PRNT_CALLS;
241                         break;
242                 case 'o':
243                         if (!strcmp(optarg, "nfs"))
244                                 opt_prt |= PRNT_CALLS;
245                         else if (!strcmp(optarg, "rpc"))
246                                 opt_prt |= PRNT_RPC;
247                         else if (!strcmp(optarg, "net"))
248                                 opt_prt |= PRNT_NET;
249                         else if (!strcmp(optarg, "rc"))
250                                 opt_prt |= PRNT_RC;
251                         else if (!strcmp(optarg, "fh"))
252                                 opt_prt |= PRNT_FH;
253                         else if (!strcmp(optarg, "all"))
254                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
255                         else {
256                                 fprintf(stderr, "nfsstat: unknown category: "
257                                                 "%s\n", optarg);
258                                 return 2;
259                         }
260                         break;
261                 case '2':
262                 case '3':
263                 case '4':
264                         opt_prt |= versions[c - '2'];
265                         break;
266                 case 'v':
267                         opt_all = 1;
268                         break;
269                 case '\3':
270                         opt_prt |= PRNT_AUTO;
271                         break;
272                 case 'r':
273                         opt_prt |= PRNT_RPC;
274                         break;
275                 case 's':
276                         opt_srv = 1;
277                         break;
278                 case 'z':
279                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
280                                         "not yet supported\n");
281                         return 2;
282                 case 'm':
283                         return mounts(MOUNTSFILE);
284                 case '\1':
285                         usage(progname);
286                         return 0;
287                 case '\2':
288                         fprintf(stdout, "nfsstat: " VERSION "\n");
289                         return 0;
290                 default:
291                         printf("Try `%s --help' for more information.\n", progname);
292                         return -1;
293                 }
294         }
295
296         if (opt_all) {
297                 opt_srv = opt_clt = 1;
298                 opt_prt |= PRNT_ALL;
299         }
300         if (!(opt_srv + opt_clt))
301                 opt_srv = opt_clt = 1;
302         if (!(opt_prt & 0xfff)) {
303                 opt_prt |= PRNT_CALLS + PRNT_RPC;
304         }
305         if (!(opt_prt & 0xe000)) {
306                 opt_prt |= PRNT_AUTO;
307         }
308         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
309                 fprintf(stderr,
310                         "You requested file handle or request cache "
311                         "statistics while using the -c option.\n"
312                         "This information is available only for the NFS "
313                         "server.\n");
314         }
315
316         if (opt_srv) {
317                 srv_info = parse_statfile(NFSSVCSTAT, svcinfo);
318                 if (srv_info == 0 && opt_clt == 0) {
319                         fprintf(stderr, "Warning: No Server Stats (%s: %m).\n", NFSSVCSTAT);
320                         return 2;
321                 }
322                 if (srv_info == 0)
323                         opt_srv = 0;
324         }
325
326         if (opt_clt) {
327                 clt_info = parse_statfile(NFSCLTSTAT, cltinfo);
328                 if (opt_srv == 0 && clt_info == 0) {
329                         fprintf(stderr, "Warning: No Client Stats (%s: %m).\n", NFSCLTSTAT);
330                         return 2;
331                 }
332                 if (clt_info == 0)
333                         opt_clt = 0;
334         }
335
336         if (opt_srv) {
337                 if (opt_prt & PRNT_NET) {
338                         print_numbers(
339                         "Server packet stats:\n"
340                         "packets    udp        tcp        tcpconn\n",
341                         svcnetinfo, 4
342                         );
343                         printf("\n");
344                 }
345                 if (opt_prt & PRNT_RPC) {
346                         print_numbers(
347                         "Server rpc stats:\n"
348                         "calls      badcalls   badauth    badclnt    xdrcall\n",
349                         svcrpcinfo, 5
350                         );
351                         printf("\n");
352                 }
353                 if (opt_prt & PRNT_RC) {
354                         print_numbers(
355                         "Server reply cache:\n"
356                         "hits       misses     nocache\n",
357                         svcrcinfo, 3
358                         );
359                         printf("\n");
360                 }
361
362                 /*
363                  * 2.2 puts all fh-related info after the 'rc' header
364                  * 2.4 puts all fh-related info after the 'fh' header, but relocates
365                  *     'stale' to the start and swaps dir and nondir :-(  
366                  *     We preseve the 2.2 order
367                  */
368                 if (opt_prt & PRNT_FH) {
369                         if (get_stat_info("fh", svcinfo)) {     /* >= 2.4 */
370                                 int t = svcfhinfo[3];
371                                 svcfhinfo[3]=svcfhinfo[4];
372                                 svcfhinfo[4]=t;
373                                 
374                                 svcfhinfo[5]=svcfhinfo[0]; /* relocate 'stale' */
375                                 
376                                 print_numbers(
377                                         "Server file handle cache:\n"
378                                         "lookup     anon       ncachedir  ncachedir  stale\n",
379                                         svcfhinfo + 1, 5);
380                         } else                                  /* < 2.4 */
381                                 print_numbers(
382                                         "Server file handle cache:\n"
383                                         "lookup     anon       ncachedir  ncachedir  stale\n",
384                                         svcrcinfo + 3, 5);
385                         printf("\n");
386                 }
387                 if (opt_prt & PRNT_CALLS) {
388                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(svcv2info)))
389                                 print_callstats(
390                                 "Server nfs v2:\n",
391                                     nfsv2name, svcv2info + 1, sizeof(nfsv2name)/sizeof(char *)
392                                 );
393                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(svcv3info)))
394                                 print_callstats(
395                                 "Server nfs v3:\n",
396                                 nfsv3name, svcv3info + 1, sizeof(nfsv3name)/sizeof(char *)
397                                 );
398                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(svcv4info))) {
399                                 print_callstats(
400                                 "Server nfs v4:\n",
401                                 nfssvrv4name, svcv4info + 1, sizeof(nfssvrv4name)/sizeof(char *)
402                                 );
403                                 print_callstats(
404                                 "Server nfs v4 operations:\n",
405                                 nfssvrv4opname, svcv4opinfo + 1, sizeof(nfssvrv4opname)/sizeof(char *)
406                                 );
407                         }
408                 }
409         }
410
411         if (opt_clt) {
412                 if (opt_prt & PRNT_NET) {
413                         print_numbers(
414                         "Client packet stats:\n"
415                         "packets    udp        tcp        tcpconn\n",
416                         cltnetinfo, 4
417                         );
418                         printf("\n");
419                 }
420                 if (opt_prt & PRNT_RPC) {
421                         print_numbers(
422                         "Client rpc stats:\n"
423                         "calls      retrans    authrefrsh\n",
424                         cltrpcinfo, 3
425                         );
426                         printf("\n");
427                 }
428                 if (opt_prt & PRNT_CALLS) {
429                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltv2info)))
430                                 print_callstats(
431                                 "Client nfs v2:\n",
432                                 nfsv2name, cltv2info + 1,  sizeof(nfsv2name)/sizeof(char *)
433                                 );
434                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltv3info)))
435                                 print_callstats(
436                                 "Client nfs v3:\n",
437                                 nfsv3name, cltv3info + 1, sizeof(nfsv3name)/sizeof(char *)
438                                 );
439                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltv4info)))
440                                 print_callstats(
441                                 "Client nfs v4:\n",
442                                 nfscltv4name, cltv4info + 1,  sizeof(nfscltv4name)/sizeof(char *)
443                                 );
444                 }
445         }
446
447         return 0;
448 }
449
450 static statinfo *
451 get_stat_info(const char *sp, struct statinfo *statp)
452 {
453         struct statinfo *ip;
454
455         for (ip = statp; ip->tag; ip++) {
456                 if (!strcmp(sp, ip->tag))
457                         return ip;
458         }
459
460         return NULL;
461 }
462
463 static void
464 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
465 {
466         unsigned int    i;
467
468         fputs(hdr, stdout);
469         for (i = 0; i < nr; i++)
470                 printf("%s%-8d", i? "   " : "", info[i]);
471         printf("\n");
472 }
473
474 static void
475 print_callstats(const char *hdr, const char **names,
476                                  unsigned int *info, unsigned int nr)
477 {
478         unsigned long long      total;
479         unsigned long long      pct;
480         int             i, j;
481
482         fputs(hdr, stdout);
483         for (i = 0, total = 0; i < nr; i++)
484                 total += info[i];
485         if (!total)
486                 total = 1;
487         for (i = 0; i < nr; i += 6) {
488                 for (j = 0; j < 6 && i + j < nr; j++)
489                         printf("%-13s", names[i+j]);
490                 printf("\n");
491                 for (j = 0; j < 6 && i + j < nr; j++) {
492                         pct = ((unsigned long long) info[i+j]*100)/total;
493                         printf("%-8d%3llu%% ", info[i+j], pct);
494                 }
495                 printf("\n");
496         }
497         printf("\n");
498 }
499
500
501 static int
502 parse_statfile(const char *name, struct statinfo *statp)
503 {
504         char    buffer[4096], *next;
505         FILE    *fp;
506
507         /* Being unable to read e.g. the nfsd stats file shouldn't
508          * be a fatal error -- it usually means the module isn't loaded.
509          */
510         if ((fp = fopen(name, "r")) == NULL) {
511                 // fprintf(stderr, "Warning: %s: %m\n", name);
512                 return 0;
513         }
514
515         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
516                 struct statinfo *ip;
517                 char            *sp, *line = buffer;
518                 unsigned int    i, cnt;
519                 unsigned int    total = 0;
520
521                 if ((next = strchr(line, '\n')) != NULL)
522                         *next++ = '\0';
523                 if (!(sp = strtok(line, " \t")))
524                         continue;
525
526                 ip = get_stat_info(sp, statp);
527                 if (!ip)
528                         continue;
529
530                 cnt = ip->nrvals;
531
532                 for (i = 0; i < cnt; i++) {
533                         if (!(sp = strtok(NULL, " \t")))
534                                 break;
535                         ip->valptr[i] = atoi(sp);
536                         total += ip->valptr[i];
537                 }
538                 ip->valptr[i] = total;
539         }
540
541         fclose(fp);
542         return 1;
543 }
544
545 static int
546 mounts(const char *name)
547 {
548         char    buffer[4096], *next;
549         FILE    *fp;
550
551         /* Being unable to read e.g. the nfsd stats file shouldn't
552          * be a fatal error -- it usually means the module isn't loaded.
553          */
554         if ((fp = fopen(name, "r")) == NULL) {
555                 fprintf(stderr, "Warning: %s: %m\n", name);
556                 return 0;
557         }
558
559         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
560                 char          *line = buffer;
561                 char          *device, *mount, *type, *flags;
562
563                 if ((next = strchr(line, '\n')) != NULL)
564                         *next = '\0';
565
566                 if (!(device = strtok(line, " \t")))
567                         continue;
568
569                 if (!(mount = strtok(NULL, " \t")))
570                         continue;
571
572                 if (!(type = strtok(NULL, " \t")))
573                         continue;
574
575                 if (strcmp(type, "nfs")) {
576                     continue;
577                 }
578
579                 if (!(flags = strtok(NULL, " \t")))
580                         continue;
581
582                 printf("%s from %s\n", mount, device);
583                 printf(" Flags:\t%s\n", flags);
584                 printf("\n");
585
586                 continue;
587         }
588
589         fclose(fp);
590         return 1;
591 }
592
593 static int
594 has_stats(const unsigned int *info)
595 {
596         return (info[0] && info[info[0] + 1] != info[0]);
597 }