Tomato 1.26 beta (1786)
[tomato.git] / release / src / router / rstats / rstats.c
blobe876045cdeddef9a959706dbd0a16bba2534d517
1 /*
3 rstats
4 Copyright (C) 2006-2009 Jonathan Zarate
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <signal.h>
24 #include <time.h>
25 #include <sys/types.h>
26 #include <sys/sysinfo.h>
27 #include <sys/stat.h>
28 #include <stdint.h>
29 #include <syslog.h>
31 #include <bcmnvram.h>
32 #include <shutils.h>
33 #include <shared.h>
36 // #define DEBUG_NOISY
37 // #define DEBUG_STIME
40 #ifdef DEBUG_NOISY
41 #define _dprintf(args...) cprintf(args)
42 #else
43 #define _dprintf(args...) do { } while (0)
44 #endif
48 #define K 1024
49 #define M (1024 * 1024)
50 #define G (1024 * 1024 * 1024)
52 #define SMIN 60
53 #define SHOUR (60 * 60)
54 #define SDAY (60 * 60 * 24)
55 #define Y2K 946684800UL
57 #define INTERVAL 120
59 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
60 #define MAX_NDAILY 62
61 #define MAX_NMONTHLY 25
62 #define MAX_SPEED_IF 10
63 #define MAX_ROLLOVER (225 * M)
65 #define MAX_COUNTER 2
66 #define RX 0
67 #define TX 1
69 #define DAILY 0
70 #define MONTHLY 1
72 #define ID_V0 0x30305352
73 #define ID_V1 0x31305352
74 #define CURRENT_ID ID_V1
77 typedef struct {
78 uint32_t xtime;
79 uint64_t counter[MAX_COUNTER];
80 } data_t;
82 typedef struct {
83 uint32_t id;
85 data_t daily[MAX_NDAILY];
86 int dailyp;
88 data_t monthly[MAX_NMONTHLY];
89 int monthlyp;
90 } history_t;
92 typedef struct {
93 uint32_t id;
95 data_t daily[62];
96 int dailyp;
98 data_t monthly[12];
99 int monthlyp;
100 } history_v0_t;
102 typedef struct {
103 char ifname[12];
104 long utime;
105 unsigned long speed[MAX_NSPEED][MAX_COUNTER];
106 unsigned long last[MAX_COUNTER];
107 int tail;
108 char sync;
109 } speed_t;
111 history_t history;
112 speed_t speed[MAX_SPEED_IF];
113 int speed_count;
114 long save_utime;
115 char save_path[96];
116 long uptime;
118 volatile int gothup = 0;
119 volatile int gotuser = 0;
120 volatile int gotterm = 0;
122 const char history_fn[] = "/var/lib/misc/rstats-history";
123 const char speed_fn[] = "/var/lib/misc/rstats-speed";
124 const char uncomp_fn[] = "/var/tmp/rstats-uncomp";
125 const char source_fn[] = "/var/lib/misc/rstats-source";
128 static int get_stime(void)
130 #ifdef DEBUG_STIME
131 return 90;
132 #else
133 int t;
134 t = nvram_get_int("rstats_stime");
135 if (t < 1) t = 1;
136 else if (t > 8760) t = 8760;
137 return t * SHOUR;
138 #endif
141 static int comp(const char *path, void *buffer, int size)
143 char s[256];
145 if (f_write(path, buffer, size, 0, 0) != size) return 0;
147 sprintf(s, "%s.gz", path);
148 unlink(s);
150 sprintf(s, "gzip %s", path);
151 return system(s) == 0;
154 static void save(int quick)
156 int i;
157 char *bi, *bo;
158 int n;
159 char hgz[256];
160 char tmp[256];
161 char bak[256];
162 time_t now;
163 struct tm *tms;
164 static int lastbak = -1;
166 _dprintf("%s: quick=%d\n", __FUNCTION__, quick);
168 f_write("/var/lib/misc/rstats-stime", &save_utime, sizeof(save_utime), 0, 0);
170 comp(speed_fn, speed, sizeof(speed[0]) * speed_count);
173 if ((now = time(0)) < Y2K) {
174 _dprintf("%s: time not set\n", __FUNCTION__);
175 return;
179 comp(history_fn, &history, sizeof(history));
181 _dprintf("%s: write source=%s\n", __FUNCTION__, save_path);
182 f_write_string(source_fn, save_path, 0, 0);
184 if (quick) {
185 return;
188 sprintf(hgz, "%s.gz", history_fn);
190 if (strcmp(save_path, "*nvram") == 0) {
191 if (!wait_action_idle(10)) {
192 _dprintf("%s: busy, not saving\n", __FUNCTION__);
193 return;
196 if ((n = f_read_alloc(hgz, &bi, 20 * 1024)) > 0) {
197 if ((bo = malloc(base64_encoded_len(n) + 1)) != NULL) {
198 n = base64_encode(bi, bo, n);
199 bo[n] = 0;
200 nvram_set("rstats_data", bo);
201 if (!nvram_match("debug_nocommit", "1")) nvram_commit();
203 _dprintf("%s: nvram commit\n", __FUNCTION__);
205 free(bo);
208 free(bi);
210 else if (save_path[0] != 0) {
211 strcpy(tmp, save_path);
212 strcat(tmp, ".tmp");
214 for (i = 15; i > 0; --i) {
215 if (!wait_action_idle(10)) {
216 _dprintf("%s: busy, not saving\n", __FUNCTION__);
218 else {
219 _dprintf("%s: cp %s %s\n", __FUNCTION__, hgz, tmp);
220 if (eval("cp", hgz, tmp) == 0) {
221 _dprintf("%s: copy ok\n", __FUNCTION__);
223 if (!nvram_match("rstats_bak", "0")) {
224 now = time(0);
225 tms = localtime(&now);
226 if (lastbak != tms->tm_yday) {
227 strcpy(bak, save_path);
228 n = strlen(bak);
229 if ((n > 3) && (strcmp(bak + (n - 3), ".gz") == 0)) n -= 3;
230 sprintf(bak + n, "_%d.bak", ((tms->tm_yday / 7) % 3) + 1);
231 if (eval("cp", save_path, bak) == 0) lastbak = tms->tm_yday;
235 _dprintf("%s: rename %s %s\n", __FUNCTION__, tmp, save_path);
236 if (rename(tmp, save_path) == 0) {
237 _dprintf("%s: rename ok\n", __FUNCTION__);
238 break;
243 // might not be ready
244 sleep(3);
245 if (gotterm) break;
250 static int decomp(const char *fname, void *buffer, int size, int max)
252 char s[256];
253 int n;
255 _dprintf("%s: fname=%s\n", __FUNCTION__, fname);
257 unlink(uncomp_fn);
259 n = 0;
260 sprintf(s, "gzip -dc %s > %s", fname, uncomp_fn);
261 if (system(s) == 0) {
262 n = f_read(uncomp_fn, buffer, size * max);
263 _dprintf("%s: n=%d\n", __FUNCTION__, n);
264 if (n <= 0) n = 0;
265 else n = n / size;
267 else {
268 _dprintf("%s: %s != 0\n", __FUNCTION__, s);
270 unlink(uncomp_fn);
271 memset((char *)buffer + (size * n), 0, (max - n) * size);
272 return n;
275 static void clear_history(void)
277 memset(&history, 0, sizeof(history));
278 history.id = CURRENT_ID;
281 static int load_history(const char *fname)
283 history_t hist;
285 _dprintf("%s: fname=%s\n", __FUNCTION__, fname);
287 if ((decomp(fname, &hist, sizeof(hist), 1) != 1) || (hist.id != CURRENT_ID)) {
288 history_v0_t v0;
290 if ((decomp(fname, &v0, sizeof(v0), 1) != 1) || (v0.id != ID_V0)) {
291 _dprintf("%s: load failed\n", __FUNCTION__);
292 return 0;
294 else {
295 // --- temp conversion ---
296 clear_history();
298 // V0 -> V1
299 history.id = CURRENT_ID;
300 memcpy(history.daily, v0.daily, sizeof(history.daily));
301 history.dailyp = v0.dailyp;
302 memcpy(history.monthly, v0.monthly, sizeof(v0.monthly)); // v0 is just shorter
303 history.monthlyp = v0.monthlyp;
306 else {
307 memcpy(&history, &hist, sizeof(history));
310 _dprintf("%s: dailyp=%d monthlyp=%d\n", __FUNCTION__, history.dailyp, history.monthlyp);
311 return 1;
314 static void load_new(void)
316 char hgz[256];
318 sprintf(hgz, "%s.gz.new", history_fn);
319 if (load_history(hgz)) save(0);
320 unlink(hgz);
323 static void load(int new)
325 int i;
326 long t;
327 char *bi, *bo;
328 int n;
329 char hgz[256];
330 char sp[sizeof(save_path)];
331 unsigned char mac[6];
333 uptime = get_uptime();
335 strlcpy(save_path, nvram_safe_get("rstats_path"), sizeof(save_path) - 32);
336 if (((n = strlen(save_path)) > 0) && (save_path[n - 1] == '/')) {
337 ether_atoe(nvram_safe_get("et0macaddr"), mac);
338 sprintf(save_path + n, "tomato_rstats_%02x%02x%02x%02x%02x%02x.gz",
339 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
342 if (f_read("/var/lib/misc/rstats-stime", &save_utime, sizeof(save_utime)) != sizeof(save_utime)) {
343 save_utime = 0;
345 t = uptime + get_stime();
346 if ((save_utime < uptime) || (save_utime > t)) save_utime = t;
347 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__, uptime / 60, save_utime / 60);
351 sprintf(hgz, "%s.gz", speed_fn);
352 speed_count = decomp(hgz, speed, sizeof(speed[0]), MAX_SPEED_IF);
353 _dprintf("%s: speed_count = %d\n", __FUNCTION__, speed_count);
355 for (i = 0; i < speed_count; ++i) {
356 if (speed[i].utime > uptime) {
357 speed[i].utime = uptime;
358 speed[i].sync = 1;
364 sprintf(hgz, "%s.gz", history_fn);
366 if (new) {
367 unlink(hgz);
368 save_utime = 0;
369 return;
372 f_read_string(source_fn, sp, sizeof(sp)); // always terminated
373 _dprintf("%s: read source=%s save_path=%s\n", __FUNCTION__, sp, save_path);
374 if ((strcmp(sp, save_path) == 0) && (load_history(hgz))) {
375 _dprintf("%s: using local file\n", __FUNCTION__);
376 return;
379 if (save_path[0] != 0) {
380 if (strcmp(save_path, "*nvram") == 0) {
381 if (!wait_action_idle(60)) exit(0);
383 bi = nvram_safe_get("rstats_data");
384 if ((n = strlen(bi)) > 0) {
385 if ((bo = malloc(base64_decoded_len(n))) != NULL) {
386 n = base64_decode(bi, bo, n);
387 _dprintf("%s: nvram n=%d\n", __FUNCTION__, n);
388 f_write(hgz, bo, n, 0, 0);
389 free(bo);
390 load_history(hgz);
394 else {
395 i = 1;
396 while (1) {
397 if (wait_action_idle(10)) {
399 // cifs quirk: try forcing refresh
400 eval("ls", save_path);
402 if (load_history(save_path)) {
403 f_write_string(source_fn, save_path, 0, 0);
404 break;
408 // not ready...
409 sleep(i);
410 if ((i *= 2) > 900) i = 900; // 15m
412 if (gotterm) {
413 save_path[0] = 0;
414 return;
417 if (i > (3 * 60)) {
418 syslog(LOG_WARNING, "Problem loading %s. Still trying...", save_path);
425 static void save_speedjs(long next)
427 int i, j, k;
428 speed_t *sp;
429 int p;
430 FILE *f;
431 uint64_t total;
432 uint64_t tmax;
433 unsigned long n;
434 char c;
436 if ((f = fopen("/var/tmp/rstats-speed.js", "w")) == NULL) return;
438 _dprintf("%s: speed_count = %d\n", __FUNCTION__, speed_count);
440 fprintf(f, "\nspeed_history = {\n");
442 for (i = 0; i < speed_count; ++i) {
443 sp = &speed[i];
444 fprintf(f, "%s'%s': {\n", i ? " },\n" : "", sp->ifname);
445 for (j = 0; j < MAX_COUNTER; ++j) {
446 total = tmax = 0;
447 fprintf(f, "%sx: [", j ? ",\n t" : " r");
448 p = sp->tail;
449 for (k = 0; k < MAX_NSPEED; ++k) {
450 p = (p + 1) % MAX_NSPEED;
451 n = sp->speed[p][j];
452 fprintf(f, "%s%lu", k ? "," : "", n);
453 total += n;
454 if (n > tmax) tmax = n;
456 fprintf(f, "],\n");
458 c = j ? 't' : 'r';
459 fprintf(f, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
460 c, total / MAX_NSPEED, c, tmax, c, total);
463 fprintf(f, "%s_next: %ld};\n", speed_count ? "},\n" : "", ((next >= 1) ? next : 1));
464 fclose(f);
466 rename("/var/tmp/rstats-speed.js", "/var/spool/rstats-speed.js");
470 static void save_datajs(FILE *f, int mode)
472 data_t *data;
473 int p;
474 int max;
475 int k, kn;
477 fprintf(f, "\n%s_history = [\n", (mode == DAILY) ? "daily" : "monthly");
479 if (mode == DAILY) {
480 data = history.daily;
481 p = history.dailyp;
482 max = MAX_NDAILY;
484 else {
485 data = history.monthly;
486 p = history.monthlyp;
487 max = MAX_NMONTHLY;
489 kn = 0;
490 for (k = max; k > 0; --k) {
491 p = (p + 1) % max;
492 if (data[p].xtime == 0) continue;
493 fprintf(f, "%s[0x%lx,0x%llx,0x%llx]", kn ? "," : "",
494 (unsigned long)data[p].xtime, data[p].counter[0] / K, data[p].counter[1] / K);
495 ++kn;
497 fprintf(f, "];\n");
500 static void save_histjs(void)
502 FILE *f;
504 if ((f = fopen("/var/tmp/rstats-history.js", "w")) != NULL) {
505 save_datajs(f, DAILY);
506 save_datajs(f, MONTHLY);
507 fclose(f);
508 rename("/var/tmp/rstats-history.js", "/var/spool/rstats-history.js");
513 static void bump(data_t *data, int *tail, int max, uint32_t xnow, unsigned long *counter)
515 int t, i;
517 t = *tail;
518 if (data[t].xtime != xnow) {
519 for (i = max - 1; i >= 0; --i) {
520 if (data[i].xtime == xnow) {
521 t = i;
522 break;
525 if (i < 0) {
526 *tail = t = (t + 1) % max;
527 data[t].xtime = xnow;
528 memset(data[t].counter, 0, sizeof(data[0].counter));
531 for (i = 0; i < MAX_COUNTER; ++i) {
532 data[t].counter[i] += counter[i];
536 static void calc(void)
538 FILE *f;
539 char buf[256];
540 char *ifname;
541 char *p;
542 unsigned long counter[MAX_COUNTER];
543 speed_t *sp;
544 int i, j;
545 time_t now;
546 time_t mon;
547 struct tm *tms;
548 uint32_t c;
549 uint32_t sc;
550 unsigned long diff;
551 long tick;
552 int n;
553 char *exclude;
555 now = time(0);
556 exclude = nvram_safe_get("rstats_exclude");
558 if ((f = fopen("/proc/net/dev", "r")) == NULL) return;
559 fgets(buf, sizeof(buf), f); // header
560 fgets(buf, sizeof(buf), f); // "
561 while (fgets(buf, sizeof(buf), f)) {
562 if ((p = strchr(buf, ':')) == NULL) continue;
563 *p = 0;
564 if ((ifname = strrchr(buf, ' ')) == NULL) ifname = buf;
565 else ++ifname;
566 if ((strcmp(ifname, "lo") == 0) || (find_word(exclude, ifname))) continue;
568 // <rx bytes, packets, errors, dropped, fifo errors, frame errors, compressed, multicast><tx ...>
569 if (sscanf(p + 1, "%lu%*u%*u%*u%*u%*u%*u%*u%lu", &counter[0], &counter[1]) != 2) continue;
571 sp = speed;
572 for (i = speed_count; i > 0; --i) {
573 if (strcmp(sp->ifname, ifname) == 0) break;
574 ++sp;
576 if (i == 0) {
577 if (speed_count >= MAX_SPEED_IF) continue;
579 _dprintf("%s: add %s as #%d\n", __FUNCTION__, ifname, speed_count);
581 i = speed_count++;
582 sp = &speed[i];
583 memset(sp, 0, sizeof(*sp));
584 strcpy(sp->ifname, ifname);
585 sp->sync = 1;
586 sp->utime = uptime;
588 if (sp->sync) {
589 _dprintf("%s: sync %s\n", __FUNCTION__, ifname);
590 sp->sync = -1;
592 memcpy(sp->last, counter, sizeof(sp->last));
593 memset(counter, 0, sizeof(counter));
595 else {
596 sp->sync = -1;
598 tick = uptime - sp->utime;
599 n = tick / INTERVAL;
600 if (n < 1) {
601 _dprintf("%s: %s is a little early... %d < %d\n", __FUNCTION__, ifname, tick, INTERVAL);
602 continue;
605 sp->utime += (n * INTERVAL);
606 _dprintf("%s: %s n=%d tick=%d\n", __FUNCTION__, ifname, n, tick);
608 for (i = 0; i < MAX_COUNTER; ++i) {
609 c = counter[i];
610 sc = sp->last[i];
611 if (c < sc) {
612 diff = (0xFFFFFFFF - sc) + c;
613 if (diff > MAX_ROLLOVER) diff = 0;
615 else {
616 diff = c - sc;
618 sp->last[i] = c;
619 counter[i] = diff;
622 for (j = 0; j < n; ++j) {
623 sp->tail = (sp->tail + 1) % MAX_NSPEED;
624 for (i = 0; i < MAX_COUNTER; ++i) {
625 sp->speed[sp->tail][i] = counter[i] / n;
630 // todo: split, delay
632 if (now > Y2K) {
633 if (get_wan_proto() == WP_DISABLED) {
634 if ((nvram_get_int("wan_islan") == 0) || (!nvram_match("wan_ifnameX", ifname))) continue;
636 else {
637 if (!nvram_match("wan_iface", ifname)) continue;
640 tms = localtime(&now);
641 bump(history.daily, &history.dailyp, MAX_NDAILY,
642 (tms->tm_year << 16) | ((uint32_t)tms->tm_mon << 8) | tms->tm_mday, counter);
644 n = nvram_get_int("rstats_offset");
645 if ((n < 1) || (n > 31)) n = 1;
646 mon = now + ((1 - n) * (60 * 60 * 24));
647 tms = localtime(&mon);
648 bump(history.monthly, &history.monthlyp, MAX_NMONTHLY,
649 (tms->tm_year << 16) | ((uint32_t)tms->tm_mon << 8), counter);
652 fclose(f);
654 // cleanup stale entries
655 for (i = 0; i < speed_count; ++i) {
656 sp = &speed[i];
657 if (sp->sync == -1) {
658 sp->sync = 0;
659 continue;
661 if (((uptime - sp->utime) > (10 * SMIN)) || (find_word(exclude, sp->ifname))) {
662 _dprintf("%s: #%d removing. > time limit or excluded\n", __FUNCTION__, i);
663 --speed_count;
664 memcpy(sp, sp + 1, (speed_count - i) * sizeof(speed[0]));
666 else {
667 _dprintf("%s: %s not found setting sync=1\n", __FUNCTION__, sp->ifname, i);
668 sp->sync = 1;
672 // todo: total > user
673 if (uptime >= save_utime) {
674 save(0);
675 save_utime = uptime + get_stime();
676 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__, uptime / 60, save_utime / 60);
681 static void sig_handler(int sig)
683 switch (sig) {
684 case SIGTERM:
685 case SIGINT:
686 gotterm = 1;
687 break;
688 case SIGHUP:
689 gothup = 1;
690 break;
691 case SIGUSR1:
692 gotuser = 1;
693 break;
694 case SIGUSR2:
695 gotuser = 2;
696 break;
700 int main(int argc, char *argv[])
702 struct sigaction sa;
703 long z;
704 int new;
706 printf("rstats\nCopyright (C) 2006-2009 Jonathan Zarate\n\n");
708 if (fork() != 0) return 0;
710 openlog("rstats", LOG_PID, LOG_USER);
712 new = 0;
713 if (argc > 1) {
714 if (strcmp(argv[1], "--new") == 0) {
715 new = 1;
716 _dprintf("new=1\n");
720 clear_history();
721 unlink("/var/tmp/rstats-load");
723 sa.sa_handler = sig_handler;
724 sa.sa_flags = 0;
725 sigemptyset(&sa.sa_mask);
726 sigaction(SIGUSR1, &sa, NULL);
727 sigaction(SIGUSR2, &sa, NULL);
728 sigaction(SIGHUP, &sa, NULL);
729 sigaction(SIGTERM, &sa, NULL);
730 sigaction(SIGINT, &sa, NULL);
732 load(new);
734 z = uptime = get_uptime();
735 while (1) {
736 while (uptime < z) {
737 sleep(z - uptime);
738 if (gothup) {
739 if (unlink("/var/tmp/rstats-load") == 0) load_new();
740 else save(0);
741 gothup = 0;
743 if (gotterm) {
744 save(!nvram_match("rstats_sshut", "1"));
745 exit(0);
747 if (gotuser == 1) {
748 save_speedjs(z - get_uptime());
749 gotuser = 0;
751 else if (gotuser == 2) {
752 save_histjs();
753 gotuser = 0;
755 uptime = get_uptime();
757 calc();
758 z += INTERVAL;
761 return 0;