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