2 * nstat.c handy utility to read counters /proc/net/netstat and snmp
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version
7 * 2 of the License, or (at your option) any later version.
9 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
22 #include <sys/socket.h>
33 int reset_history = 0;
34 int ignore_history = 0;
37 int scan_interval = 0;
38 int time_constant = 0;
43 char info_source[128];
46 int generic_proc_open(char *env, char *name)
49 char *p = getenv(env);
51 p = getenv("PROC_ROOT") ? : "/proc";
52 snprintf(store, sizeof(store)-1, "%s/%s", p, name);
55 return open(store, O_RDONLY);
58 int net_netstat_open(void)
60 return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
63 int net_snmp_open(void)
65 return generic_proc_open("PROC_NET_SNMP", "net/snmp");
68 int net_snmp6_open(void)
70 return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
75 struct nstat_ent *next;
77 unsigned long long val;
82 struct nstat_ent *kern_db;
83 struct nstat_ent *hist_db;
85 char *useless_numbers[] = {
86 "IpForwarding", "IpDefaultTTL",
87 "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
88 "TcpMaxConn", "TcpCurrEstab"
91 int useless_number(char *id)
94 for (i=0; i<sizeof(useless_numbers)/sizeof(*useless_numbers); i++)
95 if (strcmp(id, useless_numbers[i]) == 0)
107 for (i=0; i<npatterns; i++) {
108 if (!fnmatch(patterns[i], id, 0))
114 void load_good_table(FILE *fp)
117 struct nstat_ent *db = NULL;
120 while (fgets(buf, sizeof(buf), fp) != NULL) {
122 unsigned long long val;
126 buf[strlen(buf)-1] = 0;
127 if (info_source[0] && strcmp(info_source, buf+1))
129 strncpy(info_source, buf+1, sizeof(info_source)-1);
132 nr = sscanf(buf, "%s%llu%lg", idbuf, &val, &rate);
137 if (useless_number(idbuf))
139 if ((n = malloc(sizeof(*n))) == NULL)
141 n->id = strdup(idbuf);
142 n->ival = (unsigned long)val;
158 void load_ugly_table(FILE *fp)
161 struct nstat_ent *db = NULL;
164 while (fgets(buf, sizeof(buf), fp) != NULL) {
169 p = strchr(buf, ':');
179 if ((next = strchr(p, ' ')) != NULL)
181 else if ((next = strchr(p, '\n')) != NULL)
183 strcpy(idbuf+off, p);
184 n = malloc(sizeof(*n));
187 n->id = strdup(idbuf);
194 if (fgets(buf, sizeof(buf), fp) == NULL)
197 p = strrchr(buf, ' ');
201 if (sscanf(p+1, "%lu", &n->ival) != 1)
204 /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
205 if (strcmp(idbuf, "IcmpOutAddrMaskReps") == 0)
209 } while (p > buf + off + 2);
215 if (useless_number(n->id)) {
227 FILE *fp = fdopen(net_snmp_open(), "r");
234 void load_snmp6(void)
236 FILE *fp = fdopen(net_snmp6_open(), "r");
243 void load_netstat(void)
245 FILE *fp = fdopen(net_netstat_open(), "r");
252 void dump_kern_db(FILE *fp, int to_hist)
254 struct nstat_ent *n, *h;
256 fprintf(fp, "#%s\n", info_source);
257 for (n=kern_db; n; n=n->next) {
258 unsigned long long val = n->val;
259 if (!dump_zeros && !val && !n->rate)
262 struct nstat_ent *h1;
265 for (h1 = h; h1; h1 = h1->next) {
266 if (strcmp(h1->id, n->id) == 0) {
273 fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
277 void dump_incr_db(FILE *fp)
279 struct nstat_ent *n, *h;
281 fprintf(fp, "#%s\n", info_source);
282 for (n=kern_db; n; n=n->next) {
284 unsigned long long val = n->val;
285 struct nstat_ent *h1;
286 for (h1 = h; h1; h1 = h1->next) {
287 if (strcmp(h1->id, n->id) == 0) {
297 if (!dump_zeros && !val && !n->rate)
301 fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
302 n->rate, ovfl?" (overflow)":"");
308 void sigchild(int signo)
312 void update_db(int interval)
314 struct nstat_ent *n, *h;
326 for (n = kern_db; n; n = n->next) {
327 struct nstat_ent *h1;
328 for (h1 = h; h1; h1 = h1->next) {
329 if (strcmp(h1->id, n->id) == 0) {
331 unsigned long incr = h1->ival - n->ival;
334 sample = (double)(incr*1000)/interval;
335 if (interval >= scan_interval) {
336 n->rate += W*(sample-n->rate);
337 } else if (interval >= 1000) {
338 if (interval >= time_constant) {
341 double w = W*(double)interval/scan_interval;
342 n->rate += w*(sample-n->rate);
347 struct nstat_ent *tmp = h;
361 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
364 void server_loop(int fd)
366 struct timeval snaptime;
369 p.events = p.revents = POLLIN;
371 sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
372 getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
382 gettimeofday(&now, NULL);
383 tdiff = T_DIFF(now, snaptime);
384 if (tdiff >= scan_interval) {
389 if (poll(&p, 1, tdiff + scan_interval) > 0
390 && (p.revents&POLLIN)) {
391 int clnt = accept(fd, NULL, NULL);
396 } else if ((pid = fork()) != 0) {
401 FILE *fp = fdopen(clnt, "w");
411 while (children && waitpid(-1, &status, WNOHANG) > 0)
416 int verify_forging(int fd)
419 int olen = sizeof(cred);
420 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void*)&cred, &olen) ||
423 if (cred.uid == getuid() || cred.uid == 0)
428 static void usage(void) __attribute__((noreturn));
430 static void usage(void)
433 "Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ]\n"
439 int main(int argc, char *argv[])
442 struct sockaddr_un sun;
443 FILE *hist_fp = NULL;
447 while ((ch = getopt(argc, argv, "h?vVzrnasd:t:")) != EOF) {
465 scan_interval = 1000*atoi(optarg);
468 if (sscanf(optarg, "%d", &time_constant) != 1 ||
469 time_constant <= 0) {
470 fprintf(stderr, "nstat: invalid time constant divisor\n");
476 printf("nstat utility, iproute2-ss%s\n", SNAPSHOT);
488 sun.sun_family = AF_UNIX;
490 sprintf(sun.sun_path+1, "nstat%d", getuid());
492 if (scan_interval > 0) {
493 if (time_constant == 0)
495 time_constant *= 1000;
496 W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
497 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
498 perror("nstat: socket");
501 if (bind(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
502 perror("nstat: bind");
505 if (listen(fd, 5) < 0) {
506 perror("nstat: listen");
512 close(0); close(1); close(2); setsid();
513 signal(SIGPIPE, SIG_IGN);
514 signal(SIGCHLD, sigchild);
522 if (getenv("NSTAT_HISTORY"))
523 snprintf(hist_name, sizeof(hist_name), getenv("NSTAT_HISTORY"));
525 sprintf(hist_name, "/tmp/.nstat.u%d", getuid());
530 if (!ignore_history || !no_update) {
533 fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
535 perror("nstat: open history file");
538 if ((hist_fp = fdopen(fd, "r+")) == NULL) {
539 perror("nstat: fdopen history file");
542 if (flock(fileno(hist_fp), LOCK_EX)) {
543 perror("nstat: flock history file");
546 if (fstat(fileno(hist_fp), &stb) != 0) {
547 perror("nstat: fstat history file");
550 if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
551 fprintf(stderr, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
554 if (!ignore_history) {
557 if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
558 if (fscanf(tfp, "%ld", &uptime) != 1)
562 if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
563 fprintf(stderr, "nstat: history is aged out, resetting\n");
564 ftruncate(fileno(hist_fp), 0);
568 load_good_table(hist_fp);
574 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
575 (connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0
576 || (strcpy(sun.sun_path+1, "nstat0"),
577 connect(fd, (struct sockaddr*)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
578 && verify_forging(fd) == 0) {
579 FILE *sfp = fdopen(fd, "r");
580 load_good_table(sfp);
581 if (hist_db && source_mismatch) {
582 fprintf(stderr, "nstat: history is stale, ignoring it.\n");
589 if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
590 fprintf(stderr, "nstat: history is stale, ignoring it.\n");
597 if (info_source[0] == 0)
598 strcpy(info_source, "kernel");
602 if (ignore_history || hist_db == NULL)
603 dump_kern_db(stdout, 0);
605 dump_incr_db(stdout);
608 ftruncate(fileno(hist_fp), 0);
610 dump_kern_db(hist_fp, 1);