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
;
124 char idbuf
[sizeof(buf
)];
126 buf
[strlen(buf
)-1] = 0;
127 if (info_source
[0] && strcmp(info_source
, buf
+1))
130 strncat(info_source
, buf
+1, sizeof(info_source
)-1);
133 /* idbuf is as big as buf, so this is safe */
134 nr
= sscanf(buf
, "%s%llu%lg", idbuf
, &val
, &rate
);
139 if (useless_number(idbuf
))
141 if ((n
= malloc(sizeof(*n
))) == NULL
)
143 n
->id
= strdup(idbuf
);
144 n
->ival
= (unsigned long)val
;
160 void load_ugly_table(FILE *fp
)
163 struct nstat_ent
*db
= NULL
;
166 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
167 char idbuf
[sizeof(buf
)];
171 p
= strchr(buf
, ':');
176 strncat(idbuf
, buf
, sizeof(idbuf
) - 1);
182 if ((next
= strchr(p
, ' ')) != NULL
)
184 else if ((next
= strchr(p
, '\n')) != NULL
)
186 if (off
< sizeof(idbuf
)) {
188 strncat(idbuf
, p
, sizeof(idbuf
) - off
- 1);
190 n
= malloc(sizeof(*n
));
193 n
->id
= strdup(idbuf
);
200 if (fgets(buf
, sizeof(buf
), fp
) == NULL
)
203 p
= strrchr(buf
, ' ');
207 if (sscanf(p
+1, "%lu", &n
->ival
) != 1)
210 /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
211 if (strcmp(idbuf
, "IcmpOutAddrMaskReps") == 0)
215 } while (p
> buf
+ off
+ 2);
221 if (useless_number(n
->id
)) {
233 FILE *fp
= fdopen(net_snmp_open(), "r");
240 void load_snmp6(void)
242 FILE *fp
= fdopen(net_snmp6_open(), "r");
249 void load_netstat(void)
251 FILE *fp
= fdopen(net_netstat_open(), "r");
258 void dump_kern_db(FILE *fp
, int to_hist
)
260 struct nstat_ent
*n
, *h
;
262 fprintf(fp
, "#%s\n", info_source
);
263 for (n
=kern_db
; n
; n
=n
->next
) {
264 unsigned long long val
= n
->val
;
265 if (!dump_zeros
&& !val
&& !n
->rate
)
268 struct nstat_ent
*h1
;
271 for (h1
= h
; h1
; h1
= h1
->next
) {
272 if (strcmp(h1
->id
, n
->id
) == 0) {
279 fprintf(fp
, "%-32s%-16llu%6.1f\n", n
->id
, val
, n
->rate
);
283 void dump_incr_db(FILE *fp
)
285 struct nstat_ent
*n
, *h
;
287 fprintf(fp
, "#%s\n", info_source
);
288 for (n
=kern_db
; n
; n
=n
->next
) {
290 unsigned long long val
= n
->val
;
291 struct nstat_ent
*h1
;
292 for (h1
= h
; h1
; h1
= h1
->next
) {
293 if (strcmp(h1
->id
, n
->id
) == 0) {
303 if (!dump_zeros
&& !val
&& !n
->rate
)
307 fprintf(fp
, "%-32s%-16llu%6.1f%s\n", n
->id
, val
,
308 n
->rate
, ovfl
?" (overflow)":"");
314 void sigchild(int signo
)
318 void update_db(int interval
)
320 struct nstat_ent
*n
, *h
;
332 for (n
= kern_db
; n
; n
= n
->next
) {
333 struct nstat_ent
*h1
;
334 for (h1
= h
; h1
; h1
= h1
->next
) {
335 if (strcmp(h1
->id
, n
->id
) == 0) {
337 unsigned long incr
= h1
->ival
- n
->ival
;
340 sample
= (double)(incr
*1000)/interval
;
341 if (interval
>= scan_interval
) {
342 n
->rate
+= W
*(sample
-n
->rate
);
343 } else if (interval
>= 1000) {
344 if (interval
>= time_constant
) {
347 double w
= W
*(double)interval
/scan_interval
;
348 n
->rate
+= w
*(sample
-n
->rate
);
353 struct nstat_ent
*tmp
= h
;
367 #define T_DIFF(a,b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
370 void server_loop(int fd
)
372 struct timeval snaptime
= { 0 };
375 p
.events
= p
.revents
= POLLIN
;
377 sprintf(info_source
, "%d.%lu sampling_interval=%d time_const=%d",
378 getpid(), (unsigned long)random(), scan_interval
/1000, time_constant
/1000);
388 gettimeofday(&now
, NULL
);
389 tdiff
= T_DIFF(now
, snaptime
);
390 if (tdiff
>= scan_interval
) {
395 if (poll(&p
, 1, tdiff
+ scan_interval
) > 0
396 && (p
.revents
&POLLIN
)) {
397 int clnt
= accept(fd
, NULL
, NULL
);
402 } else if ((pid
= fork()) != 0) {
407 FILE *fp
= fdopen(clnt
, "w");
417 while (children
&& waitpid(-1, &status
, WNOHANG
) > 0)
422 int verify_forging(int fd
)
425 socklen_t olen
= sizeof(cred
);
427 if (getsockopt(fd
, SOL_SOCKET
, SO_PEERCRED
, (void*)&cred
, &olen
) ||
430 if (cred
.uid
== getuid() || cred
.uid
== 0)
435 static void usage(void) __attribute__((noreturn
));
437 static void usage(void)
440 "Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ]\n"
446 int main(int argc
, char *argv
[])
449 struct sockaddr_un sun
;
450 FILE *hist_fp
= NULL
;
454 while ((ch
= getopt(argc
, argv
, "h?vVzrnasd:t:")) != EOF
) {
472 scan_interval
= 1000*atoi(optarg
);
475 if (sscanf(optarg
, "%d", &time_constant
) != 1 ||
476 time_constant
<= 0) {
477 fprintf(stderr
, "nstat: invalid time constant divisor\n");
483 printf("nstat utility, iproute2-ss%s\n", SNAPSHOT
);
495 sun
.sun_family
= AF_UNIX
;
497 sprintf(sun
.sun_path
+1, "nstat%d", getuid());
499 if (scan_interval
> 0) {
500 if (time_constant
== 0)
502 time_constant
*= 1000;
503 W
= 1 - 1/exp(log(10)*(double)scan_interval
/time_constant
);
504 if ((fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) < 0) {
505 perror("nstat: socket");
508 if (bind(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) < 0) {
509 perror("nstat: bind");
512 if (listen(fd
, 5) < 0) {
513 perror("nstat: listen");
519 close(0); close(1); close(2); setsid();
520 signal(SIGPIPE
, SIG_IGN
);
521 signal(SIGCHLD
, sigchild
);
529 if (getenv("NSTAT_HISTORY"))
530 snprintf(hist_name
, sizeof(hist_name
), getenv("NSTAT_HISTORY"));
532 sprintf(hist_name
, "/tmp/.nstat.u%d", getuid());
537 if (!ignore_history
|| !no_update
) {
540 fd
= open(hist_name
, O_RDWR
|O_CREAT
|O_NOFOLLOW
, 0600);
542 perror("nstat: open history file");
545 if ((hist_fp
= fdopen(fd
, "r+")) == NULL
) {
546 perror("nstat: fdopen history file");
549 if (flock(fileno(hist_fp
), LOCK_EX
)) {
550 perror("nstat: flock history file");
553 if (fstat(fileno(hist_fp
), &stb
) != 0) {
554 perror("nstat: fstat history file");
557 if (stb
.st_nlink
!= 1 || stb
.st_uid
!= getuid()) {
558 fprintf(stderr
, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
561 if (!ignore_history
) {
564 if ((tfp
= fopen("/proc/uptime", "r")) != NULL
) {
565 if (fscanf(tfp
, "%ld", &uptime
) != 1)
569 if (uptime
>= 0 && time(NULL
) >= stb
.st_mtime
+uptime
) {
570 fprintf(stderr
, "nstat: history is aged out, resetting\n");
571 ftruncate(fileno(hist_fp
), 0);
575 load_good_table(hist_fp
);
581 if ((fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) >= 0 &&
582 (connect(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) == 0
583 || (strcpy(sun
.sun_path
+1, "nstat0"),
584 connect(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) == 0))
585 && verify_forging(fd
) == 0) {
586 FILE *sfp
= fdopen(fd
, "r");
587 load_good_table(sfp
);
588 if (hist_db
&& source_mismatch
) {
589 fprintf(stderr
, "nstat: history is stale, ignoring it.\n");
596 if (hist_db
&& info_source
[0] && strcmp(info_source
, "kernel")) {
597 fprintf(stderr
, "nstat: history is stale, ignoring it.\n");
604 if (info_source
[0] == 0)
605 strcpy(info_source
, "kernel");
609 if (ignore_history
|| hist_db
== NULL
)
610 dump_kern_db(stdout
, 0);
612 dump_incr_db(stdout
);
615 ftruncate(fileno(hist_fp
), 0);
617 dump_kern_db(hist_fp
, 1);