ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/libstatgrab/src/statgrab/statgrab.c
Revision: 1.34
Committed: Wed Jul 13 09:31:54 2005 UTC (18 years, 10 months ago) by tdb
Content type: text/plain
Branch: MAIN
Changes since 1.33: +2 -2 lines
Log Message:
Change the name of the duplex value from "dup" to "duplex". This will
break the ABI, but we're changing the fs stats anyway. The API change
is minimal, so hopefully won't affect too many people.

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.33 2005/04/25 12:04:30 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
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].duplex,
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 FACTOR Display floating-point values as integers scaled by FACTOR\n"
643 " -K Display byte counts in kibibytes\n"
644 " -M Display byte counts in mebibytes\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