revert recent changes to rstats and cstats
[tomato.git] / release / src / router / rstats / rstats.c
blob916d62d782de5290b6db955ea91688f69f8ad8ca
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 <sys/ioctl.h>
29 #include <stdint.h>
30 #include <syslog.h>
32 #include <bcmnvram.h>
33 #include <shutils.h>
36 //#define DEBUG_NOISY
37 //#define DEBUG_STIME
40 #include <shared.h>
42 #define K 1024
43 #define M (1024 * 1024)
44 #define G (1024 * 1024 * 1024)
46 #define SMIN 60
47 #define SHOUR (60 * 60)
48 #define SDAY (60 * 60 * 24)
49 #define Y2K 946684800UL
51 #define INTERVAL 120
53 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
54 #define MAX_NDAILY 62
55 #define MAX_NMONTHLY 25
56 #define MAX_SPEED_IF 10
57 #define MAX_ROLLOVER (225 * M)
59 #define MAX_COUNTER 2
60 #define RX 0
61 #define TX 1
63 #define DAILY 0
64 #define MONTHLY 1
66 #define ID_V0 0x30305352
67 #define ID_V1 0x31305352
68 #define CURRENT_ID ID_V1
70 #define HI_BACK 5
72 typedef struct {
73 uint32_t xtime;
74 uint64_t counter[MAX_COUNTER];
75 } data_t;
77 typedef struct {
78 uint32_t id;
80 data_t daily[MAX_NDAILY];
81 int dailyp;
83 data_t monthly[MAX_NMONTHLY];
84 int monthlyp;
85 } history_t;
87 typedef struct {
88 uint32_t id;
90 data_t daily[62];
91 int dailyp;
93 data_t monthly[12];
94 int monthlyp;
95 } history_v0_t;
97 typedef struct {
98 char ifname[12];
99 long utime;
100 unsigned long speed[MAX_NSPEED][MAX_COUNTER];
101 unsigned long last[MAX_COUNTER];
102 int tail;
103 char sync;
104 } speed_t;
106 history_t history;
107 speed_t speed[MAX_SPEED_IF];
108 int speed_count;
109 long save_utime;
110 char save_path[96];
111 long uptime;
113 volatile int gothup = 0;
114 volatile int gotuser = 0;
115 volatile int gotterm = 0;
117 const char history_fn[] = "/var/lib/misc/rstats-history";
118 const char speed_fn[] = "/var/lib/misc/rstats-speed";
119 const char uncomp_fn[] = "/var/tmp/rstats-uncomp";
120 const char source_fn[] = "/var/lib/misc/rstats-source";
123 static int get_stime(void)
125 #ifdef DEBUG_STIME
126 return 90;
127 #else
128 int t;
129 t = nvram_get_int("rstats_stime");
130 if (t < 1) t = 1;
131 else if (t > 8760) t = 8760;
132 return t * SHOUR;
133 #endif
136 static int comp(const char *path, void *buffer, int size)
138 char s[256];
140 if (f_write(path, buffer, size, 0, 0) != size) return 0;
142 sprintf(s, "%s.gz", path);
143 unlink(s);
145 sprintf(s, "gzip %s", path);
146 return system(s) == 0;
149 static void save(int quick)
151 int i;
152 char *bi, *bo;
153 int n;
154 int b;
155 char hgz[256];
156 char tmp[256];
157 char bak[256];
158 char bkp[256];
159 time_t now;
160 struct tm *tms;
161 static int lastbak = -1;
163 _dprintf("%s: quick=%d\n", __FUNCTION__, quick);
165 f_write("/var/lib/misc/rstats-stime", &save_utime, sizeof(save_utime), 0, 0);
167 comp(speed_fn, speed, sizeof(speed[0]) * speed_count);
170 if ((now = time(0)) < Y2K) {
171 _dprintf("%s: time not set\n", __FUNCTION__);
172 return;
176 comp(history_fn, &history, sizeof(history));
178 _dprintf("%s: write source=%s\n", __FUNCTION__, save_path);
179 f_write_string(source_fn, save_path, 0, 0);
181 if (quick) {
182 return;
185 sprintf(hgz, "%s.gz", history_fn);
187 if (strcmp(save_path, "*nvram") == 0) {
188 if (!wait_action_idle(10)) {
189 _dprintf("%s: busy, not saving\n", __FUNCTION__);
190 return;
193 if ((n = f_read_alloc(hgz, &bi, 20 * 1024)) > 0) {
194 if ((bo = malloc(base64_encoded_len(n) + 1)) != NULL) {
195 n = base64_encode(bi, bo, n);
196 bo[n] = 0;
197 nvram_set("rstats_data", bo);
198 if (!nvram_match("debug_nocommit", "1")) nvram_commit();
200 _dprintf("%s: nvram commit\n", __FUNCTION__);
202 free(bo);
205 free(bi);
207 else if (save_path[0] != 0) {
208 strcpy(tmp, save_path);
209 strcat(tmp, ".tmp");
211 for (i = 15; i > 0; --i) {
212 if (!wait_action_idle(10)) {
213 _dprintf("%s: busy, not saving\n", __FUNCTION__);
215 else {
216 _dprintf("%s: cp %s %s\n", __FUNCTION__, hgz, tmp);
217 if (eval("cp", hgz, tmp) == 0) {
218 _dprintf("%s: copy ok\n", __FUNCTION__);
220 if (!nvram_match("rstats_bak", "0")) {
221 now = time(0);
222 tms = localtime(&now);
223 if (lastbak != tms->tm_yday) {
224 strcpy(bak, save_path);
225 n = strlen(bak);
226 if ((n > 3) && (strcmp(bak + (n - 3), ".gz") == 0)) n -= 3;
227 strcpy(bkp, bak);
228 for (b = HI_BACK-1; b > 0; --b) {
229 sprintf(bkp + n, "_%d.bak", b + 1);
230 sprintf(bak + n, "_%d.bak", b);
231 rename(bak, bkp);
233 if (eval("cp", "-p", save_path, bak) == 0) lastbak = tms->tm_yday;
237 _dprintf("%s: rename %s %s\n", __FUNCTION__, tmp, save_path);
238 if (rename(tmp, save_path) == 0) {
239 _dprintf("%s: rename ok\n", __FUNCTION__);
240 break;
245 // might not be ready
246 sleep(3);
247 if (gotterm) break;
252 static int decomp(const char *fname, void *buffer, int size, int max)
254 char s[256];
255 int n;
257 _dprintf("%s: fname=%s\n", __FUNCTION__, fname);
259 unlink(uncomp_fn);
261 n = 0;
262 sprintf(s, "gzip -dc %s > %s", fname, uncomp_fn);
263 if (system(s) == 0) {
264 n = f_read(uncomp_fn, buffer, size * max);
265 _dprintf("%s: n=%d\n", __FUNCTION__, n);
266 if (n <= 0) n = 0;
267 else n = n / size;
269 else {
270 _dprintf("%s: %s != 0\n", __FUNCTION__, s);
272 unlink(uncomp_fn);
273 memset((char *)buffer + (size * n), 0, (max - n) * size);
274 return n;
277 static void clear_history(void)
279 memset(&history, 0, sizeof(history));
280 history.id = CURRENT_ID;
283 static int load_history(const char *fname)
285 history_t hist;
287 _dprintf("%s: fname=%s\n", __FUNCTION__, fname);
289 if ((decomp(fname, &hist, sizeof(hist), 1) != 1) || (hist.id != CURRENT_ID)) {
290 history_v0_t v0;
292 if ((decomp(fname, &v0, sizeof(v0), 1) != 1) || (v0.id != ID_V0)) {
293 _dprintf("%s: load failed\n", __FUNCTION__);
294 return 0;
296 else {
297 // --- temp conversion ---
298 clear_history();
300 // V0 -> V1
301 history.id = CURRENT_ID;
302 memcpy(history.daily, v0.daily, sizeof(history.daily));
303 history.dailyp = v0.dailyp;
304 memcpy(history.monthly, v0.monthly, sizeof(v0.monthly)); // v0 is just shorter
305 history.monthlyp = v0.monthlyp;
308 else {
309 memcpy(&history, &hist, sizeof(history));
312 _dprintf("%s: dailyp=%d monthlyp=%d\n", __FUNCTION__, history.dailyp, history.monthlyp);
313 return 1;
316 /* Try loading from the backup versions.
317 * We'll try from oldest to newest, then
318 * retry the requested one again last. In case the drive mounts while
319 * we are trying to find a good version.
321 static int try_hardway(const char *fname)
323 char fn[256];
324 int n, b, found = 0;
326 strcpy(fn, fname);
327 n = strlen(fn);
328 if ((n > 3) && (strcmp(fn + (n - 3), ".gz") == 0))
329 n -= 3;
330 for (b = HI_BACK; b > 0; --b) {
331 sprintf(fn + n, "_%d.bak", b);
332 found |= load_history(fn);
334 found |= load_history(fname);
336 return found;
339 static void load_new(void)
341 char hgz[256];
343 sprintf(hgz, "%s.gz.new", history_fn);
344 if (load_history(hgz)) save(0);
345 unlink(hgz);
348 static void load(int new)
350 int i;
351 long t;
352 char *bi, *bo;
353 int n;
354 char hgz[256];
355 char sp[sizeof(save_path)];
356 unsigned char mac[6];
358 uptime = get_uptime();
360 strlcpy(save_path, nvram_safe_get("rstats_path"), sizeof(save_path) - 32);
361 if (((n = strlen(save_path)) > 0) && (save_path[n - 1] == '/')) {
362 ether_atoe(nvram_safe_get("et0macaddr"), mac);
363 sprintf(save_path + n, "tomato_rstats_%02x%02x%02x%02x%02x%02x.gz",
364 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
367 if (f_read("/var/lib/misc/rstats-stime", &save_utime, sizeof(save_utime)) != sizeof(save_utime)) {
368 save_utime = 0;
370 t = uptime + get_stime();
371 if ((save_utime < uptime) || (save_utime > t)) save_utime = t;
372 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__, uptime / 60, save_utime / 60);
376 sprintf(hgz, "%s.gz", speed_fn);
377 speed_count = decomp(hgz, speed, sizeof(speed[0]), MAX_SPEED_IF);
378 _dprintf("%s: speed_count = %d\n", __FUNCTION__, speed_count);
380 for (i = 0; i < speed_count; ++i) {
381 if (speed[i].utime > uptime) {
382 speed[i].utime = uptime;
383 speed[i].sync = 1;
389 sprintf(hgz, "%s.gz", history_fn);
391 if (new) {
392 unlink(hgz);
393 save_utime = 0;
394 return;
397 f_read_string(source_fn, sp, sizeof(sp)); // always terminated
398 _dprintf("%s: read source=%s save_path=%s\n", __FUNCTION__, sp, save_path);
399 if ((strcmp(sp, save_path) == 0) && (load_history(hgz))) {
400 _dprintf("%s: using local file\n", __FUNCTION__);
401 return;
404 if (save_path[0] != 0) {
405 if (strcmp(save_path, "*nvram") == 0) {
406 if (!wait_action_idle(60)) exit(0);
408 bi = nvram_safe_get("rstats_data");
409 if ((n = strlen(bi)) > 0) {
410 if ((bo = malloc(base64_decoded_len(n))) != NULL) {
411 n = base64_decode(bi, bo, n);
412 _dprintf("%s: nvram n=%d\n", __FUNCTION__, n);
413 f_write(hgz, bo, n, 0, 0);
414 free(bo);
415 load_history(hgz);
419 else {
420 i = 1;
421 while (1) {
422 if (wait_action_idle(10)) {
424 // cifs quirk: try forcing refresh
425 eval("ls", save_path);
427 /* If we can't access the path, keep trying - maybe it isn't mounted yet.
428 * If we can, and we can sucessfully load it, oksy.
429 * If we can, and we cannot load it, then maybe it has been deleted, or
430 * maybe it's corrupted (like 0 bytes long).
431 * In these cases, try the backup files.
433 if (load_history(save_path) || try_hardway(save_path)) {
434 f_write_string(source_fn, save_path, 0, 0);
435 break;
439 // not ready...
440 sleep(i);
441 if ((i *= 2) > 900) i = 900; // 15m
443 if (gotterm) {
444 save_path[0] = 0;
445 return;
448 if (i > (3 * 60)) {
449 syslog(LOG_WARNING, "Problem loading %s. Still trying...", save_path);
456 static void save_speedjs(long next)
458 int i, j, k;
459 speed_t *sp;
460 int p;
461 FILE *f;
462 uint64_t total;
463 uint64_t tmax;
464 unsigned long n;
465 char c;
466 int up;
467 int sfd;
468 struct ifreq ifr;
470 if ((f = fopen("/var/tmp/rstats-speed.js", "w")) == NULL) return;
472 _dprintf("%s: speed_count = %d\n", __FUNCTION__, speed_count);
474 if ((sfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
475 _dprintf("[%s %d]: error opening socket %m\n", __FUNCTION__, __LINE__);
478 fprintf(f, "\nspeed_history = {\n");
480 for (i = 0; i < speed_count; ++i) {
481 sp = &speed[i];
483 up = 0;
484 if (sfd >= 0) {
485 strcpy(ifr.ifr_name, sp->ifname);
486 if (ioctl(sfd, SIOCGIFFLAGS, &ifr) == 0)
487 up = (ifr.ifr_flags & IFF_UP);
490 fprintf(f, "%s'%s': { up: %d", i ? " },\n" : "", sp->ifname, up);
492 for (j = 0; j < MAX_COUNTER; ++j) {
493 total = tmax = 0;
494 c = j ? 't' : 'r';
495 fprintf(f, ",\n %cx: [", c);
496 p = sp->tail;
497 for (k = 0; k < MAX_NSPEED; ++k) {
498 p = (p + 1) % MAX_NSPEED;
499 n = sp->speed[p][j];
500 fprintf(f, "%s%lu", k ? "," : "", n);
501 total += n;
502 if (n > tmax) tmax = n;
504 fprintf(f, "],\n");
506 fprintf(f, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
507 c, total / MAX_NSPEED, c, tmax, c, total);
510 fprintf(f, "%s_next: %ld};\n", speed_count ? "},\n" : "", ((next >= 1) ? next : 1));
512 if (sfd >= 0) close(sfd);
513 fclose(f);
515 rename("/var/tmp/rstats-speed.js", "/var/spool/rstats-speed.js");
519 static void save_datajs(FILE *f, int mode)
521 data_t *data;
522 int p;
523 int max;
524 int k, kn;
526 fprintf(f, "\n%s_history = [\n", (mode == DAILY) ? "daily" : "monthly");
528 if (mode == DAILY) {
529 data = history.daily;
530 p = history.dailyp;
531 max = MAX_NDAILY;
533 else {
534 data = history.monthly;
535 p = history.monthlyp;
536 max = MAX_NMONTHLY;
538 kn = 0;
539 for (k = max; k > 0; --k) {
540 p = (p + 1) % max;
541 if (data[p].xtime == 0) continue;
542 fprintf(f, "%s[0x%lx,0x%llx,0x%llx]", kn ? "," : "",
543 (unsigned long)data[p].xtime, data[p].counter[0] / K, data[p].counter[1] / K);
544 ++kn;
546 fprintf(f, "];\n");
549 static void save_histjs(void)
551 FILE *f;
553 if ((f = fopen("/var/tmp/rstats-history.js", "w")) != NULL) {
554 save_datajs(f, DAILY);
555 save_datajs(f, MONTHLY);
556 fclose(f);
557 rename("/var/tmp/rstats-history.js", "/var/spool/rstats-history.js");
562 static void bump(data_t *data, int *tail, int max, uint32_t xnow, unsigned long *counter)
564 int t, i;
566 t = *tail;
567 if (data[t].xtime != xnow) {
568 for (i = max - 1; i >= 0; --i) {
569 if (data[i].xtime == xnow) {
570 t = i;
571 break;
574 if (i < 0) {
575 *tail = t = (t + 1) % max;
576 data[t].xtime = xnow;
577 memset(data[t].counter, 0, sizeof(data[0].counter));
580 for (i = 0; i < MAX_COUNTER; ++i) {
581 data[t].counter[i] += counter[i];
585 static void calc(void)
587 FILE *f;
588 char buf[256];
589 char *ifname;
590 char *p;
591 unsigned long counter[MAX_COUNTER];
592 speed_t *sp;
593 int i, j;
594 time_t now;
595 time_t mon;
596 struct tm *tms;
597 uint32_t c;
598 uint32_t sc;
599 unsigned long diff;
600 long tick;
601 int n;
602 char *exclude;
604 now = time(0);
605 exclude = nvram_safe_get("rstats_exclude");
607 if ((f = fopen("/proc/net/dev", "r")) == NULL) return;
608 fgets(buf, sizeof(buf), f); // header
609 fgets(buf, sizeof(buf), f); // "
610 while (fgets(buf, sizeof(buf), f)) {
611 if ((p = strchr(buf, ':')) == NULL) continue;
612 *p = 0;
613 if ((ifname = strrchr(buf, ' ')) == NULL) ifname = buf;
614 else ++ifname;
615 if ((strcmp(ifname, "lo") == 0) || (find_word(exclude, ifname))) continue;
617 // <rx bytes, packets, errors, dropped, fifo errors, frame errors, compressed, multicast><tx ...>
618 if (sscanf(p + 1, "%lu%*u%*u%*u%*u%*u%*u%*u%lu", &counter[0], &counter[1]) != 2) continue;
620 sp = speed;
621 for (i = speed_count; i > 0; --i) {
622 if (strcmp(sp->ifname, ifname) == 0) break;
623 ++sp;
625 if (i == 0) {
626 if (speed_count >= MAX_SPEED_IF) continue;
628 _dprintf("%s: add %s as #%d\n", __FUNCTION__, ifname, speed_count);
630 i = speed_count++;
631 sp = &speed[i];
632 memset(sp, 0, sizeof(*sp));
633 strcpy(sp->ifname, ifname);
634 sp->sync = 1;
635 sp->utime = uptime;
637 if (sp->sync) {
638 _dprintf("%s: sync %s\n", __FUNCTION__, ifname);
639 sp->sync = -1;
641 memcpy(sp->last, counter, sizeof(sp->last));
642 memset(counter, 0, sizeof(counter));
644 else {
645 sp->sync = -1;
647 tick = uptime - sp->utime;
648 n = tick / INTERVAL;
649 if (n < 1) {
650 _dprintf("%s: %s is a little early... %d < %d\n", __FUNCTION__, ifname, tick, INTERVAL);
651 continue;
654 sp->utime += (n * INTERVAL);
655 _dprintf("%s: %s n=%d tick=%d\n", __FUNCTION__, ifname, n, tick);
657 for (i = 0; i < MAX_COUNTER; ++i) {
658 c = counter[i];
659 sc = sp->last[i];
660 if (c < sc) {
661 diff = (0xFFFFFFFF - sc) + c;
662 if (diff > MAX_ROLLOVER) diff = 0;
664 else {
665 diff = c - sc;
667 sp->last[i] = c;
668 counter[i] = diff;
671 for (j = 0; j < n; ++j) {
672 sp->tail = (sp->tail + 1) % MAX_NSPEED;
673 for (i = 0; i < MAX_COUNTER; ++i) {
674 sp->speed[sp->tail][i] = counter[i] / n;
679 // todo: split, delay
681 if (now > Y2K) { /* Skip this if the time&date is not set yet */
682 if (get_wan_proto() == WP_DISABLED) {
683 if ((nvram_get_int("wan_islan") == 0) || (!nvram_match("wan_ifnameX", ifname))) continue;
685 else {
686 if (!nvram_match("wan_iface", ifname)) continue;
689 tms = localtime(&now);
690 bump(history.daily, &history.dailyp, MAX_NDAILY,
691 (tms->tm_year << 16) | ((uint32_t)tms->tm_mon << 8) | tms->tm_mday, counter);
693 n = nvram_get_int("rstats_offset");
694 if ((n < 1) || (n > 31)) n = 1;
695 mon = now + ((1 - n) * (60 * 60 * 24));
696 tms = localtime(&mon);
697 bump(history.monthly, &history.monthlyp, MAX_NMONTHLY,
698 (tms->tm_year << 16) | ((uint32_t)tms->tm_mon << 8), counter);
701 fclose(f);
703 // cleanup stale entries
704 for (i = 0; i < speed_count; ++i) {
705 sp = &speed[i];
706 if (sp->sync == -1) {
707 sp->sync = 0;
708 continue;
710 if (((uptime - sp->utime) > (10 * SMIN)) || (find_word(exclude, sp->ifname))) {
711 _dprintf("%s: #%d removing. > time limit or excluded\n", __FUNCTION__, i);
712 --speed_count;
713 memcpy(sp, sp + 1, (speed_count - i) * sizeof(speed[0]));
715 else {
716 _dprintf("%s: %s not found setting sync=1\n", __FUNCTION__, sp->ifname, i);
717 sp->sync = 1;
721 // todo: total > user
722 if (uptime >= save_utime) {
723 save(0);
724 save_utime = uptime + get_stime();
725 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__, uptime / 60, save_utime / 60);
730 static void sig_handler(int sig)
732 switch (sig) {
733 case SIGTERM:
734 case SIGINT:
735 gotterm = 1;
736 break;
737 case SIGHUP:
738 gothup = 1;
739 break;
740 case SIGUSR1:
741 gotuser = 1;
742 break;
743 case SIGUSR2:
744 gotuser = 2;
745 break;
749 int main(int argc, char *argv[])
751 struct sigaction sa;
752 long z;
753 int new;
755 printf("rstats\nCopyright (C) 2006-2009 Jonathan Zarate\n\n");
757 if (fork() != 0) return 0;
759 openlog("rstats", LOG_PID, LOG_USER);
761 new = 0;
762 if (argc > 1) {
763 if (strcmp(argv[1], "--new") == 0) {
764 new = 1;
765 _dprintf("new=1\n");
769 clear_history();
770 unlink("/var/tmp/rstats-load");
772 sa.sa_handler = sig_handler;
773 sa.sa_flags = 0;
774 sigemptyset(&sa.sa_mask);
775 sigaction(SIGUSR1, &sa, NULL);
776 sigaction(SIGUSR2, &sa, NULL);
777 sigaction(SIGHUP, &sa, NULL);
778 sigaction(SIGTERM, &sa, NULL);
779 sigaction(SIGINT, &sa, NULL);
781 load(new);
783 z = uptime = get_uptime();
784 while (1) {
785 while (uptime < z) {
786 sleep(z - uptime);
787 if (gothup) {
788 if (unlink("/var/tmp/rstats-load") == 0) load_new();
789 else save(0);
790 gothup = 0;
792 if (gotterm) {
793 save(!nvram_match("rstats_sshut", "1"));
794 exit(0);
796 if (gotuser == 1) {
797 save_speedjs(z - get_uptime());
798 gotuser = 0;
800 else if (gotuser == 2) {
801 save_histjs();
802 gotuser = 0;
804 uptime = get_uptime();
806 calc();
807 z += INTERVAL;
810 return 0;