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