ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/libstatgrab/src/statgrab/statgrab.c
Revision: 1.30
Committed: Tue Aug 10 18:58:42 2004 UTC (20 years, 3 months ago) by ats
Content type: text/plain
Branch: MAIN
Changes since 1.29: +41 -19 lines
Log Message:
Add -K, -M and -G to display byte counts in kibibytes, mibibytes and
gibibytes (mostly for MRTG use, since it can't deal with numbers bigger
than 32 bits).

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.29 2004/08/10 18:50:37 ats 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
317 free(buf);
318 }
319 }
320 }
321
322 void populate_disk() {
323 int n, i;
324 sg_disk_io_stats *diskio;
325
326 diskio = use_diffs ? sg_get_disk_io_stats_diff(&n)
327 : sg_get_disk_io_stats(&n);
328 if (diskio != NULL) {
329 for (i = 0; i < n; i++) {
330 const char *name = diskio[i].disk_name;
331
332 add_stat(STRING, &diskio[i].disk_name,
333 "disk", name, "disk_name", NULL);
334 add_stat(BYTES, &diskio[i].read_bytes,
335 "disk", name, "read_bytes", NULL);
336 add_stat(BYTES, &diskio[i].write_bytes,
337 "disk", name, "write_bytes", NULL);
338 add_stat(TIME_T, &diskio[i].systime,
339 "disk", name, "systime", NULL);
340 }
341 }
342 }
343
344 void populate_proc() {
345 /* FIXME expose individual process info too */
346 sg_process_count *proc = sg_get_process_count();
347
348 if (proc != NULL) {
349 add_stat(INT, &proc->total, "proc", "total", NULL);
350 add_stat(INT, &proc->running, "proc", "running", NULL);
351 add_stat(INT, &proc->sleeping, "proc", "sleeping", NULL);
352 add_stat(INT, &proc->stopped, "proc", "stopped", NULL);
353 add_stat(INT, &proc->zombie, "proc", "zombie", NULL);
354 }
355 }
356
357 void populate_net() {
358 int num_io, num_iface, i;
359 sg_network_io_stats *io;
360 sg_network_iface_stats *iface;
361
362 io = use_diffs ? sg_get_network_io_stats_diff(&num_io)
363 : sg_get_network_io_stats(&num_io);
364 if (io != NULL) {
365 for (i = 0; i < num_io; i++) {
366 const char *name = io[i].interface_name;
367
368 add_stat(STRING, &io[i].interface_name,
369 "net", name, "interface_name", NULL);
370 add_stat(BYTES, &io[i].tx,
371 "net", name, "tx", NULL);
372 add_stat(BYTES, &io[i].rx,
373 "net", name, "rx", NULL);
374 add_stat(LONG_LONG, &io[i].ipackets,
375 "net", name, "ipackets", NULL);
376 add_stat(LONG_LONG, &io[i].opackets,
377 "net", name, "opackets", NULL);
378 add_stat(LONG_LONG, &io[i].ierrors,
379 "net", name, "ierrors", NULL);
380 add_stat(LONG_LONG, &io[i].oerrors,
381 "net", name, "oerrors", NULL);
382 add_stat(LONG_LONG, &io[i].collisions,
383 "net", name, "collisions", NULL);
384 add_stat(TIME_T, &io[i].systime,
385 "net", name, "systime", NULL);
386 }
387 }
388
389 iface = sg_get_network_iface_stats(&num_iface);
390 if (iface != NULL) {
391 for (i = 0; i < num_iface; i++) {
392 const char *name = iface[i].interface_name;
393 int had_io = 0, j;
394
395 /* If there wasn't a corresponding io stat,
396 add interface_name from here. */
397 if (io != NULL) {
398 for (j = 0; j < num_io; j++) {
399 if (strcmp(io[j].interface_name,
400 name) == 0) {
401 had_io = 1;
402 break;
403 }
404 }
405 }
406 if (!had_io) {
407 add_stat(STRING, &iface[i].interface_name,
408 "net", name, "interface_name", NULL);
409 }
410
411 add_stat(INT, &iface[i].speed,
412 "net", name, "speed", NULL);
413 add_stat(BOOL, &iface[i].up,
414 "net", name, "up", NULL);
415 add_stat(DUPLEX, &iface[i].dup,
416 "net", name, "duplex", NULL);
417 }
418 }
419 }
420
421 void populate_page() {
422 sg_page_stats *page;
423
424 page = use_diffs ? sg_get_page_stats_diff() : sg_get_page_stats();
425 if (page != NULL) {
426 add_stat(LONG_LONG, &page->pages_pagein, "page", "in", NULL);
427 add_stat(LONG_LONG, &page->pages_pageout, "page", "out", NULL);
428 add_stat(TIME_T, &page->systime, "page", "systime", NULL);
429 }
430 }
431
432 typedef struct {
433 const char *name;
434 void (*populate)();
435 int interesting;
436 } toplevel;
437 toplevel toplevels[] = {
438 {"const.", populate_const, 0},
439 {"cpu.", populate_cpu, 0},
440 {"mem.", populate_mem, 0},
441 {"load.", populate_load, 0},
442 {"user.", populate_user, 0},
443 {"swap.", populate_swap, 0},
444 {"general.", populate_general, 0},
445 {"fs.", populate_fs, 0},
446 {"disk.", populate_disk, 0},
447 {"proc.", populate_proc, 0},
448 {"net.", populate_net, 0},
449 {"page.", populate_page, 0},
450 {NULL, NULL, 0}
451 };
452
453 /* Set the "interesting" flag on the sections that we actually need to
454 fetch. */
455 void select_interesting(int argc, char **argv) {
456 toplevel *t;
457
458 if (argc == 0) {
459 for (t = &toplevels[0]; t->name != NULL; t++)
460 t->interesting = 1;
461 } else {
462 int i;
463
464 for (i = 0; i < argc; i++) {
465 for (t = &toplevels[0]; t->name != NULL; t++) {
466 if (strncmp(argv[i], t->name,
467 strlen(t->name)) == 0) {
468 t->interesting = 1;
469 break;
470 }
471 }
472 }
473 }
474 }
475
476 /* Clear and rebuild the stats array. */
477 void get_stats() {
478 toplevel *t;
479
480 clear_stats();
481
482 for (t = &toplevels[0]; t->name != NULL; t++) {
483 if (t->interesting)
484 t->populate();
485 }
486
487 if (stats != NULL)
488 qsort(stats, num_stats, sizeof *stats, stats_compare);
489 }
490
491 /* Print the value of a stat. */
492 void print_stat_value(const stat *s) {
493 void *v = s->stat;
494 double fv;
495 long lv;
496 long long llv;
497
498 switch (s->type) {
499 case LONG_LONG:
500 printf("%lld", *(long long *)v);
501 break;
502 case BYTES:
503 llv = *(long long *)v;
504 if (bytes_scale_factor != 0) {
505 llv /= bytes_scale_factor;
506 }
507 printf("%lld", llv);
508 break;
509 case TIME_T:
510 /* FIXME option for formatted time? */
511 lv = *(time_t *)v;
512 printf("%ld", lv);
513 break;
514 case FLOAT:
515 case DOUBLE:
516 if (s->type == FLOAT) {
517 fv = *(float *)v;
518 } else {
519 fv = *(double *)v;
520 }
521 if (float_scale_factor != 0) {
522 printf("%ld", (long)(float_scale_factor * fv));
523 } else {
524 printf("%f", fv);
525 }
526 break;
527 case STRING:
528 /* FIXME escaping? */
529 printf("%s", *(char **)v);
530 break;
531 case INT:
532 printf("%d", *(int *)v);
533 break;
534 case BOOL:
535 printf("%s", *(int *)v ? "true" : "false");
536 break;
537 case DUPLEX:
538 switch (*(sg_iface_duplex *) v) {
539 case SG_IFACE_DUPLEX_FULL:
540 printf("full");
541 break;
542 case SG_IFACE_DUPLEX_HALF:
543 printf("half");
544 break;
545 default:
546 printf("unknown");
547 break;
548 }
549 break;
550 }
551 }
552
553 /* Print the name and value of a stat. */
554 void print_stat(const stat *s) {
555 switch (display_mode) {
556 case DISPLAY_LINUX:
557 printf("%s = ", s->name);
558 break;
559 case DISPLAY_BSD:
560 printf("%s: ", s->name);
561 break;
562 case DISPLAY_MRTG:
563 case DISPLAY_PLAIN:
564 break;
565 }
566 print_stat_value(s);
567 printf("\n");
568 }
569
570 /* Print stats as specified on the provided command line. */
571 void print_stats(int argc, char **argv) {
572 int i;
573
574 if (argc == optind) {
575 /* Print all stats. */
576 for (i = 0; i < num_stats; i++)
577 print_stat(&stats[i]);
578 } else {
579 /* Print selected stats. */
580 for (i = optind; i < argc; i++) {
581 char *name = argv[i];
582 stat key;
583 const stat *s, *end;
584 int (*compare)(const void *, const void *);
585
586 key.name = name;
587 if (name[strlen(name) - 1] == '.')
588 compare = stats_compare_prefix;
589 else
590 compare = stats_compare;
591
592 if (stats == NULL) {
593 s = NULL;
594 } else {
595 s = (const stat *)bsearch(&key, stats,
596 num_stats,
597 sizeof *stats,
598 compare);
599 }
600
601 if (s == NULL) {
602 printf("Unknown stat %s\n", name);
603 continue;
604 }
605
606 /* Find the range of stats the user wanted. */
607 for (; s >= stats; s--) {
608 if (compare(&key, s) != 0)
609 break;
610 }
611 s++;
612 for (end = s; end < &stats[num_stats]; end++) {
613 if (compare(&key, end) != 0)
614 break;
615 }
616
617 /* And print them. */
618 for (; s < end; s++) {
619 print_stat(s);
620 }
621 }
622 }
623 }
624
625 void usage() {
626 printf("Usage: statgrab [OPTION]... [STAT]...\n"
627 "Display system statistics.\n"
628 "\n"
629 "If no STATs are given, all will be displayed. Specify 'STAT.' to display all\n"
630 "statistics starting with that prefix.\n"
631 "\n");
632 printf(" -l Linux sysctl-style output (default)\n"
633 " -b BSD sysctl-style output\n"
634 " -m MRTG-compatible output\n"
635 " -u Plain output (only show values)\n"
636 " -n Display cumulative stats once (default)\n"
637 " -s Display stat differences repeatedly\n"
638 " -o Display stat differences once\n"
639 " -t DELAY When repeating, wait DELAY seconds between updates (default 1)\n"
640 " -p Display CPU usage differences as percentages rather than\n"
641 " absolute values\n"
642 " -f SCALE Display floating-point values as integers scaled by FACTOR\n"
643 " -K Display byte counts in kibibytes\n"
644 " -M Display byte counts in mibibytes\n"
645 " -G Display byte counts in gibibytes\n"
646 "\n");
647 printf("Version %s - report bugs to <%s>.\n",
648 PACKAGE_VERSION, PACKAGE_BUGREPORT);
649 exit(1);
650 }
651
652 int main(int argc, char **argv) {
653 opterr = 0;
654 while (1) {
655 int c = getopt(argc, argv, "lbmunsot:pf:KMG");
656 if (c == -1)
657 break;
658 switch (c) {
659 case 'l':
660 display_mode = DISPLAY_LINUX;
661 break;
662 case 'b':
663 display_mode = DISPLAY_BSD;
664 break;
665 case 'm':
666 display_mode = DISPLAY_MRTG;
667 break;
668 case 'u':
669 display_mode = DISPLAY_PLAIN;
670 break;
671 case 'n':
672 repeat_mode = REPEAT_NONE;
673 break;
674 case 's':
675 repeat_mode = REPEAT_FOREVER;
676 break;
677 case 'o':
678 repeat_mode = REPEAT_ONCE;
679 break;
680 case 't':
681 repeat_time = atoi(optarg);
682 break;
683 case 'p':
684 use_cpu_percent = 1;
685 break;
686 case 'f':
687 float_scale_factor = atol(optarg);
688 break;
689 case 'K':
690 bytes_scale_factor = 1024;
691 break;
692 case 'M':
693 bytes_scale_factor = 1024 * 1024;
694 break;
695 case 'G':
696 bytes_scale_factor = 1024 * 1024 * 1024;
697 break;
698 default:
699 usage();
700 }
701 }
702
703 if (display_mode == DISPLAY_MRTG) {
704 if ((argc - optind) != 2)
705 die("mrtg mode: must specify exactly two stats");
706 if (repeat_mode == REPEAT_FOREVER)
707 die("mrtg mode: cannot repeat display");
708 }
709
710 if (use_cpu_percent && repeat_mode == REPEAT_NONE)
711 die("CPU percentage usage display requires stat differences");
712
713 if (repeat_mode == REPEAT_NONE)
714 use_diffs = 0;
715 else
716 use_diffs = 1;
717
718 select_interesting(argc - optind, &argv[optind]);
719
720 /* We don't care if sg_init fails, because we can just display
721 the statistics that can be read as non-root. */
722 sg_init();
723 if (sg_drop_privileges() != 0)
724 die("Failed to drop setuid/setgid privileges");
725
726 switch (repeat_mode) {
727 case REPEAT_NONE:
728 get_stats();
729 print_stats(argc, argv);
730 break;
731 case REPEAT_ONCE:
732 get_stats();
733 sleep(repeat_time);
734 get_stats();
735 print_stats(argc, argv);
736 break;
737 case REPEAT_FOREVER:
738 while (1) {
739 get_stats();
740 print_stats(argc, argv);
741 printf("\n");
742 sleep(repeat_time);
743 }
744 }
745
746 if (display_mode == DISPLAY_MRTG) {
747 printf("\n");
748 printf("statgrab\n");
749 }
750
751 return 0;
752 }
753