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