/* lnstat.c: Unified linux network statistics * * Copyright (C) 2004 by Harald Welte * * Development of this code was funded by Astaro AG, http://www.astaro.com/ * * Based on original concept and ideas from predecessor rtstat.c: * * Copyright 2001 by Robert Olsson * Uppsala University, Sweden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #define LNSTAT_VERSION "0.01 040928" #define PROC_NET_STAT "/proc/net/stat" #define LNSTAT_MAX_FILES 32 #define LNSTAT_MAX_FIELDS_PER_LINE 32 #define LNSTAT_MAX_FIELD_NAME_LEN 32 struct lnstat_file; struct lnstat_field { struct lnstat_file *file; unsigned int num; /* field number in line */ char name[LNSTAT_MAX_FIELD_NAME_LEN+1]; unsigned long values[2]; /* two buffers for values */ }; struct lnstat_file { struct lnstat_file *next; char path[PATH_MAX+1]; char basename[NAME_MAX+1]; struct timeval last_read; /* last time of read */ struct timeval interval; /* interval */ FILE *fp; unsigned int num_fields; /* number of fields */ struct lnstat_field fields[LNSTAT_MAX_FIELDS_PER_LINE]; }; /* Read (and summarize for SMP) the different stats vars. */ static int scan_lines(struct lnstat_file *lf, int i) { int j, num_lines = 0; for (j = 0; j < lf->num_fields; j++) lf->fields[j].values[i] = 0; while(!feof(lf->fp)) { #define FGETS_BUF_SIZE 512 char buf[FGETS_BUF_SIZE]; char *ptr = buf; num_lines++; fgets(buf, FGETS_BUF_SIZE-1, lf->fp); gettimeofday(&lf->last_read, NULL); for (j = 0; j < lf->num_fields; j++) lf->fields[j].values[i] += strtoul(ptr, &ptr, 16); } return num_lines; } static int time_after(struct timeval *last, struct timeval *tout, struct timeval *now) { if (now->tv_sec > last->tv_sec + tout->tv_sec) return 1; if (now->tv_sec == last->tv_sec + tout->tv_sec) { if (now->tv_usec > last->tv_usec + tout->tv_usec) return 1; } return 0; } int lnstat_update(struct lnstat_file *lnstat_files, void (*print_cb)(struct lnstat_file *, void *), void *v) { struct lnstat_file *lf; char buf[FGETS_BUF_SIZE]; struct timeval tv; /* FIXME: for more precision we need to move this into the loop */ gettimeofday(&tv, NULL); for (lf = lnstat_files; lf; lf = lf->next) { if (time_after(&lf->last_read, &lf->interval, &tv)) { rewind(lf->fp); fgets(buf, FGETS_BUF_SIZE-1, lf->fp); scan_lines(lf, 1); (print_cb)(lf, v); rewind(lf->fp); fgets(buf, FGETS_BUF_SIZE-1, lf->fp); scan_lines(lf, 0); } } return 0; } /* scan first template line and fill in per-field data structures */ static int lnstat_scan_fields(struct lnstat_file *lf) { char buf[FGETS_BUF_SIZE]; char *tok; int i; rewind(lf->fp); fgets(buf, FGETS_BUF_SIZE-1, lf->fp); tok = strtok(buf, " \t\n"); for (i = 0; i < LNSTAT_MAX_FIELDS_PER_LINE; i++) { lf->fields[i].file = lf; strncpy(lf->fields[i].name, tok, LNSTAT_MAX_FIELD_NAME_LEN); /* has to be null-terminate since we initialize to zero * and field size is NAME_LEN + 1 */ tok = strtok(NULL, " \t\n"); if (!tok) { lf->num_fields = i+1; return 0; } } return 0; } /* find out whether string 'name; is in given string array */ static int name_in_array(const int num, const char **arr, const char *name) { int i; for (i = 0; i < num; i++) { if (!strcmp(arr[i], name)) return 1; } return 0; } /* allocate lnstat_file and open given file */ static struct lnstat_file *alloc_and_open(const char *path, const char *file) { struct lnstat_file *lf; /* allocate */ lf = malloc(sizeof(*lf)); if (!lf) return NULL; /* initialize */ memset(lf, 0, sizeof(*lf)); /* de->d_name is guaranteed to be <= NAME_MAX */ strcpy(lf->basename, file); strcpy(lf->path, path); strcat(lf->path, "/"); strcat(lf->path, lf->basename); /* open */ lf->fp = fopen(lf->path, "r"); if (!lf->fp) { free(lf); return NULL; } return lf; } /* lnstat_scan_dir - find and parse all available statistics files/fields */ struct lnstat_file *lnstat_scan_dir(const char *path, const int num_req_files, const char **req_files) { DIR *dir; struct lnstat_file *lnstat_files = NULL; struct dirent *de; if (!path) path = PROC_NET_STAT; dir = opendir(path); if (!dir) return NULL; while (de = readdir(dir)) { struct lnstat_file *lf; if (de->d_type != DT_REG) continue; if (num_req_files && !name_in_array(num_req_files, req_files, de->d_name)) continue; lf = alloc_and_open(path, de->d_name); if (!lf) return NULL; /* fill in field structure */ if (lnstat_scan_fields(lf) < 0) return NULL; /* prepend to global list */ lf->next = lnstat_files; lnstat_files = lf; } closedir(dir); return lnstat_files; } int lnstat_dump(FILE *outfd, struct lnstat_file *lnstat_files) { struct lnstat_file *lf; for (lf = lnstat_files; lf; lf = lf->next) { int i; fprintf(outfd, "%s:\n", lf->path); for (i = 0; i < lf->num_fields; i++) fprintf(outfd, "\t%2u: %s\n", i+1, lf->fields[i].name); } return 0; } static struct option opts[] = { { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'h' }, { "interval", 1, NULL, 'i' }, { "subject", 1, NULL, 's' }, { "file", 1, NULL, 'f' }, { "key", 1, NULL, 'k' }, }; static int usage(char *name, int exit_code) { fprintf(stderr, "%s Version %s\n", name, LNSTAT_VERSION); fprintf(stderr, "Copyright (C) 2004 by Harald Welte " "\n"); fprintf(stderr, "This program is free software with ABSOLUTELY NO " "WARRANTY.\n\n"); fprintf(stderr, "Parameters:\n"); fprintf(stderr, "\t-h --help\t\tThis help message\n"); fprintf(stderr, "\t-i --interval\t\tSet interval\n"); fprintf(stderr, "\t-s --subject [0-2]\t?\n"); fprintf(stderr, "\t-f --file\t\tStatistics file to use\n"); fprintf(stderr, "\n"); exit(exit_code); } static void print_cb(struct lnstat_file *lf, void *v) { int i; int *interval = v; for (i = 0; i < lf->num_fields; i++) { unsigned long val = (lf->fields[i].values[1] -lf->fields[i].values[0])/(*interval); fprintf(stdout, "%5lu ", val); } fputc('\n', stdout); } int main(int argc, char **argv) { char *basename; int c, i = 1, interval = 2, hdr = 2; struct lnstat_file *lnstat_files; int num_req_files = 0, num_req_keys = 0; char *req_files[LNSTAT_MAX_FILES]; char *req_keys[LNSTAT_MAX_FILES*LNSTAT_MAX_FIELDS_PER_LINE]; /* backwards compatibility mode for old tools */ basename = strrchr(argv[0], '/') + 1; if (!strcmp(basename, "rtstat")) { /* rtstat compatibility mode */ req_files[0] = "rt_cache"; num_req_files = 1; } else if (!strcmp(basename, "ctstat")) { /* ctstat compatibility mode */ req_files[0] = "ip_conntrack"; num_req_files = 1; } while ((c = getopt_long(argc, argv,"Vh?s:i:f:k:", opts, NULL)) != -1) switch (c) { case '?': case 'h': usage(argv[0], 0); case 'i': sscanf(optarg, "%u", &interval); break; case 's': sscanf(optarg, "%u", &hdr); break; case 'f': req_files[num_req_files++] = strdup(optarg); break; case 'k': req_keys[num_req_keys++] = strdup(optarg); break; default: usage(argv[0], 1); } if (interval < 1 ) interval=1; lnstat_files = lnstat_scan_dir(PROC_NET_STAT, num_req_files, req_files); lnstat_dump(stderr, lnstat_files); for (; 1; i++) { #if 0 if (hdr > 1 && (! (i % 20))) print_hdr_line(); #endif lnstat_update(lnstat_files, &print_cb, &interval); sleep(interval); } return 1; }