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