ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/libstatgrab/src/statgrab/statgrab.c
Revision: 1.21
Committed: Wed Feb 18 17:29:15 2004 UTC (20 years, 8 months ago) by ats
Content type: text/plain
Branch: MAIN
Changes since 1.20: +12 -4 lines
Log Message:
Buggy Solaris qsort/bsearch still try to access the array if the length
is zero.

File Contents

# User Rev Content
1 tdb 1.1 /*
2     * i-scream central monitoring system
3     * http://www.i-scream.org
4 tdb 1.16 * Copyright (C) 2000-2004 i-scream
5 tdb 1.1 *
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 tdb 1.17 *
20 ats 1.21 * $Id: statgrab.c,v 1.20 2004/02/14 00:06:00 ats Exp $
21 tdb 1.1 */
22    
23 tdb 1.3 #ifdef HAVE_CONFIG_H
24     #include "config.h"
25     #endif
26    
27 tdb 1.1 #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     TIME_T,
37     FLOAT,
38     DOUBLE,
39     STRING,
40 ats 1.18 INT,
41 ats 1.20 BOOL,
42 ats 1.18 DUPLEX
43 tdb 1.1 } stat_type;
44    
45     typedef enum {
46     DISPLAY_LINUX = 0,
47     DISPLAY_BSD,
48     DISPLAY_MRTG,
49     DISPLAY_PLAIN
50     } display_mode_type;
51    
52     typedef enum {
53     REPEAT_NONE = 0,
54     REPEAT_ONCE,
55     REPEAT_FOREVER
56     } repeat_mode_type;
57    
58     typedef struct {
59     char *name;
60     stat_type type;
61     void *stat;
62     } stat;
63    
64     stat *stats = NULL;
65     int num_stats = 0;
66     int alloc_stats = 0;
67     #define INCREMENT_STATS 64
68    
69     display_mode_type display_mode = DISPLAY_LINUX;
70     repeat_mode_type repeat_mode = REPEAT_NONE;
71     int repeat_time = 1;
72     int use_cpu_percent = 0;
73 ats 1.8 int use_diffs = 0;
74 tdb 1.1
75     /* Exit with an error message. */
76     void die(const char *s) {
77     fprintf(stderr, "fatal: %s\n", s);
78     exit(1);
79     }
80    
81     /* Remove all the recorded stats. */
82     void clear_stats() {
83     int i;
84    
85     for (i = 0; i < num_stats; i++)
86     free(stats[i].name);
87     num_stats = 0;
88     }
89    
90     /* Add a stat. The varargs make up the name, joined with dots; the name is
91     terminated with a NULL. */
92     void add_stat(stat_type type, void *stat, ...) {
93     va_list ap;
94     int len = 0;
95     char *name, *p;
96    
97     /* Figure out how long the name will be, including dots and trailing
98     \0. */
99     va_start(ap, stat);
100     while (1) {
101     const char *part = va_arg(ap, const char *);
102     if (part == NULL)
103     break;
104     len += 1 + strlen(part);
105     }
106     va_end(ap);
107    
108     /* Paste the name together. */
109     name = malloc(len);
110     if (name == NULL)
111     die("out of memory");
112     p = name;
113     va_start(ap, stat);
114     while (1) {
115     const char *part = va_arg(ap, const char *);
116     int partlen;
117     if (part == NULL)
118     break;
119     partlen = strlen(part);
120     memcpy(p, part, partlen);
121     p += partlen;
122     *p++ = '.';
123     }
124     va_end(ap);
125     *--p = '\0';
126    
127     /* Replace spaces with underscores. */
128     for (p = name; *p != '\0'; p++) {
129     if (*p == ' ')
130     *p = '_';
131     }
132    
133     /* Stretch the stats array if necessary. */
134     if (num_stats >= alloc_stats) {
135     alloc_stats += INCREMENT_STATS;
136     stats = realloc(stats, alloc_stats * sizeof *stats);
137     if (stats == NULL)
138     die("out of memory");
139     }
140    
141     stats[num_stats].name = name;
142     stats[num_stats].type = type;
143     stats[num_stats].stat = stat;
144     ++num_stats;
145     }
146    
147 ats 1.9 /* Compare two stats by name for qsort and bsearch. */
148 tdb 1.1 int stats_compare(const void *a, const void *b) {
149     return strcmp(((stat *)a)->name, ((stat *)b)->name);
150     }
151    
152 ats 1.9 /* Compare up to the length of the key for bsearch. */
153     int stats_compare_prefix(const void *key, const void *item) {
154     const char *kn = ((stat *)key)->name;
155     const char *in = ((stat *)item)->name;
156    
157     return strncmp(kn, in, strlen(kn));
158     }
159    
160 ats 1.8 void populate_const() {
161 tdb 1.1 static int zero = 0;
162    
163     /* Constants, for use with MRTG mode. */
164     add_stat(INT, &zero, "const", "0", NULL);
165 ats 1.8 }
166 tdb 1.1
167 ats 1.8 void populate_cpu() {
168     if (use_cpu_percent) {
169     cpu_percent_t *cpu_p = cpu_percent_usage();
170 tdb 1.1
171     if (cpu_p != NULL) {
172     add_stat(FLOAT, &cpu_p->user,
173     "cpu", "user", NULL);
174     add_stat(FLOAT, &cpu_p->kernel,
175     "cpu", "kernel", NULL);
176     add_stat(FLOAT, &cpu_p->idle,
177     "cpu", "idle", NULL);
178     add_stat(FLOAT, &cpu_p->iowait,
179     "cpu", "iowait", NULL);
180     add_stat(FLOAT, &cpu_p->swap,
181     "cpu", "swap", NULL);
182     add_stat(FLOAT, &cpu_p->nice,
183     "cpu", "nice", NULL);
184 ats 1.7 add_stat(TIME_T, &cpu_p->time_taken,
185 tdb 1.1 "cpu", "time_taken", NULL);
186     }
187     } else {
188 ats 1.8 cpu_states_t *cpu_s;
189    
190 tdb 1.1 cpu_s = use_diffs ? get_cpu_diff() : get_cpu_totals();
191     if (cpu_s != NULL) {
192     add_stat(LONG_LONG, &cpu_s->user,
193     "cpu", "user", NULL);
194     add_stat(LONG_LONG, &cpu_s->kernel,
195     "cpu", "kernel", NULL);
196     add_stat(LONG_LONG, &cpu_s->idle,
197     "cpu", "idle", NULL);
198     add_stat(LONG_LONG, &cpu_s->iowait,
199     "cpu", "iowait", NULL);
200     add_stat(LONG_LONG, &cpu_s->swap,
201     "cpu", "swap", NULL);
202     add_stat(LONG_LONG, &cpu_s->nice,
203     "cpu", "nice", NULL);
204     add_stat(LONG_LONG, &cpu_s->total,
205     "cpu", "total", NULL);
206     add_stat(TIME_T, &cpu_s->systime,
207     "cpu", "systime", NULL);
208     }
209     }
210 ats 1.8 }
211    
212     void populate_mem() {
213     mem_stat_t *mem = get_memory_stats();
214 tdb 1.1
215     if (mem != NULL) {
216     add_stat(LONG_LONG, &mem->total, "mem", "total", NULL);
217     add_stat(LONG_LONG, &mem->free, "mem", "free", NULL);
218     add_stat(LONG_LONG, &mem->used, "mem", "used", NULL);
219     add_stat(LONG_LONG, &mem->cache, "mem", "cache", NULL);
220     }
221 ats 1.8 }
222    
223     void populate_load() {
224     load_stat_t *load = get_load_stats();
225 tdb 1.1
226     if (load != NULL) {
227     add_stat(DOUBLE, &load->min1, "load", "min1", NULL);
228     add_stat(DOUBLE, &load->min5, "load", "min5", NULL);
229     add_stat(DOUBLE, &load->min15, "load", "min15", NULL);
230     }
231 ats 1.8 }
232    
233     void populate_user() {
234     user_stat_t *user = get_user_stats();
235 tdb 1.1
236     if (user != NULL) {
237     add_stat(INT, &user->num_entries, "user", "num", NULL);
238     add_stat(STRING, &user->name_list, "user", "names", NULL);
239     }
240 ats 1.8 }
241    
242     void populate_swap() {
243     swap_stat_t *swap = get_swap_stats();
244 tdb 1.1
245     if (swap != NULL) {
246     add_stat(LONG_LONG, &swap->total, "swap", "total", NULL);
247     add_stat(LONG_LONG, &swap->used, "swap", "used", NULL);
248     add_stat(LONG_LONG, &swap->free, "swap", "free", NULL);
249     }
250 ats 1.8 }
251    
252     void populate_general() {
253     general_stat_t *gen = get_general_stats();
254 tdb 1.1
255     if (gen != NULL) {
256     add_stat(STRING, &gen->os_name,
257     "general", "os_name", NULL);
258     add_stat(STRING, &gen->os_release,
259     "general", "os_release", NULL);
260     add_stat(STRING, &gen->os_version,
261     "general", "os_version", NULL);
262     add_stat(STRING, &gen->platform, "general", "platform", NULL);
263     add_stat(STRING, &gen->hostname, "general", "hostname", NULL);
264     add_stat(TIME_T, &gen->uptime, "general", "uptime", NULL);
265     }
266 ats 1.8 }
267    
268     void populate_fs() {
269     int n, i;
270     disk_stat_t *disk = get_disk_stats(&n);
271 tdb 1.1
272     if (disk != NULL) {
273     for (i = 0; i < n; i++) {
274     /* FIXME it'd be nicer if libstatgrab did this */
275 ats 1.10 char *buf, *name, *p;
276     const char *device = disk[i].device_name;
277    
278     if (strcmp(device, "/") == 0)
279     device = "root";
280    
281     buf = strdup(device);
282     if (buf == NULL)
283     die("out of memory");
284    
285     name = buf;
286 ats 1.13 if (strlen(name) == 2 && name[1] == ':')
287     name[1] = '\0';
288 ats 1.10 if (strncmp(name, "/dev/", 5) == 0)
289     name += 5;
290     while ((p = strchr(name, '/')) != NULL)
291     *p = '_';
292    
293 tdb 1.1 add_stat(STRING, &disk[i].device_name,
294     "fs", name, "device_name", NULL);
295     add_stat(STRING, &disk[i].fs_type,
296     "fs", name, "fs_type", NULL);
297     add_stat(STRING, &disk[i].mnt_point,
298     "fs", name, "mnt_point", NULL);
299     add_stat(LONG_LONG, &disk[i].size,
300     "fs", name, "size", NULL);
301     add_stat(LONG_LONG, &disk[i].used,
302     "fs", name, "used", NULL);
303     add_stat(LONG_LONG, &disk[i].avail,
304     "fs", name, "avail", NULL);
305     add_stat(LONG_LONG, &disk[i].total_inodes,
306     "fs", name, "total_inodes", NULL);
307     add_stat(LONG_LONG, &disk[i].used_inodes,
308     "fs", name, "used_inodes", NULL);
309     add_stat(LONG_LONG, &disk[i].free_inodes,
310     "fs", name, "free_inodes", NULL);
311 ats 1.10
312     free(buf);
313 tdb 1.1 }
314     }
315 ats 1.8 }
316    
317     void populate_disk() {
318     int n, i;
319     diskio_stat_t *diskio;
320 tdb 1.1
321     diskio = use_diffs ? get_diskio_stats_diff(&n) : get_diskio_stats(&n);
322     if (diskio != NULL) {
323     for (i = 0; i < n; i++) {
324     const char *name = diskio[i].disk_name;
325    
326     add_stat(STRING, &diskio[i].disk_name,
327     "disk", name, "disk_name", NULL);
328     add_stat(LONG_LONG, &diskio[i].read_bytes,
329     "disk", name, "read_bytes", NULL);
330     add_stat(LONG_LONG, &diskio[i].write_bytes,
331     "disk", name, "write_bytes", NULL);
332     add_stat(TIME_T, &diskio[i].systime,
333     "disk", name, "systime", NULL);
334     }
335     }
336 ats 1.8 }
337    
338     void populate_proc() {
339     process_stat_t *proc = get_process_stats();
340 tdb 1.1
341     if (proc != NULL) {
342     add_stat(INT, &proc->total, "proc", "total", NULL);
343     add_stat(INT, &proc->running, "proc", "running", NULL);
344     add_stat(INT, &proc->sleeping, "proc", "sleeping", NULL);
345     add_stat(INT, &proc->stopped, "proc", "stopped", NULL);
346     add_stat(INT, &proc->zombie, "proc", "zombie", NULL);
347     }
348 ats 1.8 }
349    
350     void populate_net() {
351     int n, i;
352     network_stat_t *net;
353 ats 1.18 network_iface_stat_t *iface;
354 tdb 1.1
355     net = use_diffs ? get_network_stats_diff(&n) : get_network_stats(&n);
356     if (net != NULL) {
357     for (i = 0; i < n; i++) {
358     const char *name = net[i].interface_name;
359    
360     add_stat(STRING, &net[i].interface_name,
361     "net", name, "interface_name", NULL);
362     add_stat(LONG_LONG, &net[i].tx,
363     "net", name, "tx", NULL);
364     add_stat(LONG_LONG, &net[i].rx,
365     "net", name, "rx", NULL);
366     add_stat(TIME_T, &net[i].systime,
367     "net", name, "systime", NULL);
368     }
369     }
370 ats 1.18
371     iface = get_network_iface_stats(&n);
372     if (iface != NULL) {
373     for (i = 0; i < n; i++) {
374     const char *name = iface[i].interface_name;
375    
376     add_stat(INT, &iface[i].speed,
377     "net", name, "speed", NULL);
378 ats 1.20 add_stat(BOOL, &iface[i].up,
379     "net", name, "up", NULL);
380 ats 1.18 add_stat(DUPLEX, &iface[i].dup,
381     "net", name, "duplex", NULL);
382     }
383     }
384 ats 1.8 }
385    
386     void populate_page() {
387     page_stat_t *page;
388 tdb 1.1
389     page = use_diffs ? get_page_stats_diff() : get_page_stats();
390     if (page != NULL) {
391     add_stat(LONG_LONG, &page->pages_pagein, "page", "in", NULL);
392     add_stat(LONG_LONG, &page->pages_pageout, "page", "out", NULL);
393 ats 1.11 add_stat(TIME_T, &page->systime, "page", "systime", NULL);
394 tdb 1.1 }
395 ats 1.8 }
396    
397     typedef struct {
398     const char *name;
399     void (*populate)();
400     int interesting;
401     } toplevel;
402     toplevel toplevels[] = {
403     {"const.", populate_const, 0},
404     {"cpu.", populate_cpu, 0},
405     {"mem.", populate_mem, 0},
406     {"load.", populate_load, 0},
407     {"user.", populate_user, 0},
408     {"swap.", populate_swap, 0},
409     {"general.", populate_general, 0},
410     {"fs.", populate_fs, 0},
411     {"disk.", populate_disk, 0},
412     {"proc.", populate_proc, 0},
413     {"net.", populate_net, 0},
414     {"page.", populate_page, 0},
415     {NULL, NULL, 0}
416     };
417    
418     /* Set the "interesting" flag on the sections that we actually need to
419     fetch. */
420     void select_interesting(int argc, char **argv) {
421     toplevel *t;
422    
423     if (argc == 0) {
424     for (t = &toplevels[0]; t->name != NULL; t++)
425     t->interesting = 1;
426     } else {
427     int i;
428    
429     for (i = 0; i < argc; i++) {
430     for (t = &toplevels[0]; t->name != NULL; t++) {
431     if (strncmp(argv[i], t->name,
432     strlen(t->name)) == 0) {
433     t->interesting = 1;
434     break;
435     }
436     }
437     }
438     }
439     }
440    
441     /* Clear and rebuild the stats array. */
442     void get_stats() {
443     toplevel *t;
444    
445     clear_stats();
446    
447     for (t = &toplevels[0]; t->name != NULL; t++) {
448     if (t->interesting)
449     t->populate();
450     }
451 tdb 1.1
452 ats 1.21 if (stats != NULL)
453     qsort(stats, num_stats, sizeof *stats, stats_compare);
454 tdb 1.1 }
455    
456     /* Print the value of a stat. */
457     void print_stat_value(const stat *s) {
458     void *v = s->stat;
459    
460     switch (s->type) {
461     case LONG_LONG:
462     printf("%lld", *(long long *)v);
463     break;
464     case TIME_T:
465     /* FIXME option for formatted time? */
466     printf("%ld", *(time_t *)v);
467     break;
468     case FLOAT:
469     printf("%f", *(float *)v);
470     break;
471     case DOUBLE:
472     printf("%f", *(double *)v);
473     break;
474     case STRING:
475     /* FIXME escaping? */
476     printf("%s", *(char **)v);
477     break;
478     case INT:
479     printf("%d", *(int *)v);
480 ats 1.20 break;
481     case BOOL:
482     printf("%s", *(int *)v ? "true" : "false");
483 ats 1.18 break;
484     case DUPLEX:
485 ats 1.19 switch (*(statgrab_duplex *) v) {
486 ats 1.18 case FULL_DUPLEX:
487     printf("full");
488     break;
489     case HALF_DUPLEX:
490     printf("half");
491     break;
492     default:
493     printf("unknown");
494     break;
495     }
496 tdb 1.1 break;
497     }
498     }
499    
500     /* Print the name and value of a stat. */
501     void print_stat(const stat *s) {
502     switch (display_mode) {
503     case DISPLAY_LINUX:
504     printf("%s = ", s->name);
505     break;
506     case DISPLAY_BSD:
507     printf("%s: ", s->name);
508     break;
509     case DISPLAY_MRTG:
510     case DISPLAY_PLAIN:
511     break;
512     }
513     print_stat_value(s);
514     printf("\n");
515     }
516    
517     /* Print stats as specified on the provided command line. */
518     void print_stats(int argc, char **argv) {
519     int i;
520    
521     if (argc == optind) {
522     /* Print all stats. */
523     for (i = 0; i < num_stats; i++)
524     print_stat(&stats[i]);
525     } else {
526     /* Print selected stats. */
527     for (i = optind; i < argc; i++) {
528 ats 1.9 char *name = argv[i];
529 tdb 1.1 stat key;
530 ats 1.9 const stat *s, *end;
531     int (*compare)(const void *, const void *);
532    
533     key.name = name;
534     if (name[strlen(name) - 1] == '.')
535     compare = stats_compare_prefix;
536     else
537     compare = stats_compare;
538 tdb 1.1
539 ats 1.21 if (stats == NULL) {
540     s = NULL;
541     } else {
542     s = (const stat *)bsearch(&key, stats,
543     num_stats,
544     sizeof *stats,
545     compare);
546     }
547    
548 ats 1.9 if (s == NULL) {
549     printf("Unknown stat %s\n", name);
550     continue;
551     }
552    
553     /* Find the range of stats the user wanted. */
554     for (; s >= stats; s--) {
555     if (compare(&key, s) != 0)
556     break;
557     }
558     s++;
559     for (end = s; end < &stats[num_stats]; end++) {
560     if (compare(&key, end) != 0)
561     break;
562     }
563    
564     /* And print them. */
565     for (; s < end; s++) {
566 tdb 1.1 print_stat(s);
567     }
568     }
569     }
570     }
571    
572     void usage() {
573     printf("Usage: statgrab [OPTION]... [STAT]...\n"
574 ats 1.9 "Display system statistics.\n"
575     "\n"
576     "If no STATs are given, all will be displayed. Specify 'STAT.' to display all\n"
577     "statistics starting with that prefix.\n"
578 tdb 1.1 "\n");
579     printf(" -l Linux sysctl-style output (default)\n"
580     " -b BSD sysctl-style output\n"
581     " -m MRTG-compatible output\n"
582     " -u Plain output (only show values)\n"
583     " -n Display cumulative stats once (default)\n"
584     " -s Display stat differences repeatedly\n"
585     " -o Display stat differences once\n"
586     " -t DELAY When repeating, wait DELAY seconds between updates (default 1)\n"
587 ats 1.5 " -p Display CPU usage differences as percentages rather than\n"
588     " absolute values\n"
589 tdb 1.1 "\n");
590 tdb 1.2 printf("Version %s - report bugs to <%s>.\n",
591 ats 1.5 PACKAGE_VERSION, PACKAGE_BUGREPORT);
592 tdb 1.1 exit(1);
593     }
594    
595     int main(int argc, char **argv) {
596     opterr = 0;
597     while (1) {
598     int c = getopt(argc, argv, "lbmunsot:p");
599     if (c == -1)
600     break;
601     switch (c) {
602     case 'l':
603     display_mode = DISPLAY_LINUX;
604     break;
605     case 'b':
606     display_mode = DISPLAY_BSD;
607     break;
608     case 'm':
609     display_mode = DISPLAY_MRTG;
610     break;
611     case 'u':
612     display_mode = DISPLAY_PLAIN;
613     break;
614     case 'n':
615     repeat_mode = REPEAT_NONE;
616     break;
617     case 's':
618     repeat_mode = REPEAT_FOREVER;
619     break;
620     case 'o':
621     repeat_mode = REPEAT_ONCE;
622     break;
623     case 't':
624     repeat_time = atoi(optarg);
625     break;
626     case 'p':
627     use_cpu_percent = 1;
628     break;
629     default:
630     usage();
631     }
632     }
633    
634     if (display_mode == DISPLAY_MRTG) {
635     if ((argc - optind) != 2)
636     die("mrtg mode: must specify exactly two stats");
637 ats 1.5 if (repeat_mode == REPEAT_FOREVER)
638 tdb 1.1 die("mrtg mode: cannot repeat display");
639     }
640 ats 1.5
641     if (use_cpu_percent && repeat_mode == REPEAT_NONE)
642     die("CPU percentage usage display requires stat differences");
643 tdb 1.1
644 ats 1.8 if (repeat_mode == REPEAT_NONE)
645     use_diffs = 0;
646     else
647     use_diffs = 1;
648    
649     select_interesting(argc - optind, &argv[optind]);
650    
651 ats 1.12 /* We don't care if statgrab_init fails, because we can just display
652     the statistics that can be read as non-root. */
653     statgrab_init();
654 ats 1.15 if (statgrab_drop_privileges() != 0)
655     die("Failed to drop setuid/setgid privileges");
656 ats 1.12
657 tdb 1.1 switch (repeat_mode) {
658     case REPEAT_NONE:
659 ats 1.8 get_stats();
660 tdb 1.1 print_stats(argc, argv);
661     break;
662     case REPEAT_ONCE:
663 ats 1.8 get_stats();
664 tdb 1.1 sleep(repeat_time);
665 ats 1.8 get_stats();
666 tdb 1.1 print_stats(argc, argv);
667     break;
668     case REPEAT_FOREVER:
669     while (1) {
670 ats 1.8 get_stats();
671 tdb 1.1 print_stats(argc, argv);
672     printf("\n");
673     sleep(repeat_time);
674     }
675     }
676    
677     if (display_mode == DISPLAY_MRTG) {
678     printf("\n");
679     printf("statgrab\n");
680     }
681    
682     return 0;
683     }
684