ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/libstatgrab/src/statgrab/statgrab.c
Revision: 1.35
Committed: Wed Jul 13 13:01:24 2005 UTC (19 years, 3 months ago) by tdb
Content type: text/plain
Branch: MAIN
CVS Tags: LIBSTATGRAB_0_12
Changes since 1.34: +15 -1 lines
Log Message:
Add more fs stats.

Patches provided by: Roman Neuhauser

File Contents

# Content
1 /*
2 * i-scream libstatgrab
3 * http://www.i-scream.org
4 * Copyright (C) 2000-2004 i-scream
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 *
20 * $Id: statgrab.c,v 1.34 2005/07/13 09:31:54 tdb Exp $
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <statgrab.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33
34 typedef enum {
35 LONG_LONG = 0,
36 BYTES,
37 TIME_T,
38 FLOAT,
39 DOUBLE,
40 STRING,
41 INT,
42 BOOL,
43 DUPLEX
44 } stat_type;
45
46 typedef enum {
47 DISPLAY_LINUX = 0,
48 DISPLAY_BSD,
49 DISPLAY_MRTG,
50 DISPLAY_PLAIN
51 } display_mode_type;
52
53 typedef enum {
54 REPEAT_NONE = 0,
55 REPEAT_ONCE,
56 REPEAT_FOREVER
57 } repeat_mode_type;
58
59 typedef struct {
60 char *name;
61 stat_type type;
62 void *stat;
63 } stat;
64
65 stat *stats = NULL;
66 int num_stats = 0;
67 int alloc_stats = 0;
68 #define INCREMENT_STATS 64
69
70 display_mode_type display_mode = DISPLAY_LINUX;
71 repeat_mode_type repeat_mode = REPEAT_NONE;
72 int repeat_time = 1;
73 int use_cpu_percent = 0;
74 int use_diffs = 0;
75 long float_scale_factor = 0;
76 long long bytes_scale_factor = 0;
77
78 /* Exit with an error message. */
79 void die(const char *s) {
80 fprintf(stderr, "fatal: %s\n", s);
81 exit(1);
82 }
83
84 /* Remove all the recorded stats. */
85 void clear_stats() {
86 int i;
87
88 for (i = 0; i < num_stats; i++)
89 free(stats[i].name);
90 num_stats = 0;
91 }
92
93 /* Add a stat. The varargs make up the name, joined with dots; the name is
94 terminated with a NULL. */
95 void add_stat(stat_type type, void *stat, ...) {
96 va_list ap;
97 int len = 0;
98 char *name, *p;
99
100 /* Figure out how long the name will be, including dots and trailing
101 \0. */
102 va_start(ap, stat);
103 while (1) {
104 const char *part = va_arg(ap, const char *);
105 if (part == NULL)
106 break;
107 len += 1 + strlen(part);
108 }
109 va_end(ap);
110
111 /* Paste the name together. */
112 name = malloc(len);
113 if (name == NULL)
114 die("out of memory");
115 p = name;
116 va_start(ap, stat);
117 while (1) {
118 const char *part = va_arg(ap, const char *);
119 int partlen;
120 if (part == NULL)
121 break;
122 partlen = strlen(part);
123 memcpy(p, part, partlen);
124 p += partlen;
125 *p++ = '.';
126 }
127 va_end(ap);
128 *--p = '\0';
129
130 /* Replace spaces with underscores. */
131 for (p = name; *p != '\0'; p++) {
132 if (*p == ' ')
133 *p = '_';
134 }
135
136 /* Stretch the stats array if necessary. */
137 if (num_stats >= alloc_stats) {
138 alloc_stats += INCREMENT_STATS;
139 stats = realloc(stats, alloc_stats * sizeof *stats);
140 if (stats == NULL)
141 die("out of memory");
142 }
143
144 stats[num_stats].name = name;
145 stats[num_stats].type = type;
146 stats[num_stats].stat = stat;
147 ++num_stats;
148 }
149
150 /* Compare two stats by name for qsort and bsearch. */
151 int stats_compare(const void *a, const void *b) {
152 return strcmp(((stat *)a)->name, ((stat *)b)->name);
153 }
154
155 /* Compare up to the length of the key for bsearch. */
156 int stats_compare_prefix(const void *key, const void *item) {
157 const char *kn = ((stat *)key)->name;
158 const char *in = ((stat *)item)->name;
159
160 return strncmp(kn, in, strlen(kn));
161 }
162
163 void populate_const() {
164 static int zero = 0;
165
166 /* Constants, for use with MRTG mode. */
167 add_stat(INT, &zero, "const", "0", NULL);
168 }
169
170 void populate_cpu() {
171 if (use_cpu_percent) {
172 sg_cpu_percents *cpu_p = sg_get_cpu_percents();
173
174 if (cpu_p != NULL) {
175 add_stat(FLOAT, &cpu_p->user,
176 "cpu", "user", NULL);
177 add_stat(FLOAT, &cpu_p->kernel,
178 "cpu", "kernel", NULL);
179 add_stat(FLOAT, &cpu_p->idle,
180 "cpu", "idle", NULL);
181 add_stat(FLOAT, &cpu_p->iowait,
182 "cpu", "iowait", NULL);
183 add_stat(FLOAT, &cpu_p->swap,
184 "cpu", "swap", NULL);
185 add_stat(FLOAT, &cpu_p->nice,
186 "cpu", "nice", NULL);
187 add_stat(TIME_T, &cpu_p->time_taken,
188 "cpu", "time_taken", NULL);
189 }
190 } else {
191 sg_cpu_stats *cpu_s;
192
193 cpu_s = use_diffs ? sg_get_cpu_stats_diff()
194 : sg_get_cpu_stats();
195 if (cpu_s != NULL) {
196 add_stat(LONG_LONG, &cpu_s->user,
197 "cpu", "user", NULL);
198 add_stat(LONG_LONG, &cpu_s->kernel,
199 "cpu", "kernel", NULL);
200 add_stat(LONG_LONG, &cpu_s->idle,
201 "cpu", "idle", NULL);
202 add_stat(LONG_LONG, &cpu_s->iowait,
203 "cpu", "iowait", NULL);
204 add_stat(LONG_LONG, &cpu_s->swap,
205 "cpu", "swap", NULL);
206 add_stat(LONG_LONG, &cpu_s->nice,
207 "cpu", "nice", NULL);
208 add_stat(LONG_LONG, &cpu_s->total,
209 "cpu", "total", NULL);
210 add_stat(TIME_T, &cpu_s->systime,
211 "cpu", "systime", NULL);
212 }
213 }
214 }
215
216 void populate_mem() {
217 sg_mem_stats *mem = sg_get_mem_stats();
218
219 if (mem != NULL) {
220 add_stat(BYTES, &mem->total, "mem", "total", NULL);
221 add_stat(BYTES, &mem->free, "mem", "free", NULL);
222 add_stat(BYTES, &mem->used, "mem", "used", NULL);
223 add_stat(BYTES, &mem->cache, "mem", "cache", NULL);
224 }
225 }
226
227 void populate_load() {
228 sg_load_stats *load = sg_get_load_stats();
229
230 if (load != NULL) {
231 add_stat(DOUBLE, &load->min1, "load", "min1", NULL);
232 add_stat(DOUBLE, &load->min5, "load", "min5", NULL);
233 add_stat(DOUBLE, &load->min15, "load", "min15", NULL);
234 }
235 }
236
237 void populate_user() {
238 sg_user_stats *user = sg_get_user_stats();
239
240 if (user != NULL) {
241 add_stat(INT, &user->num_entries, "user", "num", NULL);
242 add_stat(STRING, &user->name_list, "user", "names", NULL);
243 }
244 }
245
246 void populate_swap() {
247 sg_swap_stats *swap = sg_get_swap_stats();
248
249 if (swap != NULL) {
250 add_stat(BYTES, &swap->total, "swap", "total", NULL);
251 add_stat(BYTES, &swap->used, "swap", "used", NULL);
252 add_stat(BYTES, &swap->free, "swap", "free", NULL);
253 }
254 }
255
256 void populate_general() {
257 /* FIXME this should be renamed to host. */
258 sg_host_info *host = sg_get_host_info();
259
260 if (host != NULL) {
261 add_stat(STRING, &host->os_name,
262 "general", "os_name", NULL);
263 add_stat(STRING, &host->os_release,
264 "general", "os_release", NULL);
265 add_stat(STRING, &host->os_version,
266 "general", "os_version", NULL);
267 add_stat(STRING, &host->platform, "general", "platform", NULL);
268 add_stat(STRING, &host->hostname, "general", "hostname", NULL);
269 add_stat(TIME_T, &host->uptime, "general", "uptime", NULL);
270 }
271 }
272
273 void populate_fs() {
274 int n, i;
275 sg_fs_stats *disk = sg_get_fs_stats(&n);
276
277 if (disk != NULL) {
278 for (i = 0; i < n; i++) {
279 /* FIXME it'd be nicer if libstatgrab did this */
280 char *buf, *name, *p;
281 const char *device = disk[i].device_name;
282
283 if (strcmp(device, "/") == 0)
284 device = "root";
285
286 buf = strdup(device);
287 if (buf == NULL)
288 die("out of memory");
289
290 name = buf;
291 if (strlen(name) == 2 && name[1] == ':')
292 name[1] = '\0';
293 if (strncmp(name, "/dev/", 5) == 0)
294 name += 5;
295 while ((p = strchr(name, '/')) != NULL)
296 *p = '_';
297
298 add_stat(STRING, &disk[i].device_name,
299 "fs", name, "device_name", NULL);
300 add_stat(STRING, &disk[i].fs_type,
301 "fs", name, "fs_type", NULL);
302 add_stat(STRING, &disk[i].mnt_point,
303 "fs", name, "mnt_point", NULL);
304 add_stat(BYTES, &disk[i].size,
305 "fs", name, "size", NULL);
306 add_stat(BYTES, &disk[i].used,
307 "fs", name, "used", NULL);
308 add_stat(BYTES, &disk[i].avail,
309 "fs", name, "avail", NULL);
310 add_stat(LONG_LONG, &disk[i].total_inodes,
311 "fs", name, "total_inodes", NULL);
312 add_stat(LONG_LONG, &disk[i].used_inodes,
313 "fs", name, "used_inodes", NULL);
314 add_stat(LONG_LONG, &disk[i].free_inodes,
315 "fs", name, "free_inodes", NULL);
316 add_stat(LONG_LONG, &disk[i].avail_inodes,
317 "fs", name, "avail_inodes", NULL);
318 add_stat(LONG_LONG, &disk[i].io_size,
319 "fs", name, "io_size", NULL);
320 add_stat(LONG_LONG, &disk[i].block_size,
321 "fs", name, "block_size", NULL);
322 add_stat(LONG_LONG, &disk[i].total_blocks,
323 "fs", name, "total_blocks", NULL);
324 add_stat(LONG_LONG, &disk[i].free_blocks,
325 "fs", name, "free_blocks", NULL);
326 add_stat(LONG_LONG, &disk[i].avail_blocks,
327 "fs", name, "avail_blocks", NULL);
328 add_stat(LONG_LONG, &disk[i].used_blocks,
329 "fs", name, "used_blocks", NULL);
330
331 free(buf);
332 }
333 }
334 }
335
336 void populate_disk() {
337 int n, i;
338 sg_disk_io_stats *diskio;
339
340 diskio = use_diffs ? sg_get_disk_io_stats_diff(&n)
341 : sg_get_disk_io_stats(&n);
342 if (diskio != NULL) {
343 for (i = 0; i < n; i++) {
344 const char *name = diskio[i].disk_name;
345
346 add_stat(STRING, &diskio[i].disk_name,
347 "disk", name, "disk_name", NULL);
348 add_stat(BYTES, &diskio[i].read_bytes,
349 "disk", name, "read_bytes", NULL);
350 add_stat(BYTES, &diskio[i].write_bytes,
351 "disk", name, "write_bytes", NULL);
352 add_stat(TIME_T, &diskio[i].systime,
353 "disk", name, "systime", NULL);
354 }
355 }
356 }
357
358 void populate_proc() {
359 /* FIXME expose individual process info too */
360 sg_process_count *proc = sg_get_process_count();
361
362 if (proc != NULL) {
363 add_stat(INT, &proc->total, "proc", "total", NULL);
364 add_stat(INT, &proc->running, "proc", "running", NULL);
365 add_stat(INT, &proc->sleeping, "proc", "sleeping", NULL);
366 add_stat(INT, &proc->stopped, "proc", "stopped", NULL);
367 add_stat(INT, &proc->zombie, "proc", "zombie", NULL);
368 }
369 }
370
371 void populate_net() {
372 int num_io, num_iface, i;
373 sg_network_io_stats *io;
374 sg_network_iface_stats *iface;
375
376 io = use_diffs ? sg_get_network_io_stats_diff(&num_io)
377 : sg_get_network_io_stats(&num_io);
378 if (io != NULL) {
379 for (i = 0; i < num_io; i++) {
380 const char *name = io[i].interface_name;
381
382 add_stat(STRING, &io[i].interface_name,
383 "net", name, "interface_name", NULL);
384 add_stat(BYTES, &io[i].tx,
385 "net", name, "tx", NULL);
386 add_stat(BYTES, &io[i].rx,
387 "net", name, "rx", NULL);
388 add_stat(LONG_LONG, &io[i].ipackets,
389 "net", name, "ipackets", NULL);
390 add_stat(LONG_LONG, &io[i].opackets,
391 "net", name, "opackets", NULL);
392 add_stat(LONG_LONG, &io[i].ierrors,
393 "net", name, "ierrors", NULL);
394 add_stat(LONG_LONG, &io[i].oerrors,
395 "net", name, "oerrors", NULL);
396 add_stat(LONG_LONG, &io[i].collisions,
397 "net", name, "collisions", NULL);
398 add_stat(TIME_T, &io[i].systime,
399 "net", name, "systime", NULL);
400 }
401 }
402
403 iface = sg_get_network_iface_stats(&num_iface);
404 if (iface != NULL) {
405 for (i = 0; i < num_iface; i++) {
406 const char *name = iface[i].interface_name;
407 int had_io = 0, j;
408
409 /* If there wasn't a corresponding io stat,
410 add interface_name from here. */
411 if (io != NULL) {
412 for (j = 0; j < num_io; j++) {
413 if (strcmp(io[j].interface_name,
414 name) == 0) {
415 had_io = 1;
416 break;
417 }
418 }
419 }
420 if (!had_io) {
421 add_stat(STRING, &iface[i].interface_name,
422 "net", name, "interface_name", NULL);
423 }
424
425 add_stat(INT, &iface[i].speed,
426 "net", name, "speed", NULL);
427 add_stat(BOOL, &iface[i].up,
428 "net", name, "up", NULL);
429 add_stat(DUPLEX, &iface[i].duplex,
430 "net", name, "duplex", NULL);
431 }
432 }
433 }
434
435 void populate_page() {
436 sg_page_stats *page;
437
438 page = use_diffs ? sg_get_page_stats_diff() : sg_get_page_stats();
439 if (page != NULL) {
440 add_stat(LONG_LONG, &page->pages_pagein, "page", "in", NULL);
441 add_stat(LONG_LONG, &page->pages_pageout, "page", "out", NULL);
442 add_stat(TIME_T, &page->systime, "page", "systime", NULL);
443 }
444 }
445
446 typedef struct {
447 const char *name;
448 void (*populate)();
449 int interesting;
450 } toplevel;
451 toplevel toplevels[] = {
452 {"const.", populate_const, 0},
453 {"cpu.", populate_cpu, 0},
454 {"mem.", populate_mem, 0},
455 {"load.", populate_load, 0},
456 {"user.", populate_user, 0},
457 {"swap.", populate_swap, 0},
458 {"general.", populate_general, 0},
459 {"fs.", populate_fs, 0},
460 {"disk.", populate_disk, 0},
461 {"proc.", populate_proc, 0},
462 {"net.", populate_net, 0},
463 {"page.", populate_page, 0},
464 {NULL, NULL, 0}
465 };
466
467 /* Set the "interesting" flag on the sections that we actually need to
468 fetch. */
469 void select_interesting(int argc, char **argv) {
470 toplevel *t;
471
472 if (argc == 0) {
473 for (t = &toplevels[0]; t->name != NULL; t++)
474 t->interesting = 1;
475 } else {
476 int i;
477
478 for (i = 0; i < argc; i++) {
479 for (t = &toplevels[0]; t->name != NULL; t++) {
480 if (strncmp(argv[i], t->name,
481 strlen(t->name)) == 0) {
482 t->interesting = 1;
483 break;
484 }
485 }
486 }
487 }
488 }
489
490 /* Clear and rebuild the stats array. */
491 void get_stats() {
492 toplevel *t;
493
494 clear_stats();
495
496 for (t = &toplevels[0]; t->name != NULL; t++) {
497 if (t->interesting)
498 t->populate();
499 }
500
501 if (stats != NULL)
502 qsort(stats, num_stats, sizeof *stats, stats_compare);
503 }
504
505 /* Print the value of a stat. */
506 void print_stat_value(const stat *s) {
507 void *v = s->stat;
508 double fv;
509 long lv;
510 long long llv;
511
512 switch (s->type) {
513 case LONG_LONG:
514 printf("%lld", *(long long *)v);
515 break;
516 case BYTES:
517 llv = *(long long *)v;
518 if (bytes_scale_factor != 0) {
519 llv /= bytes_scale_factor;
520 }
521 printf("%lld", llv);
522 break;
523 case TIME_T:
524 /* FIXME option for formatted time? */
525 lv = *(time_t *)v;
526 printf("%ld", lv);
527 break;
528 case FLOAT:
529 case DOUBLE:
530 if (s->type == FLOAT) {
531 fv = *(float *)v;
532 } else {
533 fv = *(double *)v;
534 }
535 if (float_scale_factor != 0) {
536 printf("%ld", (long)(float_scale_factor * fv));
537 } else {
538 printf("%f", fv);
539 }
540 break;
541 case STRING:
542 /* FIXME escaping? */
543 printf("%s", *(char **)v);
544 break;
545 case INT:
546 printf("%d", *(int *)v);
547 break;
548 case BOOL:
549 printf("%s", *(int *)v ? "true" : "false");
550 break;
551 case DUPLEX:
552 switch (*(sg_iface_duplex *) v) {
553 case SG_IFACE_DUPLEX_FULL:
554 printf("full");
555 break;
556 case SG_IFACE_DUPLEX_HALF:
557 printf("half");
558 break;
559 default:
560 printf("unknown");
561 break;
562 }
563 break;
564 }
565 }
566
567 /* Print the name and value of a stat. */
568 void print_stat(const stat *s) {
569 switch (display_mode) {
570 case DISPLAY_LINUX:
571 printf("%s = ", s->name);
572 break;
573 case DISPLAY_BSD:
574 printf("%s: ", s->name);
575 break;
576 case DISPLAY_MRTG:
577 case DISPLAY_PLAIN:
578 break;
579 }
580 print_stat_value(s);
581 printf("\n");
582 }
583
584 /* Print stats as specified on the provided command line. */
585 void print_stats(int argc, char **argv) {
586 int i;
587
588 if (argc == optind) {
589 /* Print all stats. */
590 for (i = 0; i < num_stats; i++)
591 print_stat(&stats[i]);
592 } else {
593 /* Print selected stats. */
594 for (i = optind; i < argc; i++) {
595 char *name = argv[i];
596 stat key;
597 const stat *s, *end;
598 int (*compare)(const void *, const void *);
599
600 key.name = name;
601 if (name[strlen(name) - 1] == '.')
602 compare = stats_compare_prefix;
603 else
604 compare = stats_compare;
605
606 if (stats == NULL) {
607 s = NULL;
608 } else {
609 s = (const stat *)bsearch(&key, stats,
610 num_stats,
611 sizeof *stats,
612 compare);
613 }
614
615 if (s == NULL) {
616 printf("Unknown stat %s\n", name);
617 continue;
618 }
619
620 /* Find the range of stats the user wanted. */
621 for (; s >= stats; s--) {
622 if (compare(&key, s) != 0)
623 break;
624 }
625 s++;
626 for (end = s; end < &stats[num_stats]; end++) {
627 if (compare(&key, end) != 0)
628 break;
629 }
630
631 /* And print them. */
632 for (; s < end; s++) {
633 print_stat(s);
634 }
635 }
636 }
637 }
638
639 void usage() {
640 printf("Usage: statgrab [OPTION]... [STAT]...\n"
641 "Display system statistics.\n"
642 "\n"
643 "If no STATs are given, all will be displayed. Specify 'STAT.' to display all\n"
644 "statistics starting with that prefix.\n"
645 "\n");
646 printf(" -l Linux sysctl-style output (default)\n"
647 " -b BSD sysctl-style output\n"
648 " -m MRTG-compatible output\n"
649 " -u Plain output (only show values)\n"
650 " -n Display cumulative stats once (default)\n"
651 " -s Display stat differences repeatedly\n"
652 " -o Display stat differences once\n"
653 " -t DELAY When repeating, wait DELAY seconds between updates (default 1)\n"
654 " -p Display CPU usage differences as percentages rather than\n"
655 " absolute values\n"
656 " -f FACTOR Display floating-point values as integers scaled by FACTOR\n"
657 " -K Display byte counts in kibibytes\n"
658 " -M Display byte counts in mebibytes\n"
659 " -G Display byte counts in gibibytes\n"
660 "\n");
661 printf("Version %s - report bugs to <%s>.\n",
662 PACKAGE_VERSION, PACKAGE_BUGREPORT);
663 exit(1);
664 }
665
666 int main(int argc, char **argv) {
667 opterr = 0;
668 while (1) {
669 int c = getopt(argc, argv, "lbmunsot:pf:KMG");
670 if (c == -1)
671 break;
672 switch (c) {
673 case 'l':
674 display_mode = DISPLAY_LINUX;
675 break;
676 case 'b':
677 display_mode = DISPLAY_BSD;
678 break;
679 case 'm':
680 display_mode = DISPLAY_MRTG;
681 break;
682 case 'u':
683 display_mode = DISPLAY_PLAIN;
684 break;
685 case 'n':
686 repeat_mode = REPEAT_NONE;
687 break;
688 case 's':
689 repeat_mode = REPEAT_FOREVER;
690 break;
691 case 'o':
692 repeat_mode = REPEAT_ONCE;
693 break;
694 case 't':
695 repeat_time = atoi(optarg);
696 break;
697 case 'p':
698 use_cpu_percent = 1;
699 break;
700 case 'f':
701 float_scale_factor = atol(optarg);
702 break;
703 case 'K':
704 bytes_scale_factor = 1024;
705 break;
706 case 'M':
707 bytes_scale_factor = 1024 * 1024;
708 break;
709 case 'G':
710 bytes_scale_factor = 1024 * 1024 * 1024;
711 break;
712 default:
713 usage();
714 }
715 }
716
717 if (display_mode == DISPLAY_MRTG) {
718 if ((argc - optind) != 2)
719 die("mrtg mode: must specify exactly two stats");
720 if (repeat_mode == REPEAT_FOREVER)
721 die("mrtg mode: cannot repeat display");
722 }
723
724 if (use_cpu_percent && repeat_mode == REPEAT_NONE)
725 die("CPU percentage usage display requires stat differences");
726
727 if (repeat_mode == REPEAT_NONE)
728 use_diffs = 0;
729 else
730 use_diffs = 1;
731
732 select_interesting(argc - optind, &argv[optind]);
733
734 /* We don't care if sg_init fails, because we can just display
735 the statistics that can be read as non-root. */
736 sg_init();
737 if (sg_drop_privileges() != 0)
738 die("Failed to drop setuid/setgid privileges");
739
740 switch (repeat_mode) {
741 case REPEAT_NONE:
742 get_stats();
743 print_stats(argc, argv);
744 break;
745 case REPEAT_ONCE:
746 get_stats();
747 sleep(repeat_time);
748 get_stats();
749 print_stats(argc, argv);
750 break;
751 case REPEAT_FOREVER:
752 while (1) {
753 get_stats();
754 print_stats(argc, argv);
755 printf("\n");
756 sleep(repeat_time);
757 }
758 }
759
760 if (display_mode == DISPLAY_MRTG) {
761 printf("\n");
762 printf("statgrab\n");
763 }
764
765 return 0;
766 }
767