]>
git.proxmox.com Git - mirror_iproute2.git/blob - misc/nstat.c
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>
31 #include <json_writer.h>
47 char info_source
[128];
50 static int generic_proc_open(const char *env
, char *name
)
53 char *p
= getenv(env
);
56 p
= getenv("PROC_ROOT") ? : "/proc";
57 snprintf(store
, sizeof(store
)-1, "%s/%s", p
, name
);
60 return open(p
, O_RDONLY
);
63 static int net_netstat_open(void)
65 return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
68 static int net_snmp_open(void)
70 return generic_proc_open("PROC_NET_SNMP", "net/snmp");
73 static int net_snmp6_open(void)
75 return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
78 static int net_sctp_snmp_open(void)
80 return generic_proc_open("PROC_NET_SCTP_SNMP", "net/sctp/snmp");
84 struct nstat_ent
*next
;
86 unsigned long long val
;
90 struct nstat_ent
*kern_db
;
91 struct nstat_ent
*hist_db
;
93 static const char *useless_numbers
[] = {
94 "IpForwarding", "IpDefaultTTL",
95 "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
96 "TcpMaxConn", "TcpCurrEstab"
99 static int useless_number(const char *id
)
103 for (i
= 0; i
< ARRAY_SIZE(useless_numbers
); i
++)
104 if (strcmp(id
, useless_numbers
[i
]) == 0)
109 static int match(const char *id
)
116 for (i
= 0; i
< npatterns
; i
++) {
117 if (!fnmatch(patterns
[i
], id
, FNM_CASEFOLD
))
123 static void load_good_table(FILE *fp
)
126 struct nstat_ent
*db
= NULL
;
129 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
131 unsigned long long val
;
133 char idbuf
[sizeof(buf
)];
136 buf
[strlen(buf
)-1] = 0;
137 if (info_source
[0] && strcmp(info_source
, buf
+1))
139 strlcpy(info_source
, buf
+ 1, sizeof(info_source
));
142 /* idbuf is as big as buf, so this is safe */
143 nr
= sscanf(buf
, "%s%llu%lg", idbuf
, &val
, &rate
);
145 fprintf(stderr
, "%s:%d: error parsing history file\n",
151 if (useless_number(idbuf
))
153 if ((n
= malloc(sizeof(*n
))) == NULL
) {
154 perror("nstat: malloc");
157 n
->id
= strdup(idbuf
);
172 static int count_spaces(const char *line
)
177 while ((c
= *line
++) != 0)
178 count
+= c
== ' ' || c
== '\n';
182 static void load_ugly_table(FILE *fp
)
187 struct nstat_ent
*db
= NULL
;
190 while ((nread
= getline(&buf
, &buflen
, fp
)) != -1) {
194 int count1
, count2
, skip
= 0;
196 p
= strchr(buf
, ':');
198 fprintf(stderr
, "%s:%d: error parsing history file\n",
202 count1
= count_spaces(buf
);
205 strncat(idbuf
, buf
, sizeof(idbuf
) - 1);
212 if ((next
= strchr(p
, ' ')) != NULL
)
214 else if ((next
= strchr(p
, '\n')) != NULL
)
216 if (off
< sizeof(idbuf
)) {
218 strncat(idbuf
, p
, sizeof(idbuf
) - off
- 1);
220 n
= malloc(sizeof(*n
));
222 perror("nstat: malloc");
225 n
->id
= strdup(idbuf
);
232 nread
= getline(&buf
, &buflen
, fp
);
234 fprintf(stderr
, "%s:%d: error parsing history file\n",
238 count2
= count_spaces(buf
);
240 skip
= count2
- count1
;
242 p
= strrchr(buf
, ' ');
244 fprintf(stderr
, "%s:%d: error parsing history file\n",
249 if (sscanf(p
+1, "%llu", &n
->val
) != 1) {
250 fprintf(stderr
, "%s:%d: error parsing history file\n",
254 /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
259 } while (p
> buf
+ off
+ 2);
266 if (useless_number(n
->id
)) {
276 static void load_sctp_snmp(void)
278 FILE *fp
= fdopen(net_sctp_snmp_open(), "r");
286 static void load_snmp(void)
288 FILE *fp
= fdopen(net_snmp_open(), "r");
296 static void load_snmp6(void)
298 FILE *fp
= fdopen(net_snmp6_open(), "r");
306 static void load_netstat(void)
308 FILE *fp
= fdopen(net_netstat_open(), "r");
317 static void dump_kern_db(FILE *fp
, int to_hist
)
319 json_writer_t
*jw
= json_output
? jsonw_new(fp
) : NULL
;
320 struct nstat_ent
*n
, *h
;
324 jsonw_start_object(jw
);
325 jsonw_pretty(jw
, pretty
);
326 jsonw_name(jw
, info_source
);
327 jsonw_start_object(jw
);
329 fprintf(fp
, "#%s\n", info_source
);
331 for (n
= kern_db
; n
; n
= n
->next
) {
332 unsigned long long val
= n
->val
;
334 if (!dump_zeros
&& !val
&& !n
->rate
)
337 struct nstat_ent
*h1
;
341 for (h1
= h
; h1
; h1
= h1
->next
) {
342 if (strcmp(h1
->id
, n
->id
) == 0) {
351 jsonw_uint_field(jw
, n
->id
, val
);
353 fprintf(fp
, "%-32s%-16llu%6.1f\n", n
->id
, val
, n
->rate
);
357 jsonw_end_object(jw
);
359 jsonw_end_object(jw
);
364 static void dump_incr_db(FILE *fp
)
366 json_writer_t
*jw
= json_output
? jsonw_new(fp
) : NULL
;
367 struct nstat_ent
*n
, *h
;
371 jsonw_start_object(jw
);
372 jsonw_pretty(jw
, pretty
);
373 jsonw_name(jw
, info_source
);
374 jsonw_start_object(jw
);
376 fprintf(fp
, "#%s\n", info_source
);
378 for (n
= kern_db
; n
; n
= n
->next
) {
380 unsigned long long val
= n
->val
;
381 struct nstat_ent
*h1
;
383 for (h1
= h
; h1
; h1
= h1
->next
) {
384 if (strcmp(h1
->id
, n
->id
) == 0) {
394 if (!dump_zeros
&& !val
&& !n
->rate
)
400 jsonw_uint_field(jw
, n
->id
, val
);
402 fprintf(fp
, "%-32s%-16llu%6.1f%s\n", n
->id
, val
,
403 n
->rate
, ovfl
?" (overflow)":"");
407 jsonw_end_object(jw
);
409 jsonw_end_object(jw
);
416 static void sigchild(int signo
)
420 static void update_db(int interval
)
422 struct nstat_ent
*n
, *h
;
435 for (n
= kern_db
; n
; n
= n
->next
) {
436 struct nstat_ent
*h1
;
438 for (h1
= h
; h1
; h1
= h1
->next
) {
439 if (strcmp(h1
->id
, n
->id
) == 0) {
441 unsigned long long incr
= h1
->val
- n
->val
;
444 sample
= (double)incr
* 1000.0 / interval
;
445 if (interval
>= scan_interval
) {
446 n
->rate
+= W
*(sample
-n
->rate
);
447 } else if (interval
>= 1000) {
448 if (interval
>= time_constant
) {
451 double w
= W
*(double)interval
/scan_interval
;
453 n
->rate
+= w
*(sample
-n
->rate
);
458 struct nstat_ent
*tmp
= h
;
473 #define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
476 static void server_loop(int fd
)
478 struct timeval snaptime
= { 0 };
482 p
.events
= p
.revents
= POLLIN
;
484 sprintf(info_source
, "%d.%lu sampling_interval=%d time_const=%d",
485 getpid(), (unsigned long)random(), scan_interval
/1000, time_constant
/1000);
497 gettimeofday(&now
, NULL
);
498 tdiff
= T_DIFF(now
, snaptime
);
499 if (tdiff
>= scan_interval
) {
504 if (poll(&p
, 1, scan_interval
- tdiff
) > 0
505 && (p
.revents
&POLLIN
)) {
506 int clnt
= accept(fd
, NULL
, NULL
);
513 } else if ((pid
= fork()) != 0) {
518 FILE *fp
= fdopen(clnt
, "w");
526 while (children
&& waitpid(-1, &status
, WNOHANG
) > 0)
531 static int verify_forging(int fd
)
534 socklen_t olen
= sizeof(cred
);
536 if (getsockopt(fd
, SOL_SOCKET
, SO_PEERCRED
, (void *)&cred
, &olen
) ||
539 if (cred
.uid
== getuid() || cred
.uid
== 0)
544 static void usage(void) __attribute__((noreturn
));
546 static void usage(void)
549 "Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
550 " -h, --help this message\n"
551 " -a, --ignore ignore history\n"
552 " -d, --scan=SECS sample every statistics every SECS\n"
553 " -j, --json format output in JSON\n"
554 " -n, --nooutput do history only\n"
555 " -p, --pretty pretty print\n"
556 " -r, --reset reset history\n"
557 " -s, --noupdate don't update history\n"
558 " -t, --interval=SECS report average over the last SECS\n"
559 " -V, --version output version information\n"
560 " -z, --zeros show entries with zero activity\n");
564 static const struct option longopts
[] = {
565 { "help", 0, 0, 'h' },
566 { "ignore", 0, 0, 'a' },
567 { "scan", 1, 0, 'd'},
568 { "nooutput", 0, 0, 'n' },
569 { "json", 0, 0, 'j' },
570 { "reset", 0, 0, 'r' },
571 { "noupdate", 0, 0, 's' },
572 { "pretty", 0, 0, 'p' },
573 { "interval", 1, 0, 't' },
574 { "version", 0, 0, 'V' },
575 { "zeros", 0, 0, 'z' },
579 int main(int argc
, char *argv
[])
582 struct sockaddr_un sun
;
583 FILE *hist_fp
= NULL
;
587 while ((ch
= getopt_long(argc
, argv
, "h?vVzrnasd:t:jp",
588 longopts
, NULL
)) != EOF
) {
606 scan_interval
= 1000*atoi(optarg
);
609 if (sscanf(optarg
, "%d", &time_constant
) != 1 ||
610 time_constant
<= 0) {
611 fprintf(stderr
, "nstat: invalid time constant divisor\n");
623 printf("nstat utility, iproute2-%s\n", version
);
635 sun
.sun_family
= AF_UNIX
;
637 sprintf(sun
.sun_path
+1, "nstat%d", getuid());
639 if (scan_interval
> 0) {
640 if (time_constant
== 0)
642 time_constant
*= 1000;
643 W
= 1 - 1/exp(log(10)*(double)scan_interval
/time_constant
);
644 if ((fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) < 0) {
645 perror("nstat: socket");
648 if (bind(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) < 0) {
649 perror("nstat: bind");
652 if (listen(fd
, 5) < 0) {
653 perror("nstat: listen");
657 perror("nstat: daemon");
660 signal(SIGPIPE
, SIG_IGN
);
661 signal(SIGCHLD
, sigchild
);
669 if ((hist_name
= getenv("NSTAT_HISTORY")) == NULL
) {
670 hist_name
= malloc(128);
671 sprintf(hist_name
, "/tmp/.nstat.u%d", getuid());
677 if (!ignore_history
|| !no_update
) {
680 fd
= open(hist_name
, O_RDWR
|O_CREAT
|O_NOFOLLOW
, 0600);
682 perror("nstat: open history file");
685 if ((hist_fp
= fdopen(fd
, "r+")) == NULL
) {
686 perror("nstat: fdopen history file");
689 if (flock(fileno(hist_fp
), LOCK_EX
)) {
690 perror("nstat: flock history file");
693 if (fstat(fileno(hist_fp
), &stb
) != 0) {
694 perror("nstat: fstat history file");
697 if (stb
.st_nlink
!= 1 || stb
.st_uid
!= getuid()) {
698 fprintf(stderr
, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
701 if (!ignore_history
) {
705 if ((tfp
= fopen("/proc/uptime", "r")) != NULL
) {
706 if (fscanf(tfp
, "%ld", &uptime
) != 1)
710 if (uptime
>= 0 && time(NULL
) >= stb
.st_mtime
+uptime
) {
711 fprintf(stderr
, "nstat: history is aged out, resetting\n");
712 if (ftruncate(fileno(hist_fp
), 0) < 0)
713 perror("nstat: ftruncate");
717 load_good_table(hist_fp
);
723 if ((fd
= socket(AF_UNIX
, SOCK_STREAM
, 0)) >= 0 &&
724 (connect(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) == 0
725 || (strcpy(sun
.sun_path
+1, "nstat0"),
726 connect(fd
, (struct sockaddr
*)&sun
, 2+1+strlen(sun
.sun_path
+1)) == 0))
727 && verify_forging(fd
) == 0) {
728 FILE *sfp
= fdopen(fd
, "r");
731 fprintf(stderr
, "nstat: fdopen failed: %s\n",
735 load_good_table(sfp
);
736 if (hist_db
&& source_mismatch
) {
737 fprintf(stderr
, "nstat: history is stale, ignoring it.\n");
745 if (hist_db
&& info_source
[0] && strcmp(info_source
, "kernel")) {
746 fprintf(stderr
, "nstat: history is stale, ignoring it.\n");
754 if (info_source
[0] == 0)
755 strcpy(info_source
, "kernel");
759 if (ignore_history
|| hist_db
== NULL
)
760 dump_kern_db(stdout
, 0);
762 dump_incr_db(stdout
);
765 if (ftruncate(fileno(hist_fp
), 0) < 0)
766 perror("nstat: ftruncate");
770 dump_kern_db(hist_fp
, 1);