nfsstat: add a comment to has_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 NFSSRVSTAT      "/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 #include <signal.h>
24 #include <time.h>
25
26 #define MAXNRVALS       32
27
28 static unsigned int     srvproc2info[20], srvproc2info_tmp[20]; /* NFSv2 call counts ([0] == 18) */
29 static unsigned int     cltproc2info[20], cltproc2info_tmp[20]; /* NFSv2 call counts ([0] == 18) */
30 static unsigned int     srvproc3info[24], srvproc3info_tmp[24]; /* NFSv3 call counts ([0] == 22) */
31 static unsigned int     cltproc3info[24], cltproc3info_tmp[24]; /* NFSv3 call counts ([0] == 22) */
32 static unsigned int     srvproc4info[4], srvproc4info_tmp[4];   /* NFSv4 call counts ([0] == 2) */
33 static unsigned int     cltproc4info[37], cltproc4info_tmp[37]; /* NFSv4 call counts ([0] == 35) */
34 static unsigned int     srvproc4opsinfo[42], srvproc4opsinfo_tmp[42];   /* NFSv4 call counts ([0] == 40) */
35 static unsigned int     srvnetinfo[5], srvnetinfo_tmp[5];       /* 0  # of received packets
36                                                                  * 1  UDP packets
37                                                                  * 2  TCP packets
38                                                                  * 3  TCP connections
39                                                                  */
40 static unsigned int     cltnetinfo[5], cltnetinfo_tmp[5];       /* 0  # of received packets
41                                                                  * 1  UDP packets
42                                                                  * 2  TCP packets
43                                                                  * 3  TCP connections
44                                                                  */
45
46 static unsigned int     srvrpcinfo[6], srvrpcinfo_tmp[6];       /* 0  total # of RPC calls
47                                                                  * 1  total # of bad calls
48                                                                  * 2  bad format
49                                                                  * 3  authentication failed
50                                                                  * 4  unknown client
51                                                                  */
52 static unsigned int     cltrpcinfo[4], cltrpcinfo_tmp[4];       /* 0  total # of RPC calls
53                                                                  * 1  retransmitted calls
54                                                                  * 2  cred refreshs
55                                                                  */
56
57 static unsigned int     srvrcinfo[9], srvrcinfo_tmp[9];         /* 0  repcache hits
58                                                                  * 1  repcache hits
59                                                                  * 2  uncached reqs
60                                                                  * (for pre-2.4 kernels:)
61                                                                  * 3  FH lookups
62                                                                  * 4  'anon' FHs
63                                                                  * 5  noncached non-directories
64                                                                  * 6  noncached directories
65                                                                  * 7  stale
66                                                                  */
67
68 static unsigned int     srvfhinfo[7], srvfhinfo_tmp[7];         /* (for kernels >= 2.4.0)
69                                                                  * 0  stale
70                                                                  * 1  FH lookups
71                                                                  * 2  'anon' FHs
72                                                                  * 3  noncached directories
73                                                                  * 4  noncached non-directories
74                                                                  * leave hole to relocate stale for order
75                                                                  *    compatability.
76                                                                  */
77
78 static const char *     nfsv2name[18] = {
79         "null", "getattr", "setattr", "root",   "lookup",  "readlink",
80         "read", "wrcache", "write",   "create", "remove",  "rename",
81         "link", "symlink", "mkdir",   "rmdir",  "readdir", "fsstat"
82 };
83
84 static const char *     nfsv3name[22] = {
85         "null",   "getattr", "setattr",  "lookup", "access",  "readlink",
86         "read",   "write",   "create",   "mkdir",  "symlink", "mknod",
87         "remove", "rmdir",   "rename",   "link",   "readdir", "readdirplus",
88         "fsstat", "fsinfo",  "pathconf", "commit"
89 };
90
91 static const char *     nfssrvproc4name[2] = {
92         "null",
93         "compound",
94 };
95
96 static const char *     nfscltproc4name[35] = {
97         "null",      "read",      "write",   "commit",      "open",        "open_conf",
98         "open_noat", "open_dgrd", "close",   "setattr",     "fsinfo",      "renew",
99         "setclntid", "confirm",   "lock",
100         "lockt",     "locku",     "access",  "getattr",     "lookup",      "lookup_root",
101         "remove",    "rename",    "link",    "symlink",     "create",      "pathconf",
102         "statfs",    "readlink",  "readdir", "server_caps", "delegreturn", "getacl",
103         "setacl",    "fs_locations"
104 };
105
106 static const char *     nfssrvproc4opname[40] = {
107         "op0-unused",   "op1-unused", "op2-future",  "access",     "close",       "commit",
108         "create",       "delegpurge", "delegreturn", "getattr",    "getfh",       "link",
109         "lock",         "lockt",      "locku",       "lookup",     "lookup_root", "nverify",
110         "open",         "openattr",   "open_conf",   "open_dgrd",  "putfh",       "putpubfh",
111         "putrootfh",    "read",       "readdir",     "readlink",   "remove",      "rename",
112         "renew",        "restorefh",  "savefh",      "secinfo",    "setattr",     "setcltid",
113         "setcltidconf", "verify",     "write",       "rellockowner"
114 };
115
116 typedef struct statinfo {
117         char            *tag;
118         int             nrvals;
119         unsigned int *  valptr;
120 } statinfo;
121
122 /*
123  * We now build the arrays of statinfos using macros, which will make it easier
124  * to add new variables for --diff-stat.
125  * e.g., SRV(net) expands into the struct statinfo:  { "net", 5, srvnetinfo }
126  */
127 #define ARRAYSIZE(x)            sizeof(x)/sizeof(*x)
128 #define STATINFO(k, t, s...)    { #t, ARRAYSIZE(k##t##info##s), k##t##info##s }
129 #define SRV(t, s...)            STATINFO(srv, t, s)
130 #define CLT(t, s...)            STATINFO(clt, t, s)
131 #define DECLARE_SRV(n, s...)    static statinfo n##s[] = { \
132                                         SRV(net,s), \
133                                         SRV(rpc,s), \
134                                         SRV(rc,s), \
135                                         SRV(fh,s), \
136                                         SRV(proc2,s), \
137                                         SRV(proc3,s),\
138                                         SRV(proc4,s), \
139                                         SRV(proc4ops,s),\
140                                         { NULL, 0, NULL }\
141                                 }
142 #define DECLARE_CLT(n, s...)    static statinfo n##s[] = { \
143                                         CLT(net,s), \
144                                         CLT(rpc,s), \
145                                         CLT(proc2,s),\
146                                         CLT(proc3,s), \
147                                         CLT(proc4,s),\
148                                         { NULL, 0, NULL }\
149                                 }
150 DECLARE_SRV(srvinfo);
151 DECLARE_SRV(srvinfo, _tmp);
152 DECLARE_CLT(cltinfo);
153 DECLARE_CLT(cltinfo, _tmp);
154
155 static void             print_numbers(const char *, unsigned int *,
156                                         unsigned int);
157 static void             print_callstats(const char *, const char **,
158                                         unsigned int *, unsigned int);
159 static int              parse_statfile(const char *, struct statinfo *);
160
161 static statinfo         *get_stat_info(const char *, struct statinfo *);
162
163 static int              mounts(const char *);
164
165 static void             get_stats(const char *, statinfo *, int *, int, const char *);
166 static int              has_stats(const unsigned int *);
167 static void             copy_stats(statinfo *, statinfo *);
168 static void             diff_stats(statinfo *, statinfo *);
169 static void             unpause(int);
170
171 static time_t           starttime;
172
173 #define PRNT_CALLS      0x0001
174 #define PRNT_RPC        0x0002
175 #define PRNT_NET        0x0004
176 #define PRNT_FH         0x0008
177 #define PRNT_RC         0x0010
178 #define PRNT_AUTO       0x1000
179 #define PRNT_V2         0x2000
180 #define PRNT_V3         0x4000
181 #define PRNT_V4         0x8000
182 #define PRNT_ALL        0x0fff
183
184 int versions[] = {
185         PRNT_V2,
186         PRNT_V3,
187         PRNT_V4
188 };
189
190 void usage(char *name)
191 {
192         printf("Usage: %s [OPTION]...\n\
193 \n\
194   -m, --mounted\t\tShow statistics on mounted NFS filesystems\n\
195   -c, --client\t\tShow NFS client statistics\n\
196   -s, --server\t\tShow NFS server statistics\n\
197   -2\t\t\tShow NFS version 2 statistics\n\
198   -3\t\t\tShow NFS version 3 statistics\n\
199   -4\t\t\tShow NFS version 4 statistics\n\
200   -o [facility]\t\tShow statistics on particular facilities.\n\
201      nfs\tNFS protocol information\n\
202      rpc\tGeneral RPC information\n\
203      net\tNetwork layer statistics\n\
204      fh\t\tUsage information on the server's file handle cache\n\
205      rc\t\tUsage information on the server's request reply cache\n\
206      all\tSelect all of the above\n\
207   -v, --verbose, --all\tSame as '-o all'\n\
208   -r, --rpc\t\tShow RPC statistics\n\
209   -n, --nfs\t\tShow NFS statistics\n\
210   -D, --diff-stat\tSaves stats, pauses, diffs current and saved\n\
211   --version\t\tShow program version\n\
212   --help\t\tWhat you just did\n\
213 \n", name);
214         exit(0);
215 }
216
217 static struct option longopts[] =
218 {
219         { "acl", 0, 0, 'a' },
220         { "all", 0, 0, 'v' },
221         { "auto", 0, 0, '\3' },
222         { "client", 0, 0, 'c' },
223         { "mounts", 0, 0, 'm' },
224         { "nfs", 0, 0, 'n' },
225         { "rpc", 0, 0, 'r' },
226         { "server", 0, 0, 's' },
227         { "verbose", 0, 0, 'v' },
228         { "zero", 0, 0, 'z' },
229         { "help", 0, 0, '\1' },
230         { "version", 0, 0, '\2' },
231         { "diff-stat", 0, 0, 'D' },
232         { NULL, 0, 0, 0 }
233 };
234
235 int
236 main(int argc, char **argv)
237 {
238         int             opt_all = 0,
239                         opt_srv = 0,
240                         opt_clt = 0,
241                         opt_prt = 0,
242                         opt_diffstat = 0;
243         int             c;
244         char           *progname;
245
246         struct sigaction act = {
247                 .sa_handler = unpause,
248                 .sa_flags = SA_ONESHOT,
249         };
250
251         if ((progname = strrchr(argv[0], '/')))
252                 progname++;
253         else
254                 progname = argv[0];
255
256         while ((c = getopt_long(argc, argv, "234acmno:Dvrsz\1\2", longopts, NULL)) != EOF) {
257                 switch (c) {
258                 case 'a':
259                         fprintf(stderr, "nfsstat: nfs acls are not yet supported.\n");
260                         return -1;
261                 case 'c':
262                         opt_clt = 1;
263                         break;
264                 case 'n':
265                         opt_prt |= PRNT_CALLS;
266                         break;
267                 case 'o':
268                         if (!strcmp(optarg, "nfs"))
269                                 opt_prt |= PRNT_CALLS;
270                         else if (!strcmp(optarg, "rpc"))
271                                 opt_prt |= PRNT_RPC;
272                         else if (!strcmp(optarg, "net"))
273                                 opt_prt |= PRNT_NET;
274                         else if (!strcmp(optarg, "rc"))
275                                 opt_prt |= PRNT_RC;
276                         else if (!strcmp(optarg, "fh"))
277                                 opt_prt |= PRNT_FH;
278                         else if (!strcmp(optarg, "all"))
279                                 opt_prt |= PRNT_CALLS | PRNT_RPC | PRNT_NET | PRNT_RC | PRNT_FH;
280                         else {
281                                 fprintf(stderr, "nfsstat: unknown category: "
282                                                 "%s\n", optarg);
283                                 return 2;
284                         }
285                         break;
286                 case 'D':
287                         opt_diffstat = 1;
288                         break;
289                 case '2':
290                 case '3':
291                 case '4':
292                         opt_prt |= versions[c - '2'];
293                         break;
294                 case 'v':
295                         opt_all = 1;
296                         break;
297                 case '\3':
298                         opt_prt |= PRNT_AUTO;
299                         break;
300                 case 'r':
301                         opt_prt |= PRNT_RPC;
302                         break;
303                 case 's':
304                         opt_srv = 1;
305                         break;
306                 case 'z':
307                         fprintf(stderr, "nfsstat: zeroing of nfs statistics "
308                                         "not yet supported\n");
309                         return 2;
310                 case 'm':
311                         return mounts(MOUNTSFILE);
312                 case '\1':
313                         usage(progname);
314                         return 0;
315                 case '\2':
316                         fprintf(stdout, "nfsstat: " VERSION "\n");
317                         return 0;
318                 default:
319                         printf("Try `%s --help' for more information.\n", progname);
320                         return -1;
321                 }
322         }
323
324         if (opt_all) {
325                 opt_srv = opt_clt = 1;
326                 opt_prt |= PRNT_ALL;
327         }
328         if (!(opt_srv + opt_clt))
329                 opt_srv = opt_clt = 1;
330         if (!(opt_prt & 0xfff)) {
331                 opt_prt |= PRNT_CALLS + PRNT_RPC;
332         }
333         if (!(opt_prt & 0xe000)) {
334                 opt_prt |= PRNT_AUTO;
335         }
336         if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
337                 fprintf(stderr,
338                         "You requested file handle or request cache "
339                         "statistics while using the -c option.\n"
340                         "This information is available only for the NFS "
341                         "server.\n");
342         }
343
344         if (opt_srv)
345                 get_stats(NFSSRVSTAT, srvinfo, &opt_srv, opt_clt, "Server");
346         if (opt_clt)
347                 get_stats(NFSCLTSTAT, cltinfo, &opt_clt, opt_srv, "Client");
348
349         /* save stat snapshots; wait for signal; then diff current and saved stats */
350         if (opt_diffstat) {
351                 starttime = time(NULL);
352                 printf("Collecting statistics; press CTRL-C to view results from interval (i.e., from pause to CTRL-C).\n");
353                 if (opt_srv)
354                         copy_stats(srvinfo_tmp, srvinfo);
355                 if (opt_clt)
356                         copy_stats(cltinfo_tmp, cltinfo);
357                 if (sigaction(SIGINT, &act, NULL) != 0) {
358                         fprintf(stderr, "Error: couldn't register for signal and pause.\n");
359                         return 1;
360                 }
361                 pause();
362                 if (opt_srv) {
363                         get_stats(NFSSRVSTAT, srvinfo, &opt_srv, opt_clt, "Server");
364                         diff_stats(srvinfo, srvinfo_tmp);
365                 }
366                 if (opt_clt) {
367                         get_stats(NFSCLTSTAT, cltinfo, &opt_clt, opt_srv, "Client");
368                         diff_stats(cltinfo, cltinfo_tmp);
369                 }
370         }
371
372         if (opt_srv) {
373                 if (opt_prt & PRNT_NET) {
374                         print_numbers(
375                         "Server packet stats:\n"
376                         "packets    udp        tcp        tcpconn\n",
377                         srvnetinfo, 4
378                         );
379                         printf("\n");
380                 }
381                 if (opt_prt & PRNT_RPC) {
382                         print_numbers(
383                         "Server rpc stats:\n"
384                         "calls      badcalls   badauth    badclnt    xdrcall\n",
385                         srvrpcinfo, 5
386                         );
387                         printf("\n");
388                 }
389                 if (opt_prt & PRNT_RC) {
390                         print_numbers(
391                         "Server reply cache:\n"
392                         "hits       misses     nocache\n",
393                         srvrcinfo, 3
394                         );
395                         printf("\n");
396                 }
397
398                 /*
399                  * 2.2 puts all fh-related info after the 'rc' header
400                  * 2.4 puts all fh-related info after the 'fh' header, but relocates
401                  *     'stale' to the start and swaps dir and nondir :-(  
402                  *     We preseve the 2.2 order
403                  */
404                 if (opt_prt & PRNT_FH) {
405                         if (get_stat_info("fh", srvinfo)) {     /* >= 2.4 */
406                                 int t = srvfhinfo[3];
407                                 srvfhinfo[3]=srvfhinfo[4];
408                                 srvfhinfo[4]=t;
409                                 
410                                 srvfhinfo[5]=srvfhinfo[0]; /* relocate 'stale' */
411                                 
412                                 print_numbers(
413                                         "Server file handle cache:\n"
414                                         "lookup     anon       ncachedir  ncachedir  stale\n",
415                                         srvfhinfo + 1, 5);
416                         } else                                  /* < 2.4 */
417                                 print_numbers(
418                                         "Server file handle cache:\n"
419                                         "lookup     anon       ncachedir  ncachedir  stale\n",
420                                         srvrcinfo + 3, 5);
421                         printf("\n");
422                 }
423                 if (opt_prt & PRNT_CALLS) {
424                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc2info)))
425                                 print_callstats(
426                                 "Server nfs v2:\n",
427                                     nfsv2name, srvproc2info + 1, sizeof(nfsv2name)/sizeof(char *)
428                                 );
429                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc3info)))
430                                 print_callstats(
431                                 "Server nfs v3:\n",
432                                 nfsv3name, srvproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
433                                 );
434                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(srvproc4info))) {
435                                 print_callstats(
436                                 "Server nfs v4:\n",
437                                 nfssrvproc4name, srvproc4info + 1, sizeof(nfssrvproc4name)/sizeof(char *)
438                                 );
439                                 print_callstats(
440                                 "Server nfs v4 operations:\n",
441                                 nfssrvproc4opname, srvproc4opsinfo + 1, sizeof(nfssrvproc4opname)/sizeof(char *)
442                                 );
443                         }
444                 }
445         }
446
447         if (opt_clt) {
448                 if (opt_prt & PRNT_NET) {
449                         print_numbers(
450                         "Client packet stats:\n"
451                         "packets    udp        tcp        tcpconn\n",
452                         cltnetinfo, 4
453                         );
454                         printf("\n");
455                 }
456                 if (opt_prt & PRNT_RPC) {
457                         print_numbers(
458                         "Client rpc stats:\n"
459                         "calls      retrans    authrefrsh\n",
460                         cltrpcinfo, 3
461                         );
462                         printf("\n");
463                 }
464                 if (opt_prt & PRNT_CALLS) {
465                         if ((opt_prt & PRNT_V2) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc2info)))
466                                 print_callstats(
467                                 "Client nfs v2:\n",
468                                 nfsv2name, cltproc2info + 1,  sizeof(nfsv2name)/sizeof(char *)
469                                 );
470                         if ((opt_prt & PRNT_V3) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc3info)))
471                                 print_callstats(
472                                 "Client nfs v3:\n",
473                                 nfsv3name, cltproc3info + 1, sizeof(nfsv3name)/sizeof(char *)
474                                 );
475                         if ((opt_prt & PRNT_V4) || ((opt_prt & PRNT_AUTO) && has_stats(cltproc4info)))
476                                 print_callstats(
477                                 "Client nfs v4:\n",
478                                 nfscltproc4name, cltproc4info + 1,  sizeof(nfscltproc4name)/sizeof(char *)
479                                 );
480                 }
481         }
482
483         return 0;
484 }
485
486 static statinfo *
487 get_stat_info(const char *sp, struct statinfo *statp)
488 {
489         struct statinfo *ip;
490
491         for (ip = statp; ip->tag; ip++) {
492                 if (!strcmp(sp, ip->tag))
493                         return ip;
494         }
495
496         return NULL;
497 }
498
499 static void
500 print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
501 {
502         unsigned int    i;
503
504         fputs(hdr, stdout);
505         for (i = 0; i < nr; i++)
506                 printf("%s%-8d", i? "   " : "", info[i]);
507         printf("\n");
508 }
509
510 static void
511 print_callstats(const char *hdr, const char **names,
512                                  unsigned int *info, unsigned int nr)
513 {
514         unsigned long long      total;
515         unsigned long long      pct;
516         int             i, j;
517
518         fputs(hdr, stdout);
519         for (i = 0, total = 0; i < nr; i++)
520                 total += info[i];
521         if (!total)
522                 total = 1;
523         for (i = 0; i < nr; i += 6) {
524                 for (j = 0; j < 6 && i + j < nr; j++)
525                         printf("%-13s", names[i+j]);
526                 printf("\n");
527                 for (j = 0; j < 6 && i + j < nr; j++) {
528                         pct = ((unsigned long long) info[i+j]*100)/total;
529                         printf("%-8d%3llu%% ", info[i+j], pct);
530                 }
531                 printf("\n");
532         }
533         printf("\n");
534 }
535
536
537 static int
538 parse_statfile(const char *name, struct statinfo *statp)
539 {
540         char    buffer[4096], *next;
541         FILE    *fp;
542
543         /* Being unable to read e.g. the nfsd stats file shouldn't
544          * be a fatal error -- it usually means the module isn't loaded.
545          */
546         if ((fp = fopen(name, "r")) == NULL) {
547                 // fprintf(stderr, "Warning: %s: %m\n", name);
548                 return 0;
549         }
550
551         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
552                 struct statinfo *ip;
553                 char            *sp, *line = buffer;
554                 unsigned int    i, cnt;
555                 unsigned int    total = 0;
556
557                 if ((next = strchr(line, '\n')) != NULL)
558                         *next++ = '\0';
559                 if (!(sp = strtok(line, " \t")))
560                         continue;
561
562                 ip = get_stat_info(sp, statp);
563                 if (!ip)
564                         continue;
565
566                 cnt = ip->nrvals;
567
568                 for (i = 0; i < cnt; i++) {
569                         if (!(sp = strtok(NULL, " \t")))
570                                 break;
571                         ip->valptr[i] = atoi(sp);
572                         total += ip->valptr[i];
573                 }
574                 ip->valptr[cnt - 1] = total;
575         }
576
577         fclose(fp);
578         return 1;
579 }
580
581 static int
582 mounts(const char *name)
583 {
584         char    buffer[4096], *next;
585         FILE    *fp;
586
587         /* Being unable to read e.g. the nfsd stats file shouldn't
588          * be a fatal error -- it usually means the module isn't loaded.
589          */
590         if ((fp = fopen(name, "r")) == NULL) {
591                 fprintf(stderr, "Warning: %s: %m\n", name);
592                 return 0;
593         }
594
595         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
596                 char          *line = buffer;
597                 char          *device, *mount, *type, *flags;
598
599                 if ((next = strchr(line, '\n')) != NULL)
600                         *next = '\0';
601
602                 if (!(device = strtok(line, " \t")))
603                         continue;
604
605                 if (!(mount = strtok(NULL, " \t")))
606                         continue;
607
608                 if (!(type = strtok(NULL, " \t")))
609                         continue;
610
611                 if (strcmp(type, "nfs")) {
612                     continue;
613                 }
614
615                 if (!(flags = strtok(NULL, " \t")))
616                         continue;
617
618                 printf("%s from %s\n", mount, device);
619                 printf(" Flags:\t%s\n", flags);
620                 printf("\n");
621
622                 continue;
623         }
624
625         fclose(fp);
626         return 1;
627 }
628
629 static void
630 get_stats(const char *file, statinfo *info, int *opt, int other_opt, const char *label)
631 {
632         if (!parse_statfile(file, info)) {
633                 if (!other_opt) {
634                         fprintf(stderr, "Warning: No %s Stats (%s: %m). \n", label, file);
635                         exit(2);
636                 }
637                 *opt = 0;
638         }
639 }
640
641 /*
642  * This is for proc2/3/4-type stats, where, in the /proc files, the first entry's value
643  * denotes the number of subsequent entries.  statinfo value arrays contain an additional
644  * field at the end which contains the sum of all previous elements in the array -- so,
645  * there are stats if the sum's greater than the entry-count.
646  */
647 static int
648 has_stats(const unsigned int *info)
649 {
650         return (info[0] && info[info[0] + 1] > info[0]);
651 }
652
653 /* clone 'src' to 'dest' */
654 static void
655 copy_stats(struct statinfo *dest, struct statinfo *src)
656 {
657         int i, j;
658
659         for (i = 0; src[i].tag; i++) {
660                 dest[i].tag = src[i].tag;
661                 dest[i].nrvals = src[i].nrvals;
662                 for (j = 0; j < dest[i].nrvals; j++)
663                         dest[i].valptr[j] = src[i].valptr[j];
664         }
665 }
666
667 /*
668  * take the difference of each individual stat value in 'new' and 'old'
669  * and store the results back into 'new'
670  */
671 static void
672 diff_stats(struct statinfo *new, struct statinfo *old)
673 {
674         int i, j, is_srv, should_diff;
675
676         is_srv = (new == srvinfo);
677         for (i = 0; old[i].tag; i++) {
678                 for (j = 0; j < new[i].nrvals; j++) {
679                         /* skip items in valptr that shouldn't be changed */
680                         should_diff = (i < (3 + is_srv) || j > 0);
681                         if (should_diff)
682                                 new[i].valptr[j] -= old[i].valptr[j];
683                 }
684         }
685 }
686
687 static void
688 unpause(int sig)
689 {
690         double time_diff;
691         int minutes, seconds;
692         time_t endtime;
693
694         endtime = time(NULL);
695         time_diff = difftime(endtime, starttime);
696         minutes = time_diff / 60;
697         seconds = (int)time_diff % 60;
698         printf("Signal received; displaying (only) statistics gathered over the last %d minutes, %d seconds:\n\n", minutes, seconds);
699 }