From 056eb9d8dd2329c20394d6b2dd81c151dfeac164 Mon Sep 17 00:00:00 2001 From: "osdl.net!shemminger" Date: Tue, 19 Oct 2004 20:21:14 +0000 Subject: [PATCH] (Logical change 1.98) --- misc/README.lnstat | 81 +++++++++++ misc/lnstat.c | 336 +++++++++++++++++++++++++++++++++++++++++++++ misc/lnstat.h | 43 ++++++ misc/lnstat_util.c | 323 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 783 insertions(+) create mode 100644 misc/README.lnstat create mode 100644 misc/lnstat.c create mode 100644 misc/lnstat.h create mode 100644 misc/lnstat_util.c diff --git a/misc/README.lnstat b/misc/README.lnstat new file mode 100644 index 00000000..057925f6 --- /dev/null +++ b/misc/README.lnstat @@ -0,0 +1,81 @@ +lnstat - linux networking statistics +(C) 2004 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. + * + */ + +/* Maximum number of fields that can be displayed */ +#define MAX_FIELDS 64 + +/* Maximum number of header lines */ +#define HDR_LINES 10 + +/* default field width if none specified */ +#define FIELD_WIDTH_DEFAULT 8 +#define FIELD_WIDTH_MAX 20 + +#define DEFAULT_INTERVAL 2 + +#define HDR_LINE_LENGTH (MAX_FIELDS*FIELD_WIDTH_MAX) + +#include +#include +#include +#include +#include + +#include "lnstat.h" + +static struct option opts[] = { + { "version", 0, NULL, 'V' }, + { "count", 1, NULL, 'c' }, + { "dump", 1, NULL, 'd' }, + { "file", 1, NULL, 'f' }, + { "help", 0, NULL, 'h' }, + { "interval", 1, NULL, 'i' }, + { "key", 1, NULL, 'k' }, + { "subject", 1, NULL, 's' }, + { "width", 1, NULL, 'w' }, +}; + +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 licensed under GNU GPLv2" + "\nwith ABSOLUTELY NO WARRANTY.\n\n"); + fprintf(stderr, "Parameters:\n"); + fprintf(stderr, "\t-V --version\t\tPrint Version of Program\n"); + fprintf(stderr, "\t-c --count \t" + "Print number of intervals\n"); + fprintf(stderr, "\t-d --dumpt\t\t" + "Dump list of available files/keys\n"); + fprintf(stderr, "\t-f --file \tStatistics file to use\n"); + fprintf(stderr, "\t-h --help\t\tThis help message\n"); + fprintf(stderr, "\t-i --interval \t" + "Set interval to 'intv' seconds\n"); + fprintf(stderr, "\t-k --keys k,k,k,...\tDisplay only keys specified\n"); + fprintf(stderr, "\t-s --subject [0-2]\t?\n"); + fprintf(stderr, "\t-w --width n,n,n,...\tWidth for each field\n"); + fprintf(stderr, "\n"); + + exit(exit_code); +} + +struct field_param { + char *name; + struct lnstat_field *lf; + struct { + unsigned int width; + } print; +}; + +struct field_params { + unsigned int num; + struct field_param params[MAX_FIELDS]; +}; + +static void print_line(FILE *of, struct lnstat_file *lnstat_files, + struct field_params *fp) +{ + int i; + + for (i = 0; i < fp->num; i++) { + struct lnstat_field *lf = fp->params[i].lf; + char formatbuf[255]; + + snprintf(formatbuf, sizeof(formatbuf)-1, "%%%ulu|", + fp->params[i].print.width); + fprintf(of, formatbuf, lf->result); + } + fputc('\n', of); +} + +/* find lnstat_field according to user specification */ +static int map_field_params(struct lnstat_file *lnstat_files, + struct field_params *fps, int interval) +{ + int i, j = 0; + struct lnstat_file *lf; + + /* no field specification on commandline, need to build default */ + if (!fps->num) { + for (lf = lnstat_files; lf; lf = lf->next) { + for (i = 0; i < lf->num_fields; i++) { + fps->params[j].lf = &lf->fields[i]; + fps->params[j].lf->file->interval.tv_sec = + interval; + if (!fps->params[j].print.width) + fps->params[j].print.width = + FIELD_WIDTH_DEFAULT; + j++; + } + } + fps->num = j; + return 1; + } + + for (i = 0; i < fps->num; i++) { + fps->params[i].lf = lnstat_find_field(lnstat_files, + fps->params[i].name); + if (!fps->params[i].lf) { + fprintf(stderr, "Field `%s' unknown\n", + fps->params[i].name); + return 0; + } + fps->params[i].lf->file->interval.tv_sec = interval; + if (!fps->params[i].print.width) + fps->params[i].print.width = FIELD_WIDTH_DEFAULT; + } + return 1; +} + +struct table_hdr { + int num_lines; + char *hdr[HDR_LINES]; +}; + +static struct table_hdr *build_hdr_string(struct lnstat_file *lnstat_files, + struct field_params *fps, + int linewidth) +{ + int h,i; + static struct table_hdr th; + int ofs = 0; + + for (i = 0; i < HDR_LINES; i++) { + th.hdr[i] = malloc(HDR_LINE_LENGTH); + memset(th.hdr[i], 0, sizeof(th.hdr[i])); + } + + for (i = 0; i < fps->num; i++) { + char *cname, *fname = fps->params[i].lf->name; + char fmt[12]; + unsigned int width = fps->params[i].print.width; + + snprintf(fmt, sizeof(fmt)-1, "%%%u.%us|", width, width); + + snprintf(th.hdr[0]+ofs, width+2, fmt, + fps->params[i].lf->file->basename); + + cname = fname; + for (h = 1; h < HDR_LINES; h++) { + if (cname - fname >= strlen(fname)) + snprintf(th.hdr[h]+ofs, width+2, fmt, ""); + else { + th.num_lines = h+1; + snprintf(th.hdr[h]+ofs, width+2, fmt, cname); + } + cname += width; + } + ofs += width+1; + } + /* fill in spaces */ + for (h = 1; h <= th.num_lines; h++) { + for (i = 0; i < ofs; i++) { + if (th.hdr[h][i] == '\0') + th.hdr[h][i] = ' '; + } + } + + return &th; +} + +static int print_hdr(FILE *of, struct table_hdr *th) +{ + int i; + + for (i = 0; i < th->num_lines; i++) { + fputs(th->hdr[i], of); + fputc('\n', of); + } + return 0; +} + + +int main(int argc, char **argv) +{ + struct lnstat_file *lnstat_files; + char *basename; + int c; + + int interval = DEFAULT_INTERVAL; + int hdr = 2; + + enum { + MODE_DUMP, + MODE_NORMAL, + } mode; + unsigned long count = 0; + struct field_params fp; + int num_req_files = 0; + char *req_files[LNSTAT_MAX_FILES]; + + memset(&fp, 0, sizeof(fp)); + + mode = MODE_NORMAL; + + /* 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,"Vc:df:h?i:k:s:w:", + opts, NULL)) != -1) { + switch (c) { + int i, len; + char *tmp, *tok; + case 'c': + count = strtoul(optarg, NULL, 0); + break; + case 'd': + mode = MODE_DUMP; + break; + case 'f': + req_files[num_req_files++] = strdup(optarg); + break; + case '?': + case 'h': + usage(argv[0], 0); + break; + case 'i': + sscanf(optarg, "%u", &interval); + break; + case 'k': + tmp = strdup(optarg); + if (!tmp) + break; + for (tok = strtok(tmp, ","); + tok; + tok = strtok(NULL, ",")) { + if (fp.num >= MAX_FIELDS) + break; + fp.params[fp.num++].name = tok; + } + break; + case 's': + sscanf(optarg, "%u", &hdr); + break; + case 'w': + tmp = strdup(optarg); + if (!tmp) + break; + i = 0; + for (tok = strtok(tmp, ","); + tok; + tok = strtok(NULL, ",")) { + len = strtoul(tok, NULL, 0); + if (len > FIELD_WIDTH_MAX) + len = FIELD_WIDTH_MAX; + fp.params[i].print.width = len; + i++; + } + if (i == 1) { + for (i = 0; i < MAX_FIELDS; i++) + fp.params[i].print.width = len; + } + break; + default: + usage(argv[0], 1); + break; + } + } + + lnstat_files = lnstat_scan_dir(PROC_NET_STAT, num_req_files, + (const char **) req_files); + + switch (mode) { + int i; + struct table_hdr *header; + case MODE_DUMP: + lnstat_dump(stderr, lnstat_files); + break; + case MODE_NORMAL: + + if (!map_field_params(lnstat_files, &fp, interval)) + exit(1); + + header = build_hdr_string(lnstat_files, &fp, 80); + if (!header) + exit(1); + + if (interval < 1 ) + interval=1; + + for (i = 0; i < count; i++) { + if ((hdr > 1 && (! (i % 20))) || (hdr == 1 && i == 0)) + print_hdr(stdout, header); + lnstat_update(lnstat_files); + print_line(stdout, lnstat_files, &fp); + sleep(interval); + } + } + + return 1; +} + diff --git a/misc/lnstat.h b/misc/lnstat.h new file mode 100644 index 00000000..8bd7ede9 --- /dev/null +++ b/misc/lnstat.h @@ -0,0 +1,43 @@ +#ifndef _LNSTAT_H +#define _LNSTAT_H + +#include + +#define LNSTAT_VERSION "0.02 041002" + +#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 */ + unsigned long result; +}; + +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 */ + int compat; /* 1 == backwards compat mode */ + FILE *fp; + unsigned int num_fields; /* number of fields */ + struct lnstat_field fields[LNSTAT_MAX_FIELDS_PER_LINE]; +}; + + +struct lnstat_file *lnstat_scan_dir(const char *path, const int num_req_files, + const char **req_files); +int lnstat_update(struct lnstat_file *lnstat_files); +int lnstat_dump(FILE *outfd, struct lnstat_file *lnstat_files); +struct lnstat_field *lnstat_find_field(struct lnstat_file *lnstat_files, + char *name); +#endif /* _LNSTAT_H */ diff --git a/misc/lnstat_util.c b/misc/lnstat_util.c new file mode 100644 index 00000000..c694b289 --- /dev/null +++ b/misc/lnstat_util.c @@ -0,0 +1,323 @@ +/* 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 "lnstat.h" + +/* size of temp buffer used to read lines from procfiles */ +#define FGETS_BUF_SIZE 1024 + + +#define RTSTAT_COMPAT_LINE "entries in_hit in_slow_tot in_no_route in_brd in_martian_dst in_martian_src out_hit out_slow_tot out_slow_mc gc_total gc_ignored gc_goal_miss gc_dst_overflow in_hlist_search out_hlist_search\n" + +/* 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)) { + char buf[FGETS_BUF_SIZE]; + char *ptr = buf; + + num_lines++; + + fgets(buf, sizeof(buf)-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) +{ + struct lnstat_file *lf; + char buf[FGETS_BUF_SIZE]; + struct timeval tv; + + gettimeofday(&tv, NULL); + + for (lf = lnstat_files; lf; lf = lf->next) { + if (time_after(&lf->last_read, &lf->interval, &tv)) { + int i; + struct lnstat_field *lfi; + + rewind(lf->fp); + if (!lf->compat) { + /* skip first line */ + fgets(buf, sizeof(buf)-1, lf->fp); + } + scan_lines(lf, 1); + + for (i = 0, lfi = &lf->fields[i]; + i < lf->num_fields; i++, lfi = &lf->fields[i]) { + if (i == 0) + lfi->result = lfi->values[1]; + else + lfi->result = (lfi->values[1]-lfi->values[0]) + / lf->interval.tv_sec; + } + + rewind(lf->fp); + fgets(buf, sizeof(buf)-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) +{ + char *tok; + int i; + + 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; +} + +static int lnstat_scan_fields(struct lnstat_file *lf) +{ + char buf[FGETS_BUF_SIZE]; + + rewind(lf->fp); + fgets(buf, sizeof(buf)-1, lf->fp); + + return __lnstat_scan_fields(lf, buf); +} + +/* fake function emulating lnstat_scan_fields() for old kernels */ +static int lnstat_scan_compat_rtstat_fields(struct lnstat_file *lf) +{ + char buf[FGETS_BUF_SIZE]; + + strncpy(buf, RTSTAT_COMPAT_LINE, sizeof(buf)-1); + + return __lnstat_scan_fields(lf, buf); +} + +/* 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); + + /* initialize to default */ + lf->interval.tv_sec = 1; + + /* 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) { + struct lnstat_file *lf; + /* Old kernel, before /proc/net/stat was introduced */ + fprintf(stderr, "Your kernel doesn't have lnstat support. "); + + /* we only support rtstat, not multiple files */ + if (num_req_files >= 2) { + fputc('\n', stderr); + return NULL; + } + + /* we really only accept rt_cache */ + if (num_req_files && !name_in_array(num_req_files, + req_files, "rt_cache")) { + fputc('\n', stderr); + return NULL; + } + + fprintf(stderr, "Fallback to old rtstat-only operation\n"); + + lf = alloc_and_open("/proc/net", "rt_cache_stat"); + if (!lf) + return NULL; + lf->compat = 1; + strncpy(lf->basename, "rt_cache", sizeof(lf->basename)); + + /* FIXME: support for old files */ + if (lnstat_scan_compat_rtstat_fields(lf) < 0) + return NULL; + + lf->next = lnstat_files; + lnstat_files = lf; + return lnstat_files; + } + + 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; +} + +struct lnstat_field *lnstat_find_field(struct lnstat_file *lnstat_files, + char *name) +{ + struct lnstat_file *lf; + struct lnstat_field *ret = NULL; + char *colon = strchr(name, ':'); + char *file, *field; + + if (colon) { + file = strndup(name, colon-name); + field = colon+1; + } else { + file = NULL; + field = name; + } + + for (lf = lnstat_files; lf; lf = lf->next) { + int i; + + if (file && strcmp(file, lf->basename)) + continue; + + for (i = 0; i < lf->num_fields; i++) { + if (!strcmp(field, lf->fields[i].name)) { + ret = &lf->fields[i]; + goto out; + } + } + } +out: + if (file) + free(file); + + return ret; +} -- 2.39.5