4 Copyright (C) 2011-2012 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>
46 volatile int gothup
= 0;
47 volatile int gotuser
= 0;
48 volatile int gotterm
= 0;
50 Node
*Node_new(char *ipaddr
) {
52 if ((self
= malloc(sizeof(Node
))) != NULL
) {
53 memset(self
, 0, sizeof(Node
));
54 self
->id
= CURRENT_ID
;
55 strncpy(self
->ipaddr
, ipaddr
, INET_ADDRSTRLEN
);
56 _dprintf("%s: new node ip=%s, version=%d, sizeof(Node)=%d (bytes)\n", __FUNCTION__
, self
->ipaddr
, self
->id
, sizeof(Node
));
61 int Node_compare(Node
*lhs
, Node
*rhs
) {
62 return strncmp(lhs
->ipaddr
, rhs
->ipaddr
, INET_ADDRSTRLEN
);
65 Tree tree
= TREE_INITIALIZER(Node_compare
);
68 void Node_print(Node
*self
, FILE *stream
) {
69 fprintf(stream
, "%s", self
->ipaddr
);
72 void Node_printer(Node
*self
, void *stream
) {
73 Node_print(self
, (FILE *)stream
);
74 fprintf((FILE *)stream
, " ");
77 void Tree_info(void) {
79 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_printer
, stdout
);
81 _dprintf("Tree depth = %d\n", TREE_DEPTH(&tree
, linkage
));
85 static int get_stime(void) {
90 t
= nvram_get_int("cstats_stime");
92 else if (t
> 8760) t
= 8760;
97 void Node_save(Node
*self
, void *t
) {
98 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
99 if(fwrite(self
, sizeof(Node
), 1, info
->stream
) > 0) {
104 static int save_history_from_tree(const char *fname
) {
106 node_print_mode_t info
;
110 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
113 if ((f
= fopen(uncomp_fn
, "wb")) != NULL
) {
116 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_save
, &info
);
119 sprintf(s
, "%s.gz", fname
);
122 if (rename(uncomp_fn
, fname
) == 0) {
123 sprintf(s
, "gzip %s", fname
);
130 static void save(int quick
) {
140 static int lastbak
= -1;
142 _dprintf("%s: quick=%d\n", __FUNCTION__
, quick
);
144 f_write("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
), 0, 0);
146 n
= save_history_from_tree(history_fn
);
147 _dprintf("%s: saved %d records from tree on file %s\n", __FUNCTION__
, n
, history_fn
);
149 _dprintf("%s: write source=%s\n", __FUNCTION__
, save_path
);
150 f_write_string(source_fn
, save_path
, 0, 0);
156 sprintf(hgz
, "%s.gz", history_fn
);
158 if (save_path
[0] != 0) {
159 strcpy(tmp
, save_path
);
162 for (i
= 15; i
> 0; --i
) {
163 if (!wait_action_idle(10)) {
164 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
167 _dprintf("%s: cp %s %s\n", __FUNCTION__
, hgz
, tmp
);
168 if (eval("cp", hgz
, tmp
) == 0) {
169 _dprintf("%s: copy ok\n", __FUNCTION__
);
171 if (!nvram_match("cstats_bak", "0")) {
173 tms
= localtime(&now
);
174 if (lastbak
!= tms
->tm_yday
) {
175 strcpy(bak
, save_path
);
177 if ((n
> 3) && (strcmp(bak
+ (n
- 3), ".gz") == 0)) n
-= 3;
178 // sprintf(bak + n, "_%d.bak", ((tms->tm_yday / 7) % 3) + 1);
179 // if (eval("cp", save_path, bak) == 0) lastbak = tms->tm_yday;
181 for (b
= HI_BACK
-1; b
> 0; --b
) {
182 sprintf(bkp
+ n
, "_%d.bak", b
+ 1);
183 sprintf(bak
+ n
, "_%d.bak", b
);
186 if (eval("cp", "-p", save_path
, bak
) == 0) lastbak
= tms
->tm_yday
;
190 _dprintf("%s: rename %s %s\n", __FUNCTION__
, tmp
, save_path
);
191 if (rename(tmp
, save_path
) == 0) {
192 _dprintf("%s: rename ok\n", __FUNCTION__
);
198 // might not be ready
205 static int load_history_to_tree(const char *fname
) {
213 exclude
= nvram_safe_get("cstats_exclude");
214 _dprintf("%s: cstats_exclude='%s'\n", __FUNCTION__
, exclude
);
215 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
219 sprintf(s
, "gzip -dc %s > %s", fname
, uncomp_fn
);
220 if (system(s
) == 0) {
221 if ((f
= fopen(uncomp_fn
, "rb")) != NULL
) {
223 while (fread(&tmp
, sizeof(Node
), 1, f
) > 0) {
224 if ((find_word(exclude
, tmp
.ipaddr
))) {
225 _dprintf("%s: not loading excluded ip '%s'\n", __FUNCTION__
, tmp
.ipaddr
);
229 if (tmp
.id
== CURRENT_ID
) {
230 _dprintf("%s: found data for ip %s\n", __FUNCTION__
, tmp
.ipaddr
);
232 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &tmp
);
234 _dprintf("%s: removing/reloading new data for ip %s\n", __FUNCTION__
, ptr
->ipaddr
);
235 TREE_REMOVE(&tree
, _Node
, linkage
, ptr
);
240 TREE_INSERT(&tree
, _Node
, linkage
, Node_new(tmp
.ipaddr
));
242 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &tmp
);
244 memcpy(ptr
->daily
, &tmp
.daily
, sizeof(data_t
) * MAX_NDAILY
);
245 ptr
->dailyp
= tmp
.dailyp
;
246 memcpy(ptr
->monthly
, &tmp
.monthly
, sizeof(data_t
) * MAX_NMONTHLY
);
247 ptr
->monthlyp
= tmp
.monthlyp
;
249 ptr
->utime
= tmp
.utime
;
250 memcpy(ptr
->speed
, &tmp
.speed
, sizeof(uint64_t) * MAX_NSPEED
* MAX_COUNTER
);
251 memcpy(ptr
->last
, &tmp
.last
, sizeof(uint64_t) * MAX_COUNTER
);
252 ptr
->tail
= tmp
.tail
;
253 // ptr->sync = tmp.sync;
256 if (ptr
->utime
> uptime
) {
263 _dprintf("%s: data for ip '%s' version %d not loaded (current version is %d)\n", __FUNCTION__
, tmp
.ipaddr
, tmp
.id
, CURRENT_ID
);
271 _dprintf("%s: %s != 0\n", __FUNCTION__
, s
);
275 _dprintf("%s: loaded %d records\n", __FUNCTION__
, n
);
277 printf("%s: Failed to parse the data file!\n", __FUNCTION__
);
279 printf("%s: Loaded %d records\n", __FUNCTION__
, n
);
284 static int load_history(const char *fname
) {
285 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
286 return load_history_to_tree(fname
);
289 /* Try loading from the backup versions.
290 * We'll try from oldest to newest, then
291 * retry the requested one again last. In case the drive mounts while
292 * we are trying to find a good version.
294 static int try_hardway(const char *fname
) {
300 if ((n
> 3) && (strcmp(fn
+ (n
- 3), ".gz") == 0))
302 for (b
= HI_BACK
; b
> 0; --b
) {
303 sprintf(fn
+ n
, "_%d.bak", b
);
304 found
|= load_history(fn
);
306 found
|= load_history(fname
);
311 static void load_new(void) {
314 sprintf(hgz
, "%s.gz.new", history_fn
);
315 if (load_history(hgz
) >= 0) save(0);
319 static void load(int new) {
324 unsigned char mac
[6];
326 uptime
= get_uptime();
328 _dprintf("%s: new=%d, uptime=%lu\n", __FUNCTION__
, new, uptime
);
330 strlcpy(save_path
, nvram_safe_get("cstats_path"), sizeof(save_path
) - 32);
331 if (((n
= strlen(save_path
)) > 0) && (save_path
[n
- 1] == '/')) {
332 ether_atoe(nvram_safe_get("et0macaddr"), mac
);
333 sprintf(save_path
+ n
, "tomato_cstats_%02x%02x%02x%02x%02x%02x.gz",
334 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
337 if (f_read("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
)) != sizeof(save_utime
)) {
340 t
= uptime
+ get_stime();
341 if ((save_utime
< uptime
) || (save_utime
> t
)) save_utime
= t
;
342 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
344 sprintf(hgz
, "%s.gz", history_fn
);
352 if (save_path
[0] != 0) {
355 if (wait_action_idle(10)) {
357 // cifs quirk: try forcing refresh
358 eval("ls", save_path
);
360 /* If we can't access the path, keep trying - maybe it isn't mounted yet.
361 * If we can, and we can sucessfully load it, oksy.
362 * If we can, and we cannot load it, then maybe it has been deleted, or
363 * maybe it's corrupted (like 0 bytes long).
364 * In these cases, try the backup files.
366 if ((load_history(save_path
) >= 0) || (try_hardway(save_path
) >= 0)) {
367 f_write_string(source_fn
, save_path
, 0, 0);
374 if ((i
*= 2) > 900) i
= 900; // 15m
382 syslog(LOG_WARNING
, "Problem loading %s. Still trying...", save_path
);
388 void Node_print_speedjs(Node
*self
, void *t
) {
390 uint64_t total
, tmax
;
394 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
396 fprintf(info
->stream
, "%s'%s': {\n", info
->kn
? " },\n" : "", self
->ipaddr
);
397 for (j
= 0; j
< MAX_COUNTER
; ++j
) {
399 fprintf(info
->stream
, "%sx: [", j
? ",\n t" : " r");
401 for (k
= 0; k
< MAX_NSPEED
; ++k
) {
402 p
= (p
+ 1) % MAX_NSPEED
;
403 n
= self
->speed
[p
][j
];
404 fprintf(info
->stream
, "%s%llu", k
? "," : "", n
);
406 if (n
> tmax
) tmax
= n
;
408 fprintf(info
->stream
, "],\n");
411 fprintf(info
->stream
, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
412 c
, total
/ MAX_NSPEED
, c
, tmax
, c
, total
);
417 static void save_speedjs(long next
) {
420 if ((f
= fopen("/var/tmp/cstats-speed.js", "w")) == NULL
) return;
422 node_print_mode_t info
;
427 fprintf(f
, "\nspeed_history = {\n");
428 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_print_speedjs
, &info
);
429 fprintf(f
, "%s_next: %ld};\n", info
.kn
? "},\n" : "", ((next
>= 1) ? next
: 1));
433 rename("/var/tmp/cstats-speed.js", "/var/spool/cstats-speed.js");
436 void Node_print_datajs(Node
*self
, void *t
) {
440 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
442 if (info
->mode
== DAILY
) {
449 data
= self
->monthly
;
454 for (k
= max
; k
> 0; --k
) {
456 if (data
[p
].xtime
== 0) continue;
457 fprintf(info
->stream
, "%s[0x%lx,'%s',%llu,%llu]", info
->kn
? "," : "",
458 (unsigned long)data
[p
].xtime
, self
->ipaddr
, data
[p
].counter
[0] / K
, data
[p
].counter
[1] / K
);
463 static void save_datajs(FILE *f
, int mode
) {
464 node_print_mode_t info
;
468 fprintf(f
, "\n%s_history = [\n", (mode
== DAILY
) ? "daily" : "monthly");
469 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_print_datajs
, &info
);
470 fprintf(f
, "\n];\n");
473 static void save_histjs(void) {
476 if ((f
= fopen("/var/tmp/cstats-history.js", "w")) != NULL
) {
477 save_datajs(f
, DAILY
);
478 save_datajs(f
, MONTHLY
);
480 rename("/var/tmp/cstats-history.js", "/var/spool/cstats-history.js");
484 static void bump(data_t
*data
, int *tail
, int max
, uint32_t xnow
, uint64_t *counter
) {
488 if (data
[t
].xtime
!= xnow
) {
489 for (i
= max
- 1; i
>= 0; --i
) {
490 if (data
[i
].xtime
== xnow
) {
496 *tail
= t
= (t
+ 1) % max
;
497 data
[t
].xtime
= xnow
;
498 memset(data
[t
].counter
, 0, sizeof(data
[0].counter
));
501 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
502 data
[t
].counter
[i
] += counter
[i
];
506 void Node_housekeeping(Node
*self
, void *info
) {
508 if (self
->sync
== -1) {
516 static void calc(void) {
520 uint64_t counter
[MAX_COUNTER
];
530 char *exclude
= NULL
;
531 char *include
= NULL
;
535 int wanup
= 0; // 0 = FALSE, 1 = TRUE
536 long wanuptime
= 0; // wanuptime in seconds
540 exclude
= strdup(nvram_safe_get("cstats_exclude"));
541 _dprintf("%s: cstats_exclude='%s'\n", __FUNCTION__
, exclude
);
543 include
= strdup(nvram_safe_get("cstats_include"));
544 _dprintf("%s: cstats_include='%s'\n", __FUNCTION__
, include
);
549 char ip
[INET_ADDRSTRLEN
];
552 char name
[] = "/proc/net/ipt_account/lanX";
554 for(br
=0 ; br
<=3 ; br
++) {
556 char bridge
[2] = "0";
562 sprintf(name
, "/proc/net/ipt_account/lan%s", bridge
);
564 if ((f
= fopen(name
, "r"))) {
566 while (fgets(buf
, sizeof(buf
), f
)) {
568 "ip = %s bytes_src = %llu %*u %*u %*u %*u packets_src = %*u %*u %*u %*u %*u bytes_dst = %llu %*u %*u %*u %*u packets_dst = %*u %*u %*u %*u %*u time = %*u",
569 ip
, &rx
, &tx
) != 3 ) continue;
571 _dprintf("%s: %s tx=%llu rx=%llu\n", __FUNCTION__
, ip
, tx
, rx
);
574 if (find_word(exclude
, ip
)) continue;
575 if ((tx
< 1) && (rx
< 1)) continue;
581 strncpy(test
.ipaddr
, ipaddr
, INET_ADDRSTRLEN
);
582 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
584 if ( (ptr
) || (nvram_get_int("cstats_all")) || (find_word(include
, ipaddr
)) ) {
587 _dprintf("%s: new ip: %s\n", __FUNCTION__
, ipaddr
);
588 TREE_INSERT(&tree
, _Node
, linkage
, Node_new(ipaddr
));
589 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
596 _dprintf("%s: sync[%s]=%d\n", __FUNCTION__
, ptr
->ipaddr
, ptr
->sync
);
598 _dprintf("%s: sync[%s] changed to -1\n", __FUNCTION__
, ptr
->ipaddr
);
601 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
602 _dprintf("%s: counter[%d]=%llu ptr->last[%d]=%llu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
]);
605 memcpy(ptr
->last
, counter
, sizeof(ptr
->last
));
606 memset(counter
, 0, sizeof(counter
));
607 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
608 _dprintf("%s: counter[%d]=%llu ptr->last[%d]=%llu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
]);
613 _dprintf("%s: sync[%s] = %d \n", __FUNCTION__
, ptr
->ipaddr
, ptr
->sync
);
616 _dprintf("%s: sync[%s] = %d \n", __FUNCTION__
, ptr
->ipaddr
, ptr
->sync
);
617 tick
= uptime
- ptr
->utime
;
620 _dprintf("%s: %s is a little early... %lu < %d\n", __FUNCTION__
, ipaddr
, tick
, INTERVAL
);
623 ptr
->utime
+= (n
* INTERVAL
);
624 _dprintf("%s: %s n=%d tick=%lu utime=%lu ptr->utime=%lu\n", __FUNCTION__
, ipaddr
, n
, tick
, uptime
, ptr
->utime
);
625 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
629 _dprintf("%s: counter[%d]=%llu ptr->last[%d]=%llu c=%llu sc=%llu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
], c
, sc
);
632 wanup
= check_wanup(); // router/shared/misc.c
633 wanuptime
= check_wanup_time(); // router/shared/misc.c
634 diff
= (0xFFFFFFFF - sc
+ 1) + c
;
635 if(wanup
&& (wanuptime
< (INTERVAL
+ 1))) diff
= 0;
636 // if (diff > MAX_ROLLOVER) diff = 0; // 225 Mbyte / 120 sec => 15 MBit/s only with rollover
637 // If a rollover AND a reconnect within the last 121 sec (INTERVAL + 1) happend, set diff to 0
638 // this will prevent traffic peaks, for example with ADSL/PPPoE
639 // see https://www.linksysinfo.org/index.php?threads/tomato-toastmans-releases.36106/page-39#post-281722
646 _dprintf("%s: counter[%d]=%llu ptr->last[%d]=%llu c=%llu sc=%llu diff=%llu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
], c
, sc
, diff
);
648 _dprintf("%s: ip=%s n=%d ptr->tail=%d\n", __FUNCTION__
, ptr
->ipaddr
, n
, ptr
->tail
);
649 for (j
= 0; j
< n
; ++j
) {
650 ptr
->tail
= (ptr
->tail
+ 1) % MAX_NSPEED
;
652 _dprintf("%s: ip=%s j=%d n=%d ptr->tail=%d\n", __FUNCTION__
, ptr
->ipaddr
, j
, n
, ptr
->tail
);
654 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
655 ptr
->speed
[ptr
->tail
][i
] = counter
[i
] / n
;
658 _dprintf("%s: ip=%s j=%d n=%d ptr->tail=%d\n", __FUNCTION__
, ptr
->ipaddr
, j
, n
, ptr
->tail
);
662 if (now
> Y2K
) { /* Skip this if the time&date is not set yet */
664 _dprintf("%s: calling bump %s ptr->dailyp=%d\n", __FUNCTION__
, ptr
->ipaddr
, ptr
->dailyp
);
666 tms
= localtime(&now
);
667 bump(ptr
->daily
, &ptr
->dailyp
, MAX_NDAILY
,
668 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8) | tms
->tm_mday
, counter
);
671 _dprintf("%s: calling bump %s ptr->monthlyp=%d\n", __FUNCTION__
, ptr
->ipaddr
, ptr
->monthlyp
);
673 n
= nvram_get_int("cstats_offset");
674 if ((n
< 1) || (n
> 31)) n
= 1;
675 mon
= now
+ ((1 - n
) * (60 * 60 * 24));
676 tms
= localtime(&mon
);
677 bump(ptr
->monthly
, &ptr
->monthlyp
, MAX_NMONTHLY
,
678 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8), counter
);
687 // remove/exclude history (if we still have any data previously stored)
691 while ((b
= strsep(&nvp
, ",")) != NULL
) {
692 _dprintf("%s: check exclude='%s'\n", __FUNCTION__
, b
);
693 strncpy(test
.ipaddr
, b
, INET_ADDRSTRLEN
);
694 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
696 _dprintf("%s: excluding '%s'\n", __FUNCTION__
, ptr
->ipaddr
);
697 TREE_REMOVE(&tree
, _Node
, linkage
, ptr
);
704 // cleanup remaining entries for next time
705 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_housekeeping
, NULL
);
707 // todo: total > user ???
708 if (uptime
>= save_utime
) {
710 save_utime
= uptime
+ get_stime();
711 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
714 _dprintf("%s: ====================================\n", __FUNCTION__
);
727 static void sig_handler(int sig
) {
745 int main(int argc
, char *argv
[]) {
751 printf("cstats - Copyright (C) 2011-2012 Augusto Bott\n");
752 printf("based on rstats - Copyright (C) 2006-2009 Jonathan Zarate\n\n");
754 if (fork() != 0) return 0;
756 openlog("cstats", LOG_PID
, LOG_USER
);
760 if (strcmp(argv
[1], "--new") == 0) {
766 unlink("/var/tmp/cstats-load");
768 sa
.sa_handler
= sig_handler
;
770 sigemptyset(&sa
.sa_mask
);
771 sigaction(SIGUSR1
, &sa
, NULL
);
772 sigaction(SIGUSR2
, &sa
, NULL
);
773 sigaction(SIGHUP
, &sa
, NULL
);
774 sigaction(SIGTERM
, &sa
, NULL
);
775 sigaction(SIGINT
, &sa
, NULL
);
779 z
= uptime
= get_uptime();
784 if (unlink("/var/tmp/cstats-load") == 0) load_new();
789 save(!nvram_match("cstats_sshut", "1"));
793 save_speedjs(z
- get_uptime());
796 else if (gotuser
== 2) {
800 uptime
= get_uptime();