4 Copyright (C) 2011 Augusto Bott
7 Copyright (C) 2006-2009 Jonathan Zarate
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
28 #include <sys/types.h>
29 #include <sys/sysinfo.h>
39 // #define DEBUG_NOISY
40 // #define DEBUG_STIME
44 // #define _dprintf(args...) cprintf(args)
45 // #define _dprintf(args...) printf(args)
47 // #define _dprintf(args...) do { } while (0)
52 #define M (1024 * 1024)
53 #define G (1024 * 1024 * 1024)
56 #define SHOUR (60 * 60)
57 #define SDAY (60 * 60 * 24)
58 #define Y2K 946684800UL
62 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
64 #define MAX_NMONTHLY 25
65 #define MAX_SPEED_IP 64
66 #define MAX_ROLLOVER (225 * M)
75 #define ID_V0 0x30305352
76 #define ID_V1 0x31305352
77 #define ID_V2 0x32305352
78 #define CURRENT_ID ID_V2
83 uint64_t counter
[MAX_COUNTER
];
89 data_t daily
[MAX_SPEED_IP
][MAX_NDAILY
];
90 int dailyp
[MAX_SPEED_IP
];
92 data_t monthly
[MAX_SPEED_IP
][MAX_NMONTHLY
];
93 int monthlyp
[MAX_SPEED_IP
];
95 char ipaddr
[MAX_SPEED_IP
][INET_ADDRSTRLEN
];
104 data_t daily
[MAX_NDAILY
];
107 data_t monthly
[MAX_NMONTHLY
];
123 char ipaddr
[INET_ADDRSTRLEN
];
125 unsigned long speed
[MAX_NSPEED
][MAX_COUNTER
];
126 unsigned long last
[MAX_COUNTER
];
132 speed_t speed
[MAX_SPEED_IP
];
138 volatile int gothup
= 0;
139 volatile int gotuser
= 0;
140 volatile int gotterm
= 0;
142 const char history_fn
[] = "/var/lib/misc/cstats-history";
143 const char speed_fn
[] = "/var/lib/misc/cstats-speed";
144 const char uncomp_fn
[] = "/var/tmp/cstats-uncomp";
145 const char source_fn
[] = "/var/lib/misc/cstats-source";
148 static int get_stime(void)
154 t
= nvram_get_int("cstats_stime");
156 else if (t
> 8760) t
= 8760;
161 static int comp(const char *path
, void *buffer
, int size
)
165 if (f_write(path
, buffer
, size
, 0, 0) != size
) return 0;
167 sprintf(s
, "%s.gz", path
);
170 sprintf(s
, "gzip %s", path
);
171 return system(s
) == 0;
174 static void save(int quick
)
184 static int lastbak
= -1;
186 _dprintf("%s: quick=%d\n", __FUNCTION__
, quick
);
188 f_write("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
), 0, 0);
190 comp(speed_fn
, speed
, sizeof(speed
[0]) * speed_count
);
193 if ((now = time(0)) < Y2K) {
194 _dprintf("%s: time not set\n", __FUNCTION__);
199 comp(history_fn
, &history
, sizeof(history
));
201 _dprintf("%s: write source=%s\n", __FUNCTION__
, save_path
);
202 f_write_string(source_fn
, save_path
, 0, 0);
208 sprintf(hgz
, "%s.gz", history_fn
);
211 if (strcmp(save_path, "*nvram") == 0) {
212 if (!wait_action_idle(10)) {
213 _dprintf("%s: busy, not saving\n", __FUNCTION__);
217 if ((n = f_read_alloc(hgz, &bi, 20 * 1024)) > 0) {
218 if ((bo = malloc(base64_encoded_len(n) + 1)) != NULL) {
219 n = base64_encode(bi, bo, n);
221 nvram_set("cstats_data", bo);
222 if (!nvram_match("debug_nocommit", "1")) nvram_commit();
224 _dprintf("%s: nvram commit\n", __FUNCTION__);
231 else if (save_path[0] != 0) {
233 if (save_path
[0] != 0) {
234 strcpy(tmp
, save_path
);
237 for (i
= 15; i
> 0; --i
) {
238 if (!wait_action_idle(10)) {
239 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
242 _dprintf("%s: cp %s %s\n", __FUNCTION__
, hgz
, tmp
);
243 if (eval("cp", hgz
, tmp
) == 0) {
244 _dprintf("%s: copy ok\n", __FUNCTION__
);
246 if (!nvram_match("cstats_bak", "0")) {
248 tms
= localtime(&now
);
249 if (lastbak
!= tms
->tm_yday
) {
250 strcpy(bak
, save_path
);
252 if ((n
> 3) && (strcmp(bak
+ (n
- 3), ".gz") == 0)) n
-= 3;
253 sprintf(bak
+ n
, "_%d.bak", ((tms
->tm_yday
/ 7) % 3) + 1);
254 if (eval("cp", save_path
, bak
) == 0) lastbak
= tms
->tm_yday
;
258 _dprintf("%s: rename %s %s\n", __FUNCTION__
, tmp
, save_path
);
259 if (rename(tmp
, save_path
) == 0) {
260 _dprintf("%s: rename ok\n", __FUNCTION__
);
266 // might not be ready
274 static int decomp(const char *fname
, void *buffer
, int size
, int max
)
279 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
284 sprintf(s
, "gzip -dc %s > %s", fname
, uncomp_fn
);
285 if (system(s
) == 0) {
286 n
= f_read(uncomp_fn
, buffer
, size
* max
);
287 _dprintf("%s: n=%d\n", __FUNCTION__
, n
);
292 _dprintf("%s: %s != 0\n", __FUNCTION__
, s
);
295 memset((char *)buffer
+ (size
* n
), 0, (max
- n
) * size
);
300 static void clear_history(void)
302 memset(&history
, 0, sizeof(history
));
303 history
.id
= CURRENT_ID
;
305 _dprintf("%s: sizeof(history)= %d, CURRENT_ID= %d...\n", __FUNCTION__
, sizeof(history
), CURRENT_ID
);
310 static int load_history(const char *fname
)
314 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
316 if ((decomp(fname
, &hist
, sizeof(hist
), 1) != 1) || (hist
.id
!= CURRENT_ID
)) {
320 if ((decomp(fname, &v0, sizeof(v0), 1) != 1) || (v0.id != ID_V0)) {
321 _dprintf("%s: load failed\n", __FUNCTION__);
325 // --- temp conversion ---
329 history.id = CURRENT_ID;
330 memcpy(history.daily, v0.daily, sizeof(history.daily));
331 history.dailyp = v0.dailyp;
332 memcpy(history.monthly, v0.monthly, sizeof(v0.monthly)); // v0 is just shorter
333 history.monthlyp = v0.monthlyp;
338 memcpy(&history
, &hist
, sizeof(history
));
340 // _dprintf("%s: howmany=%d dailyp=%d monthlyp=%d\n", __FUNCTION__, history.howmany, history.dailyp, history.monthlyp);
344 static void load_new(void)
348 sprintf(hgz
, "%s.gz.new", history_fn
);
349 if (load_history(hgz
)) save(0);
353 static void load(int new)
360 char sp
[sizeof(save_path
)];
361 unsigned char mac
[6];
363 uptime
= get_uptime();
365 _dprintf("%s: new=%d, uptime=%lu\n", __FUNCTION__
, new, uptime
);
367 strlcpy(save_path
, nvram_safe_get("cstats_path"), sizeof(save_path
) - 32);
368 if (((n
= strlen(save_path
)) > 0) && (save_path
[n
- 1] == '/')) {
369 ether_atoe(nvram_safe_get("et0macaddr"), mac
);
370 sprintf(save_path
+ n
, "tomato_cstats_%02x%02x%02x%02x%02x%02x.gz",
371 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
374 if (f_read("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
)) != sizeof(save_utime
)) {
377 t
= uptime
+ get_stime();
378 if ((save_utime
< uptime
) || (save_utime
> t
)) save_utime
= t
;
379 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
383 sprintf(hgz
, "%s.gz", speed_fn
);
384 speed_count
= decomp(hgz
, speed
, sizeof(speed
[0]), MAX_SPEED_IP
);
385 _dprintf("%s: speed_count = %d\n", __FUNCTION__
, speed_count
);
387 for (i
= 0; i
< speed_count
; ++i
) {
388 if (speed
[i
].utime
> uptime
) {
389 speed
[i
].utime
= uptime
;
396 sprintf(hgz
, "%s.gz", history_fn
);
404 f_read_string(source_fn
, sp
, sizeof(sp
)); // always terminated
405 _dprintf("%s: read source=%s save_path=%s\n", __FUNCTION__
, sp
, save_path
);
406 if ((strcmp(sp
, save_path
) == 0) && (load_history(hgz
))) {
407 _dprintf("%s: using local file\n", __FUNCTION__
);
411 if (save_path
[0] != 0) {
413 if (strcmp(save_path, "*nvram") == 0) {
414 if (!wait_action_idle(60)) exit(0);
416 bi = nvram_safe_get("cstats_data");
417 if ((n = strlen(bi)) > 0) {
418 if ((bo = malloc(base64_decoded_len(n))) != NULL) {
419 n = base64_decode(bi, bo, n);
420 _dprintf("%s: nvram n=%d\n", __FUNCTION__, n);
421 f_write(hgz, bo, n, 0, 0);
432 if (wait_action_idle(10)) {
434 // cifs quirk: try forcing refresh
435 eval("ls", save_path
);
437 if (load_history(save_path
)) {
438 f_write_string(source_fn
, save_path
, 0, 0);
445 if ((i
*= 2) > 900) i
= 900; // 15m
453 syslog(LOG_WARNING
, "Problem loading %s. Still trying...", save_path
);
461 static void save_speedjs(long next
)
472 if ((f
= fopen("/var/tmp/cstats-speed.js", "w")) == NULL
) return;
474 _dprintf("%s: speed_count = %d\n", __FUNCTION__
, speed_count
);
476 fprintf(f
, "\nspeed_history = {\n");
478 for (i
= 0; i
< speed_count
; ++i
) {
480 fprintf(f
, "%s'%s': {\n", i
? " },\n" : "", sp
->ipaddr
);
481 for (j
= 0; j
< MAX_COUNTER
; ++j
) {
483 fprintf(f
, "%sx: [", j
? ",\n t" : " r");
485 for (k
= 0; k
< MAX_NSPEED
; ++k
) {
486 p
= (p
+ 1) % MAX_NSPEED
;
488 fprintf(f
, "%s%lu", k
? "," : "", n
);
490 if (n
> tmax
) tmax
= n
;
495 fprintf(f
, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
496 c
, total
/ MAX_NSPEED
, c
, tmax
, c
, total
);
499 fprintf(f
, "%s_next: %ld};\n", speed_count
? "},\n" : "", ((next
>= 1) ? next
: 1));
502 rename("/var/tmp/cstats-speed.js", "/var/spool/cstats-speed.js");
506 static void save_datajs(FILE *f
, int mode
)
513 fprintf(f
, "\n%s_history = [\n", (mode
== DAILY
) ? "daily" : "monthly");
515 _dprintf("%s: history.howmany=%d \n", __FUNCTION__
, history
.howmany
);
518 for (i
= 0 ; i
< history
.howmany
; i
++) {
519 // _dprintf("%s: history.howmany=%d \n", __FUNCTION__, history.howmany);
521 data
= history
.daily
[i
];
522 p
= history
.dailyp
[i
];
527 data
= history
.monthly
[i
];
528 p
= history
.monthlyp
[i
];
532 _dprintf("%s: ipaddr=%s i=%d p=%d max=%d\n", __FUNCTION__
, history
.ipaddr
[i
], i
, p
, max
);
534 for (k
= max
; k
> 0; --k
) {
536 if (data
[p
].xtime
== 0) continue;
537 // fprintf(f, "%s['%s',0x%lx,0x%llx,0x%llx]", kn ? "," : "", history.ipaddr[i],
538 // (unsigned long)data[p].xtime, data[p].counter[0] / K, data[p].counter[1] / K);
539 fprintf(f
, "%s[0x%lx,'%s',%llu,%llu]", kn
? "," : "",
540 (unsigned long)data
[p
].xtime
, history
.ipaddr
[i
], data
[p
].counter
[0] / K
, data
[p
].counter
[1] / K
);
547 static void save_histjs(void)
551 if ((f
= fopen("/var/tmp/cstats-history.js", "w")) != NULL
) {
552 save_datajs(f
, DAILY
);
553 save_datajs(f
, MONTHLY
);
555 rename("/var/tmp/cstats-history.js", "/var/spool/cstats-history.js");
560 static void bump(data_t
*data
, int *tail
, int max
, uint32_t xnow
, unsigned long *counter
)
566 // _dprintf("%s: counter[0]=%llu counter[1]=%llu tail=%d\n", __FUNCTION__, data[t].counter[0], data[t].counter[1], *tail);
567 // _dprintf("%s: counter[0]=%lu counter[1]=%lu tail=%d max=%d xnow=%d\n", __FUNCTION__, counter[0], counter[1], *tail, max, xnow);
569 if (data
[t
].xtime
!= xnow
) {
570 for (i
= max
- 1; i
>= 0; --i
) {
571 if (data
[i
].xtime
== xnow
) {
577 *tail
= t
= (t
+ 1) % max
;
578 data
[t
].xtime
= xnow
;
579 memset(data
[t
].counter
, 0, sizeof(data
[0].counter
));
583 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
584 data
[t
].counter
[i
] += counter
[i
];
587 // _dprintf("%s: counter[0]=%llu counter[1]=%llu tail=%d\n", __FUNCTION__, data[t].counter[0], data[t].counter[1], *tail);
591 static void calc(void)
597 unsigned long counter
[MAX_COUNTER
];
613 exclude
= nvram_safe_get("cstats_exclude");
614 include
= nvram_safe_get("cstats_include");
616 _dprintf("%s: cstats_exclude='%s'\n", __FUNCTION__
, exclude
);
620 char ip
[INET_ADDRSTRLEN
];
623 char name
[] = "/proc/net/ipt_account/lanX";
625 for(br
=0 ; br
<=3 ; br
++) {
627 char wholenetstatsline
= 1;
629 char bridge
[2] = "0";
635 sprintf(name
, "/proc/net/ipt_account/lan%s", bridge
);
637 if ((f
= fopen(name
, "r")) == NULL
) continue;
639 // _dprintf("%s: file %s opened\n", __FUNCTION__, name);
640 // if (!wholenetstatsline)
641 // fgets(buf, sizeof(buf), f); // network
643 while (fgets(buf
, sizeof(buf
), f
)) {
644 // _dprintf("%s: read\n", __FUNCTION__);
647 "ip = %s bytes_src = %lu %*u %*u %*u %*u packets_src = %*u %*u %*u %*u %*u bytes_dst = %lu %*u %*u %*u %*u packets_dst = %*u %*u %*u %*u %*u time = %*u",
649 "ip = %s bytes_src = %lu %*u %*u %*u %*u packets_src = %*u %*u %*u %*u %*u bytes_dest = %lu %*u %*u %*u %*u packets_dest = %*u %*u %*u %*u %*u time = %*u",
651 ip
, &rx
, &tx
) != 3 ) continue;
652 // _dprintf("%s: %s tx=%lu rx=%lu\n", __FUNCTION__, ip, tx, rx);
654 // if ((tx < 1) || (rx < 1)) continue;
660 // _dprintf("%s: %s tx=%lu rx=%lu\n", __FUNCTION__, ipaddr, tx, rx);
662 if (find_word(exclude
, ipaddr
)) {
663 wholenetstatsline
= 0;
667 // if ((counter[0] < 1) || (counter[1] < 1)) continue;
670 if ((f = fopen("/proc/net/dev", "r")) == NULL) return;
671 fgets(buf, sizeof(buf), f); // header
672 fgets(buf, sizeof(buf), f); // "
673 while (fgets(buf, sizeof(buf), f)) {
674 if ((p = strchr(buf, ':')) == NULL) continue;
676 if ((ipaddr = strrchr(buf, ' ')) == NULL) ipaddr = buf;
678 if ((strcmp(ipaddr, "lo") == 0) || (find_word(exclude, ipaddr))) continue;
680 // <rx bytes, packets, errors, dropped, fifo errors, frame errors, compressed, multicast><tx ...>
681 if (sscanf(p + 1, "%lu%*u%*u%*u%*u%*u%*u%*u%lu", &counter[0], &counter[1]) != 2) continue;
684 // if (find_word(include, ip)) {
685 if ((find_word(include
, ip
)) || (wholenetstatsline
== 1)) {
687 wholenetstatsline
= 0;
690 for (i
= speed_count
; i
> 0; --i
) {
691 if (strcmp(sp
->ipaddr
, ipaddr
) == 0) break;
695 if (speed_count
>= MAX_SPEED_IP
) continue;
697 _dprintf("%s: add %s as #%d\n", __FUNCTION__
, ipaddr
, speed_count
);
701 memset(sp
, 0, sizeof(*sp
));
702 strcpy(sp
->ipaddr
, ipaddr
);
707 _dprintf("%s: sync %s\n", __FUNCTION__
, ipaddr
);
710 memcpy(sp
->last
, counter
, sizeof(sp
->last
));
711 memset(counter
, 0, sizeof(counter
));
716 tick
= uptime
- sp
->utime
;
719 _dprintf("%s: %s is a little early... %lu < %d\n", __FUNCTION__
, ipaddr
, tick
, INTERVAL
);
723 sp
->utime
+= (n
* INTERVAL
);
724 _dprintf("%s: %s n=%d tick=%lu\n", __FUNCTION__
, ipaddr
, n
, tick
);
726 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
730 diff
= (0xFFFFFFFF - sc
) + c
;
731 if (diff
> MAX_ROLLOVER
) diff
= 0;
740 for (j
= 0; j
< n
; ++j
) {
741 sp
->tail
= (sp
->tail
+ 1) % MAX_NSPEED
;
742 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
743 sp
->speed
[sp
->tail
][i
] = counter
[i
] / n
;
748 // todo: split, delay
750 if (now
> Y2K
) { /* Skip this if the time&date is not set yet */
752 for (i
= history
.howmany
; i
> 0; --i
) {
753 // _dprintf("%s: i=%d '%s' '%s'\n", __FUNCTION__, i, history.ipaddr[i], ipaddr);
754 if (strcmp(history
.ipaddr
[i
], ipaddr
) == 0) break;
757 if (history
.howmany
>= MAX_SPEED_IP
) continue;
758 if (strcmp(history
.ipaddr
[i
], ipaddr
) != 0) {
759 strncpy(history
.ipaddr
[history
.howmany
], ipaddr
, INET_ADDRSTRLEN
);
760 _dprintf("%s: history add %s/%s as #%d\n", __FUNCTION__
, ipaddr
, history
.ipaddr
[history
.howmany
], history
.howmany
);
766 // _dprintf("%s: calling bump i=%d %s total #%d history.dailyp[i]=%d\n", __FUNCTION__, i, ipaddr, history.howmany, history.dailyp[i]);
767 tms
= localtime(&now
);
768 bump(history
.daily
[i
], &history
.dailyp
[i
], MAX_NDAILY
,
769 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8) | tms
->tm_mday
, counter
);
771 // _dprintf("%s: calling bump i=%d %s total #%d history.monthlyp=%d\n", __FUNCTION__, i, ipaddr, history.howmany, history.monthlyp);
772 n
= nvram_get_int("cstats_offset");
773 if ((n
< 1) || (n
> 31)) n
= 1;
774 mon
= now
+ ((1 - n
) * (60 * 60 * 24));
775 tms
= localtime(&mon
);
776 bump(history
.monthly
[i
], &history
.monthlyp
[i
], MAX_NMONTHLY
,
777 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8), counter
);
784 // cleanup stale entries
785 for (i
= 0; i
< speed_count
; ++i
) {
787 if (sp
->sync
== -1) {
791 if (((uptime
- sp
->utime
) > (10 * SMIN
)) || (find_word(exclude
, sp
->ipaddr
))) {
792 // if ((uptime - sp->utime) > (10 * SMIN)) {
793 _dprintf("%s: #%d removing. > time limit or excluded\n", __FUNCTION__
, i
);
795 memcpy(sp
, sp
+ 1, (speed_count
- i
) * sizeof(speed
[0]));
798 _dprintf("%s: %s not found setting sync=1, %d\n", __FUNCTION__
, sp
->ipaddr
, i
);
803 for (i
= 0; i
< history
.howmany
; ++i
) {
804 if ((find_word(exclude
, history
.ipaddr
[i
]))) {
806 _dprintf("%s: #%d removing %s > excluded\n", __FUNCTION__
, i
, history
.ipaddr
[i
]);
807 memcpy(history
.ipaddr
[i
], history
.ipaddr
[i
+1], (history
.howmany
- i
) * sizeof(history
.ipaddr
[0]));
808 memcpy(history
.daily
[i
], history
.daily
[i
+1], (history
.howmany
- i
) * sizeof(history
.daily
[0]));
809 history
.dailyp
[i
] = history
.dailyp
[i
+1];
810 memcpy(history
.monthly
[i
], history
.monthly
[i
+1], (history
.howmany
- i
) * sizeof(history
.monthly
[0]));
811 history
.monthlyp
[i
] = history
.monthlyp
[i
+1];
815 // todo: total > user
816 if (uptime
>= save_utime
) {
818 save_utime
= uptime
+ get_stime();
819 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
823 static void sig_handler(int sig
) {
841 int main(int argc
, char *argv
[]) {
846 printf("cstats - Copyright (C) 2011 Augusto Bott\n");
847 printf("based on rstats - Copyright (C) 2006-2009 Jonathan Zarate\n\n");
849 if (fork() != 0) return 0;
851 openlog("cstats", LOG_PID
, LOG_USER
);
855 if (strcmp(argv
[1], "--new") == 0) {
862 unlink("/var/tmp/cstats-load");
864 sa
.sa_handler
= sig_handler
;
866 sigemptyset(&sa
.sa_mask
);
867 sigaction(SIGUSR1
, &sa
, NULL
);
868 sigaction(SIGUSR2
, &sa
, NULL
);
869 sigaction(SIGHUP
, &sa
, NULL
);
870 sigaction(SIGTERM
, &sa
, NULL
);
871 sigaction(SIGINT
, &sa
, NULL
);
875 z
= uptime
= get_uptime();
880 if (unlink("/var/tmp/cstats-load") == 0) load_new();
885 save(!nvram_match("cstats_sshut", "1"));
889 save_speedjs(z
- get_uptime());
892 else if (gotuser
== 2) {
896 uptime
= get_uptime();