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