]> git.proxmox.com Git - mirror_iproute2.git/blob - misc/nstat.c
88f52eaf8c8c266bc61f3b6ee5302c26951fd59e
[mirror_iproute2.git] / misc / nstat.c
1 /*
2 * nstat.c handy utility to read counters /proc/net/netstat and snmp
3 *
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.
8 *
9 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10 */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <time.h>
19 #include <sys/time.h>
20 #include <fnmatch.h>
21 #include <sys/file.h>
22 #include <sys/socket.h>
23 #include <sys/un.h>
24 #include <sys/poll.h>
25 #include <sys/wait.h>
26 #include <sys/stat.h>
27 #include <signal.h>
28 #include <math.h>
29 #include <getopt.h>
30
31 #include <json_writer.h>
32 #include <SNAPSHOT.h>
33 #include "utils.h"
34
35 int dump_zeros;
36 int reset_history;
37 int ignore_history;
38 int no_output;
39 int json_output;
40 int no_update;
41 int scan_interval;
42 int time_constant;
43 double W;
44 char **patterns;
45 int npatterns;
46
47 char info_source[128];
48 int source_mismatch;
49
50 static int generic_proc_open(const char *env, char *name)
51 {
52 char store[128];
53 char *p = getenv(env);
54
55 if (!p) {
56 p = getenv("PROC_ROOT") ? : "/proc";
57 snprintf(store, sizeof(store)-1, "%s/%s", p, name);
58 p = store;
59 }
60 return open(p, O_RDONLY);
61 }
62
63 static int net_netstat_open(void)
64 {
65 return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
66 }
67
68 static int net_snmp_open(void)
69 {
70 return generic_proc_open("PROC_NET_SNMP", "net/snmp");
71 }
72
73 static int net_snmp6_open(void)
74 {
75 return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
76 }
77
78 static int net_sctp_snmp_open(void)
79 {
80 return generic_proc_open("PROC_NET_SCTP_SNMP", "net/sctp/snmp");
81 }
82
83 struct nstat_ent {
84 struct nstat_ent *next;
85 char *id;
86 unsigned long long val;
87 double rate;
88 };
89
90 struct nstat_ent *kern_db;
91 struct nstat_ent *hist_db;
92
93 static const char *useless_numbers[] = {
94 "IpForwarding", "IpDefaultTTL",
95 "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
96 "TcpMaxConn", "TcpCurrEstab"
97 };
98
99 static int useless_number(const char *id)
100 {
101 int i;
102
103 for (i = 0; i < ARRAY_SIZE(useless_numbers); i++)
104 if (strcmp(id, useless_numbers[i]) == 0)
105 return 1;
106 return 0;
107 }
108
109 static int match(const char *id)
110 {
111 int i;
112
113 if (npatterns == 0)
114 return 1;
115
116 for (i = 0; i < npatterns; i++) {
117 if (!fnmatch(patterns[i], id, FNM_CASEFOLD))
118 return 1;
119 }
120 return 0;
121 }
122
123 static void load_good_table(FILE *fp)
124 {
125 char buf[4096];
126 struct nstat_ent *db = NULL;
127 struct nstat_ent *n;
128
129 while (fgets(buf, sizeof(buf), fp) != NULL) {
130 int nr;
131 unsigned long long val;
132 double rate;
133 char idbuf[sizeof(buf)];
134
135 if (buf[0] == '#') {
136 buf[strlen(buf)-1] = 0;
137 if (info_source[0] && strcmp(info_source, buf+1))
138 source_mismatch = 1;
139 info_source[0] = 0;
140 strncat(info_source, buf+1, sizeof(info_source)-1);
141 continue;
142 }
143 /* idbuf is as big as buf, so this is safe */
144 nr = sscanf(buf, "%s%llu%lg", idbuf, &val, &rate);
145 if (nr < 2) {
146 fprintf(stderr, "%s:%d: error parsing history file\n",
147 __FILE__, __LINE__);
148 exit(-2);
149 }
150 if (nr < 3)
151 rate = 0;
152 if (useless_number(idbuf))
153 continue;
154 if ((n = malloc(sizeof(*n))) == NULL) {
155 perror("nstat: malloc");
156 exit(-1);
157 }
158 n->id = strdup(idbuf);
159 n->val = val;
160 n->rate = rate;
161 n->next = db;
162 db = n;
163 }
164
165 while (db) {
166 n = db;
167 db = db->next;
168 n->next = kern_db;
169 kern_db = n;
170 }
171 }
172
173 static int count_spaces(const char *line)
174 {
175 int count = 0;
176 char c;
177
178 while ((c = *line++) != 0)
179 count += c == ' ' || c == '\n';
180 return count;
181 }
182
183 static void load_ugly_table(FILE *fp)
184 {
185 char *buf = NULL;
186 size_t buflen = 0;
187 ssize_t nread;
188 struct nstat_ent *db = NULL;
189 struct nstat_ent *n;
190
191 while ((nread = getline(&buf, &buflen, fp)) != -1) {
192 char idbuf[4096];
193 int off;
194 char *p;
195 int count1, count2, skip = 0;
196
197 p = strchr(buf, ':');
198 if (!p) {
199 fprintf(stderr, "%s:%d: error parsing history file\n",
200 __FILE__, __LINE__);
201 exit(-2);
202 }
203 count1 = count_spaces(buf);
204 *p = 0;
205 idbuf[0] = 0;
206 strncat(idbuf, buf, sizeof(idbuf) - 1);
207 off = p - buf;
208 p += 2;
209
210 while (*p) {
211 char *next;
212
213 if ((next = strchr(p, ' ')) != NULL)
214 *next++ = 0;
215 else if ((next = strchr(p, '\n')) != NULL)
216 *next++ = 0;
217 if (off < sizeof(idbuf)) {
218 idbuf[off] = 0;
219 strncat(idbuf, p, sizeof(idbuf) - off - 1);
220 }
221 n = malloc(sizeof(*n));
222 if (!n) {
223 perror("nstat: malloc");
224 exit(-1);
225 }
226 n->id = strdup(idbuf);
227 n->rate = 0;
228 n->next = db;
229 db = n;
230 p = next;
231 }
232 n = db;
233 nread = getline(&buf, &buflen, fp);
234 if (nread == -1) {
235 fprintf(stderr, "%s:%d: error parsing history file\n",
236 __FILE__, __LINE__);
237 exit(-2);
238 }
239 count2 = count_spaces(buf);
240 if (count2 > count1)
241 skip = count2 - count1;
242 do {
243 p = strrchr(buf, ' ');
244 if (!p) {
245 fprintf(stderr, "%s:%d: error parsing history file\n",
246 __FILE__, __LINE__);
247 exit(-2);
248 }
249 *p = 0;
250 if (sscanf(p+1, "%llu", &n->val) != 1) {
251 fprintf(stderr, "%s:%d: error parsing history file\n",
252 __FILE__, __LINE__);
253 exit(-2);
254 }
255 /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
256 if (skip)
257 skip--;
258 else
259 n = n->next;
260 } while (p > buf + off + 2);
261 }
262 free(buf);
263
264 while (db) {
265 n = db;
266 db = db->next;
267 if (useless_number(n->id)) {
268 free(n->id);
269 free(n);
270 } else {
271 n->next = kern_db;
272 kern_db = n;
273 }
274 }
275 }
276
277 static void load_sctp_snmp(void)
278 {
279 FILE *fp = fdopen(net_sctp_snmp_open(), "r");
280
281 if (fp) {
282 load_good_table(fp);
283 fclose(fp);
284 }
285 }
286
287 static void load_snmp(void)
288 {
289 FILE *fp = fdopen(net_snmp_open(), "r");
290
291 if (fp) {
292 load_ugly_table(fp);
293 fclose(fp);
294 }
295 }
296
297 static void load_snmp6(void)
298 {
299 FILE *fp = fdopen(net_snmp6_open(), "r");
300
301 if (fp) {
302 load_good_table(fp);
303 fclose(fp);
304 }
305 }
306
307 static void load_netstat(void)
308 {
309 FILE *fp = fdopen(net_netstat_open(), "r");
310
311 if (fp) {
312 load_ugly_table(fp);
313 fclose(fp);
314 }
315 }
316
317
318 static void dump_kern_db(FILE *fp, int to_hist)
319 {
320 json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
321 struct nstat_ent *n, *h;
322
323 h = hist_db;
324 if (jw) {
325 jsonw_start_object(jw);
326 jsonw_pretty(jw, pretty);
327 jsonw_name(jw, info_source);
328 jsonw_start_object(jw);
329 } else
330 fprintf(fp, "#%s\n", info_source);
331
332 for (n = kern_db; n; n = n->next) {
333 unsigned long long val = n->val;
334
335 if (!dump_zeros && !val && !n->rate)
336 continue;
337 if (!match(n->id)) {
338 struct nstat_ent *h1;
339
340 if (!to_hist)
341 continue;
342 for (h1 = h; h1; h1 = h1->next) {
343 if (strcmp(h1->id, n->id) == 0) {
344 val = h1->val;
345 h = h1->next;
346 break;
347 }
348 }
349 }
350
351 if (jw)
352 jsonw_uint_field(jw, n->id, val);
353 else
354 fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
355 }
356
357 if (jw) {
358 jsonw_end_object(jw);
359
360 jsonw_end_object(jw);
361 jsonw_destroy(&jw);
362 }
363 }
364
365 static void dump_incr_db(FILE *fp)
366 {
367 json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
368 struct nstat_ent *n, *h;
369
370 h = hist_db;
371 if (jw) {
372 jsonw_start_object(jw);
373 jsonw_pretty(jw, pretty);
374 jsonw_name(jw, info_source);
375 jsonw_start_object(jw);
376 } else
377 fprintf(fp, "#%s\n", info_source);
378
379 for (n = kern_db; n; n = n->next) {
380 int ovfl = 0;
381 unsigned long long val = n->val;
382 struct nstat_ent *h1;
383
384 for (h1 = h; h1; h1 = h1->next) {
385 if (strcmp(h1->id, n->id) == 0) {
386 if (val < h1->val) {
387 ovfl = 1;
388 val = h1->val;
389 }
390 val -= h1->val;
391 h = h1->next;
392 break;
393 }
394 }
395 if (!dump_zeros && !val && !n->rate)
396 continue;
397 if (!match(n->id))
398 continue;
399
400 if (jw)
401 jsonw_uint_field(jw, n->id, val);
402 else
403 fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
404 n->rate, ovfl?" (overflow)":"");
405 }
406
407 if (jw) {
408 jsonw_end_object(jw);
409
410 jsonw_end_object(jw);
411 jsonw_destroy(&jw);
412 }
413 }
414
415 static int children;
416
417 static void sigchild(int signo)
418 {
419 }
420
421 static void update_db(int interval)
422 {
423 struct nstat_ent *n, *h;
424
425 n = kern_db;
426 kern_db = NULL;
427
428 load_netstat();
429 load_snmp6();
430 load_snmp();
431 load_sctp_snmp();
432
433 h = kern_db;
434 kern_db = n;
435
436 for (n = kern_db; n; n = n->next) {
437 struct nstat_ent *h1;
438
439 for (h1 = h; h1; h1 = h1->next) {
440 if (strcmp(h1->id, n->id) == 0) {
441 double sample;
442 unsigned long long incr = h1->val - n->val;
443
444 n->val = h1->val;
445 sample = (double)incr * 1000.0 / interval;
446 if (interval >= scan_interval) {
447 n->rate += W*(sample-n->rate);
448 } else if (interval >= 1000) {
449 if (interval >= time_constant) {
450 n->rate = sample;
451 } else {
452 double w = W*(double)interval/scan_interval;
453
454 n->rate += w*(sample-n->rate);
455 }
456 }
457
458 while (h != h1) {
459 struct nstat_ent *tmp = h;
460
461 h = h->next;
462 free(tmp->id);
463 free(tmp);
464 };
465 h = h1->next;
466 free(h1->id);
467 free(h1);
468 break;
469 }
470 }
471 }
472 }
473
474 #define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
475
476
477 static void server_loop(int fd)
478 {
479 struct timeval snaptime = { 0 };
480 struct pollfd p;
481
482 p.fd = fd;
483 p.events = p.revents = POLLIN;
484
485 sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
486 getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
487
488 load_netstat();
489 load_snmp6();
490 load_snmp();
491 load_sctp_snmp();
492
493 for (;;) {
494 int status;
495 time_t tdiff;
496 struct timeval now;
497
498 gettimeofday(&now, NULL);
499 tdiff = T_DIFF(now, snaptime);
500 if (tdiff >= scan_interval) {
501 update_db(tdiff);
502 snaptime = now;
503 tdiff = 0;
504 }
505 if (poll(&p, 1, scan_interval - tdiff) > 0
506 && (p.revents&POLLIN)) {
507 int clnt = accept(fd, NULL, NULL);
508
509 if (clnt >= 0) {
510 pid_t pid;
511
512 if (children >= 5) {
513 close(clnt);
514 } else if ((pid = fork()) != 0) {
515 if (pid > 0)
516 children++;
517 close(clnt);
518 } else {
519 FILE *fp = fdopen(clnt, "w");
520
521 if (fp)
522 dump_kern_db(fp, 0);
523 exit(0);
524 }
525 }
526 }
527 while (children && waitpid(-1, &status, WNOHANG) > 0)
528 children--;
529 }
530 }
531
532 static int verify_forging(int fd)
533 {
534 struct ucred cred;
535 socklen_t olen = sizeof(cred);
536
537 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &olen) ||
538 olen < sizeof(cred))
539 return -1;
540 if (cred.uid == getuid() || cred.uid == 0)
541 return 0;
542 return -1;
543 }
544
545 static void usage(void) __attribute__((noreturn));
546
547 static void usage(void)
548 {
549 fprintf(stderr,
550 "Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
551 " -h, --help this message\n"
552 " -a, --ignore ignore history\n"
553 " -d, --scan=SECS sample every statistics every SECS\n"
554 " -j, --json format output in JSON\n"
555 " -n, --nooutput do history only\n"
556 " -p, --pretty pretty print\n"
557 " -r, --reset reset history\n"
558 " -s, --noupdate don't update history\n"
559 " -t, --interval=SECS report average over the last SECS\n"
560 " -V, --version output version information\n"
561 " -z, --zeros show entries with zero activity\n");
562 exit(-1);
563 }
564
565 static const struct option longopts[] = {
566 { "help", 0, 0, 'h' },
567 { "ignore", 0, 0, 'a' },
568 { "scan", 1, 0, 'd'},
569 { "nooutput", 0, 0, 'n' },
570 { "json", 0, 0, 'j' },
571 { "reset", 0, 0, 'r' },
572 { "noupdate", 0, 0, 's' },
573 { "pretty", 0, 0, 'p' },
574 { "interval", 1, 0, 't' },
575 { "version", 0, 0, 'V' },
576 { "zeros", 0, 0, 'z' },
577 { 0 }
578 };
579
580 int main(int argc, char *argv[])
581 {
582 char *hist_name;
583 struct sockaddr_un sun;
584 FILE *hist_fp = NULL;
585 int ch;
586 int fd;
587
588 while ((ch = getopt_long(argc, argv, "h?vVzrnasd:t:jp",
589 longopts, NULL)) != EOF) {
590 switch (ch) {
591 case 'z':
592 dump_zeros = 1;
593 break;
594 case 'r':
595 reset_history = 1;
596 break;
597 case 'a':
598 ignore_history = 1;
599 break;
600 case 's':
601 no_update = 1;
602 break;
603 case 'n':
604 no_output = 1;
605 break;
606 case 'd':
607 scan_interval = 1000*atoi(optarg);
608 break;
609 case 't':
610 if (sscanf(optarg, "%d", &time_constant) != 1 ||
611 time_constant <= 0) {
612 fprintf(stderr, "nstat: invalid time constant divisor\n");
613 exit(-1);
614 }
615 break;
616 case 'j':
617 json_output = 1;
618 break;
619 case 'p':
620 pretty = 1;
621 break;
622 case 'v':
623 case 'V':
624 printf("nstat utility, iproute2-ss%s\n", SNAPSHOT);
625 exit(0);
626 case 'h':
627 case '?':
628 default:
629 usage();
630 }
631 }
632
633 argc -= optind;
634 argv += optind;
635
636 sun.sun_family = AF_UNIX;
637 sun.sun_path[0] = 0;
638 sprintf(sun.sun_path+1, "nstat%d", getuid());
639
640 if (scan_interval > 0) {
641 if (time_constant == 0)
642 time_constant = 60;
643 time_constant *= 1000;
644 W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
645 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
646 perror("nstat: socket");
647 exit(-1);
648 }
649 if (bind(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
650 perror("nstat: bind");
651 exit(-1);
652 }
653 if (listen(fd, 5) < 0) {
654 perror("nstat: listen");
655 exit(-1);
656 }
657 if (daemon(0, 0)) {
658 perror("nstat: daemon");
659 exit(-1);
660 }
661 signal(SIGPIPE, SIG_IGN);
662 signal(SIGCHLD, sigchild);
663 server_loop(fd);
664 exit(0);
665 }
666
667 patterns = argv;
668 npatterns = argc;
669
670 if ((hist_name = getenv("NSTAT_HISTORY")) == NULL) {
671 hist_name = malloc(128);
672 sprintf(hist_name, "/tmp/.nstat.u%d", getuid());
673 }
674
675 if (reset_history)
676 unlink(hist_name);
677
678 if (!ignore_history || !no_update) {
679 struct stat stb;
680
681 fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
682 if (fd < 0) {
683 perror("nstat: open history file");
684 exit(-1);
685 }
686 if ((hist_fp = fdopen(fd, "r+")) == NULL) {
687 perror("nstat: fdopen history file");
688 exit(-1);
689 }
690 if (flock(fileno(hist_fp), LOCK_EX)) {
691 perror("nstat: flock history file");
692 exit(-1);
693 }
694 if (fstat(fileno(hist_fp), &stb) != 0) {
695 perror("nstat: fstat history file");
696 exit(-1);
697 }
698 if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
699 fprintf(stderr, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
700 exit(-1);
701 }
702 if (!ignore_history) {
703 FILE *tfp;
704 long uptime = -1;
705
706 if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
707 if (fscanf(tfp, "%ld", &uptime) != 1)
708 uptime = -1;
709 fclose(tfp);
710 }
711 if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
712 fprintf(stderr, "nstat: history is aged out, resetting\n");
713 if (ftruncate(fileno(hist_fp), 0) < 0)
714 perror("nstat: ftruncate");
715 }
716 }
717
718 load_good_table(hist_fp);
719
720 hist_db = kern_db;
721 kern_db = NULL;
722 }
723
724 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
725 (connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0
726 || (strcpy(sun.sun_path+1, "nstat0"),
727 connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
728 && verify_forging(fd) == 0) {
729 FILE *sfp = fdopen(fd, "r");
730
731 if (!sfp) {
732 fprintf(stderr, "nstat: fdopen failed: %s\n",
733 strerror(errno));
734 close(fd);
735 } else {
736 load_good_table(sfp);
737 if (hist_db && source_mismatch) {
738 fprintf(stderr, "nstat: history is stale, ignoring it.\n");
739 hist_db = NULL;
740 }
741 fclose(sfp);
742 }
743 } else {
744 if (fd >= 0)
745 close(fd);
746 if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
747 fprintf(stderr, "nstat: history is stale, ignoring it.\n");
748 hist_db = NULL;
749 info_source[0] = 0;
750 }
751 load_netstat();
752 load_snmp6();
753 load_snmp();
754 load_sctp_snmp();
755 if (info_source[0] == 0)
756 strcpy(info_source, "kernel");
757 }
758
759 if (!no_output) {
760 if (ignore_history || hist_db == NULL)
761 dump_kern_db(stdout, 0);
762 else
763 dump_incr_db(stdout);
764 }
765 if (!no_update) {
766 if (ftruncate(fileno(hist_fp), 0) < 0)
767 perror("nstat: ftruncate");
768 rewind(hist_fp);
769
770 json_output = 0;
771 dump_kern_db(hist_fp, 1);
772 fclose(hist_fp);
773 }
774 exit(0);
775 }