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>
44 //#define _dprintf(args...) cprintf(args)
45 //#define _dprintf(args...) printf(args)
47 //#define _dprintf(args...) do { } while (0)
51 #define M (1024 * 1024)
52 #define G (1024 * 1024 * 1024)
55 #define SHOUR (60 * 60)
56 #define SDAY (60 * 60 * 24)
57 #define Y2K 946684800UL
61 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
63 #define MAX_NMONTHLY 25
64 //#define MAX_SPEED_IP 64
65 #define MAX_ROLLOVER (225 * M)
74 #define ID_V0 0x30305352
75 #define ID_V1 0x31305352
76 #define ID_V2 0x32305352
77 #define CURRENT_ID ID_V2
83 uint64_t counter
[MAX_COUNTER
];
90 volatile int gothup
= 0;
91 volatile int gotuser
= 0;
92 volatile int gotterm
= 0;
94 const char history_fn
[] = "/var/lib/misc/cstats-history";
95 const char uncomp_fn
[] = "/var/tmp/cstats-uncomp";
96 const char source_fn
[] = "/var/lib/misc/cstats-source";
98 typedef struct _Node
{
99 char ipaddr
[INET_ADDRSTRLEN
];
103 data_t daily
[MAX_NDAILY
];
105 data_t monthly
[MAX_NMONTHLY
];
109 unsigned long speed
[MAX_NSPEED
][MAX_COUNTER
];
110 unsigned long last
[MAX_COUNTER
];
114 TREE_ENTRY(_Node
) linkage
;
117 typedef TREE_HEAD(_Tree
, _Node
) Tree
;
119 TREE_DEFINE(_Node
, linkage
);
122 void Node_print(Node *self, FILE *stream) {
123 fprintf(stream, "%s", self->ipaddr);
126 void Node_printer(Node *self, void *stream) {
127 Node_print(self, (FILE *)stream);
128 fprintf((FILE *)stream, " ");
131 void Tree_info(void) {
133 TREE_FORWARD_APPLY(&tree, _Node, linkage, Node_printer, stdout);
135 _dprintf("Tree depth = %d\n", TREE_DEPTH(&tree, linkage));
139 Node
*Node_new(char *ipaddr
) {
141 if ((self
= malloc(sizeof(Node
))) != NULL
) {
142 memset(self
, 0, sizeof(Node
));
143 self
->id
= CURRENT_ID
;
144 strncpy(self
->ipaddr
, ipaddr
, INET_ADDRSTRLEN
);
145 _dprintf("%s: new node ip=%s, version=%d, sizeof(Node)=%d (bytes)\n", __FUNCTION__
, self
->ipaddr
, self
->id
, sizeof(Node
));
150 int Node_compare(Node
*lhs
, Node
*rhs
) {
151 return strncmp(lhs
->ipaddr
, rhs
->ipaddr
, INET_ADDRSTRLEN
);
154 Tree tree
= TREE_INITIALIZER(Node_compare
);
156 static int get_stime(void) {
161 t
= nvram_get_int("cstats_stime");
163 else if (t
> 8760) t
= 8760;
174 void Node_save(Node
*self
, void *t
) {
175 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
176 if(fwrite(self
, sizeof(Node
), 1, info
->stream
) > 0) {
181 static int save_history_from_tree(const char *fname
) {
183 node_print_mode_t info
;
187 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
190 if ((f
= fopen(uncomp_fn
, "wb")) != NULL
) {
193 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_save
, &info
);
196 sprintf(s
, "%s.gz", fname
);
199 if (rename(uncomp_fn
, fname
) == 0) {
200 sprintf(s
, "gzip %s", fname
);
208 static void save(int quick
) {
218 static int lastbak
= -1;
220 _dprintf("%s: quick=%d\n", __FUNCTION__
, quick
);
222 f_write("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
), 0, 0);
224 n
= save_history_from_tree(history_fn
);
225 _dprintf("%s: saved %d records from tree on file %s\n", __FUNCTION__
, n
, history_fn
);
227 _dprintf("%s: write source=%s\n", __FUNCTION__
, save_path
);
228 f_write_string(source_fn
, save_path
, 0, 0);
234 sprintf(hgz
, "%s.gz", history_fn
);
236 if (save_path
[0] != 0) {
237 strcpy(tmp
, save_path
);
240 for (i
= 15; i
> 0; --i
) {
241 if (!wait_action_idle(10)) {
242 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
245 _dprintf("%s: cp %s %s\n", __FUNCTION__
, hgz
, tmp
);
246 if (eval("cp", hgz
, tmp
) == 0) {
247 _dprintf("%s: copy ok\n", __FUNCTION__
);
249 if (!nvram_match("cstats_bak", "0")) {
251 tms
= localtime(&now
);
252 if (lastbak
!= tms
->tm_yday
) {
253 strcpy(bak
, save_path
);
255 if ((n
> 3) && (strcmp(bak
+ (n
- 3), ".gz") == 0)) n
-= 3;
256 // sprintf(bak + n, "_%d.bak", ((tms->tm_yday / 7) % 3) + 1);
257 // if (eval("cp", save_path, bak) == 0) lastbak = tms->tm_yday;
259 for (b
= HI_BACK
-1; b
> 0; --b
) {
260 sprintf(bkp
+ n
, "_%d.bak", b
+ 1);
261 sprintf(bak
+ n
, "_%d.bak", b
);
264 if (eval("cp", "-p", save_path
, bak
) == 0) lastbak
= tms
->tm_yday
;
268 _dprintf("%s: rename %s %s\n", __FUNCTION__
, tmp
, save_path
);
269 if (rename(tmp
, save_path
) == 0) {
270 _dprintf("%s: rename ok\n", __FUNCTION__
);
276 // might not be ready
283 static int load_history_to_tree(const char *fname
) {
291 exclude
= nvram_safe_get("cstats_exclude");
292 _dprintf("%s: cstats_exclude='%s'\n", __FUNCTION__
, exclude
);
293 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
297 sprintf(s
, "gzip -dc %s > %s", fname
, uncomp_fn
);
298 if (system(s
) == 0) {
299 if ((f
= fopen(uncomp_fn
, "rb")) != NULL
) {
300 while (fread(&tmp
, sizeof(Node
), 1, f
) > 0) {
301 if ((find_word(exclude
, tmp
.ipaddr
))) {
302 _dprintf("%s: not loading excluded ip '%s'\n", __FUNCTION__
, tmp
.ipaddr
);
306 if (tmp
.id
== CURRENT_ID
) {
307 _dprintf("%s: found data for ip %s\n", __FUNCTION__
, tmp
.ipaddr
);
309 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &tmp
);
311 _dprintf("%s: removing/reloading new data for ip %s\n", __FUNCTION__
, ptr
->ipaddr
);
312 TREE_REMOVE(&tree
, _Node
, linkage
, ptr
);
317 TREE_INSERT(&tree
, _Node
, linkage
, Node_new(tmp
.ipaddr
));
319 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &tmp
);
321 memcpy(ptr
->daily
, &tmp
.daily
, sizeof(data_t
) * MAX_NDAILY
);
322 ptr
->dailyp
= tmp
.dailyp
;
323 memcpy(ptr
->monthly
, &tmp
.monthly
, sizeof(data_t
) * MAX_NMONTHLY
);
324 ptr
->monthlyp
= tmp
.monthlyp
;
326 ptr
->utime
= tmp
.utime
;
327 memcpy(ptr
->speed
, &tmp
.speed
, sizeof(unsigned long) * MAX_NSPEED
* MAX_COUNTER
);
328 memcpy(ptr
->last
, &tmp
.last
, sizeof(unsigned long) * MAX_COUNTER
);
329 ptr
->tail
= tmp
.tail
;
330 // ptr->sync = tmp.sync;
333 if (ptr
->utime
> uptime
) {
340 _dprintf("%s: data for ip '%s' version %d not loaded (current version is %d)\n", __FUNCTION__
, tmp
.ipaddr
, tmp
.id
, CURRENT_ID
);
348 _dprintf("%s: %s != 0\n", __FUNCTION__
, s
);
352 _dprintf("%s: loaded %d records\n", __FUNCTION__
, n
);
357 static int load_history(const char *fname
) {
358 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
359 return load_history_to_tree(fname
);
362 /* Try loading from the backup versions.
363 * We'll try from oldest to newest, then
364 * retry the requested one again last. In case the drive mounts while
365 * we are trying to find a good version.
367 static int try_hardway(const char *fname
) {
373 if ((n
> 3) && (strcmp(fn
+ (n
- 3), ".gz") == 0))
375 for (b
= HI_BACK
; b
> 0; --b
) {
376 sprintf(fn
+ n
, "_%d.bak", b
);
377 found
|= load_history(fn
);
379 found
|= load_history(fname
);
384 static void load_new(void)
388 sprintf(hgz
, "%s.gz.new", history_fn
);
389 if (load_history(hgz
)) save(0);
393 static void load(int new) {
398 unsigned char mac
[6];
400 uptime
= get_uptime();
402 _dprintf("%s: new=%d, uptime=%lu\n", __FUNCTION__
, new, uptime
);
404 strlcpy(save_path
, nvram_safe_get("cstats_path"), sizeof(save_path
) - 32);
405 if (((n
= strlen(save_path
)) > 0) && (save_path
[n
- 1] == '/')) {
406 ether_atoe(nvram_safe_get("et0macaddr"), mac
);
407 sprintf(save_path
+ n
, "tomato_cstats_%02x%02x%02x%02x%02x%02x.gz",
408 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
411 if (f_read("/var/lib/misc/cstats-stime", &save_utime
, sizeof(save_utime
)) != sizeof(save_utime
)) {
414 t
= uptime
+ get_stime();
415 if ((save_utime
< uptime
) || (save_utime
> t
)) save_utime
= t
;
416 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
418 sprintf(hgz
, "%s.gz", history_fn
);
426 if (save_path
[0] != 0) {
429 if (wait_action_idle(10)) {
431 // cifs quirk: try forcing refresh
432 eval("ls", save_path
);
434 /* If we can't access the path, keep trying - maybe it isn't mounted yet.
435 * If we can, and we can sucessfully load it, oksy.
436 * If we can, and we cannot load it, then maybe it has been deleted, or
437 * maybe it's corrupted (like 0 bytes long).
438 * In these cases, try the backup files.
440 // if (load_history(save_path)) {
441 if (load_history(save_path
) || try_hardway(save_path
)) {
442 f_write_string(source_fn
, save_path
, 0, 0);
449 if ((i
*= 2) > 900) i
= 900; // 15m
457 syslog(LOG_WARNING
, "Problem loading %s. Still trying...", save_path
);
463 void Node_print_speedjs(Node
*self
, void *t
) {
465 uint64_t total
, tmax
;
469 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
471 fprintf(info
->stream
, "%s'%s': {\n", info
->kn
? " },\n" : "", self
->ipaddr
);
472 for (j
= 0; j
< MAX_COUNTER
; ++j
) {
474 fprintf(info
->stream
, "%sx: [", j
? ",\n t" : " r");
476 for (k
= 0; k
< MAX_NSPEED
; ++k
) {
477 p
= (p
+ 1) % MAX_NSPEED
;
478 n
= self
->speed
[p
][j
];
479 fprintf(info
->stream
, "%s%lu", k
? "," : "", n
);
481 if (n
> tmax
) tmax
= n
;
483 fprintf(info
->stream
, "],\n");
486 fprintf(info
->stream
, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
487 c
, total
/ MAX_NSPEED
, c
, tmax
, c
, total
);
492 static void save_speedjs(long next
) {
495 if ((f
= fopen("/var/tmp/cstats-speed.js", "w")) == NULL
) return;
497 node_print_mode_t info
;
502 fprintf(f
, "\nspeed_history = {\n");
503 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_print_speedjs
, &info
);
504 fprintf(f
, "%s_next: %ld};\n", info
.kn
? "},\n" : "", ((next
>= 1) ? next
: 1));
508 rename("/var/tmp/cstats-speed.js", "/var/spool/cstats-speed.js");
511 void Node_print_datajs(Node
*self
, void *t
) {
515 node_print_mode_t
*info
= (node_print_mode_t
*)t
;
517 if (info
->mode
== DAILY
) {
524 data
= self
->monthly
;
529 for (k
= max
; k
> 0; --k
) {
531 if (data
[p
].xtime
== 0) continue;
532 fprintf(info
->stream
, "%s[0x%lx,'%s',%llu,%llu]", info
->kn
? "," : "",
533 (unsigned long)data
[p
].xtime
, self
->ipaddr
, data
[p
].counter
[0] / K
, data
[p
].counter
[1] / K
);
538 static void save_datajs(FILE *f
, int mode
) {
539 node_print_mode_t info
;
543 fprintf(f
, "\n%s_history = [\n", (mode
== DAILY
) ? "daily" : "monthly");
544 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_print_datajs
, &info
);
545 fprintf(f
, "\n];\n");
548 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");
559 static void bump(data_t
*data
, int *tail
, int max
, uint32_t xnow
, unsigned long *counter
) {
563 if (data
[t
].xtime
!= xnow
) {
564 for (i
= max
- 1; i
>= 0; --i
) {
565 if (data
[i
].xtime
== xnow
) {
571 *tail
= t
= (t
+ 1) % max
;
572 data
[t
].xtime
= xnow
;
573 memset(data
[t
].counter
, 0, sizeof(data
[0].counter
));
576 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
577 data
[t
].counter
[i
] += counter
[i
];
581 void Node_housekeeping(Node
*self
, void *info
) {
582 if (self
->sync
== -1) {
589 static void calc(void) {
593 unsigned long counter
[MAX_COUNTER
];
610 exclude
= nvram_safe_get("cstats_exclude");
611 include
= nvram_safe_get("cstats_include");
613 _dprintf("%s: cstats_exclude='%s'\n", __FUNCTION__
, exclude
);
617 char ip
[INET_ADDRSTRLEN
];
620 char name
[] = "/proc/net/ipt_account/lanX";
622 for(br
=0 ; br
<=3 ; br
++) {
624 char wholenetstatsline
= 1;
626 char bridge
[2] = "0";
632 sprintf(name
, "/proc/net/ipt_account/lan%s", bridge
);
634 if ((f
= fopen(name
, "r")) == NULL
) continue;
636 // _dprintf("%s: file %s opened\n", __FUNCTION__, name);
637 // if (!wholenetstatsline)
638 // fgets(buf, sizeof(buf), f); // network
640 while (fgets(buf
, sizeof(buf
), f
)) {
641 // _dprintf("%s: read\n", __FUNCTION__);
643 "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",
644 // "ip = %s bytes_src = %Lu %*Lu %*Lu %*Lu %*Lu packets_src = %*Lu %*Lu %*Lu %*Lu %*Lu bytes_dest = %Lu %*Lu %*Lu %*Lu %*Lu packets_dest = %*Lu %*Lu %*Lu %*Lu %*Lu time = %*lu",
645 ip
, &rx
, &tx
) != 3 ) continue;
646 // _dprintf("%s: %s tx=%lu rx=%lu\n", __FUNCTION__, ip, tx, rx);
648 // if ((tx < 1) || (rx < 1)) continue;
654 // _dprintf("%s: %s tx=%lu rx=%lu\n", __FUNCTION__, ipaddr, tx, rx);
656 if (find_word(exclude
, ipaddr
)) {
657 wholenetstatsline
= 0;
661 strncpy(test
.ipaddr
, ipaddr
, INET_ADDRSTRLEN
);
662 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
664 if ((find_word(include
, ipaddr
)) || (wholenetstatsline
== 1) || (ptr
) || ((nvram_get_int("cstats_all")) && ((counter
[0] > 0) || (counter
[1] > 0)) )) {
666 wholenetstatsline
= 0;
669 _dprintf("%s: new ip: %s\n", __FUNCTION__
, ipaddr
);
670 TREE_INSERT(&tree
, _Node
, linkage
, Node_new(ipaddr
));
671 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
678 _dprintf("%s: sync[%s]=%d\n", __FUNCTION__
, ptr
->ipaddr
, ptr
->sync
);
680 _dprintf("%s: sync[%s] changed to -1\n", __FUNCTION__
, ptr
->ipaddr
);
683 for (i = 0; i < MAX_COUNTER; ++i) {
684 _dprintf("%s: counter[%d]=%lu ptr->last[%d]=%lu\n", __FUNCTION__, i, counter[i], i, ptr->last[i]);
687 memcpy(ptr
->last
, counter
, sizeof(ptr
->last
));
688 memset(counter
, 0, sizeof(counter
));
689 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
690 _dprintf("%s: counter[%d]=%lu ptr->last[%d]=%lu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
]);
694 // _dprintf("%s: sync[%s] = %d \n", __FUNCTION__, ptr->ipaddr, ptr->sync);
696 _dprintf("%s: sync[%s] = %d \n", __FUNCTION__
, ptr
->ipaddr
, ptr
->sync
);
697 tick
= uptime
- ptr
->utime
;
700 _dprintf("%s: %s is a little early... %lu < %d\n", __FUNCTION__
, ipaddr
, tick
, INTERVAL
);
702 ptr
->utime
+= (n
* INTERVAL
);
703 _dprintf("%s: %s n=%d tick=%lu utime=%lu ptr->utime=%lu\n", __FUNCTION__
, ipaddr
, n
, tick
, uptime
, ptr
->utime
);
704 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
707 // _dprintf("%s: counter[%d]=%lu ptr->last[%d]=%lu c=%u sc=%u\n", __FUNCTION__, i, counter[i], i, ptr->last[i], c, sc);
709 diff
= (0xFFFFFFFF - sc
) + c
;
710 if (diff
> MAX_ROLLOVER
) diff
= 0;
717 _dprintf("%s: counter[%d]=%lu ptr->last[%d]=%lu c=%u sc=%u diff=%lu\n", __FUNCTION__
, i
, counter
[i
], i
, ptr
->last
[i
], c
, sc
, diff
);
719 _dprintf("%s: ip=%s n=%d ptr->tail=%d\n", __FUNCTION__
, ptr
->ipaddr
, n
, ptr
->tail
);
720 for (j
= 0; j
< n
; ++j
) {
721 ptr
->tail
= (ptr
->tail
+ 1) % MAX_NSPEED
;
722 // _dprintf("%s: ip=%s j=%d n=%d ptr->tail=%d\n", __FUNCTION__, ptr->ipaddr, j, n, ptr->tail);
723 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
724 ptr
->speed
[ptr
->tail
][i
] = counter
[i
] / n
;
727 _dprintf("%s: ip=%s j=%d n=%d ptr->tail=%d\n", __FUNCTION__
, ptr
->ipaddr
, j
, n
, ptr
->tail
);
731 if (now
> Y2K
) { /* Skip this if the time&date is not set yet */
732 // _dprintf("%s: calling bump %s ptr->dailyp=%d\n", __FUNCTION__, ptr->ipaddr, ptr->dailyp);
733 tms
= localtime(&now
);
734 bump(ptr
->daily
, &ptr
->dailyp
, MAX_NDAILY
,
735 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8) | tms
->tm_mday
, counter
);
737 // _dprintf("%s: calling bump %s ptr->monthlyp=%d\n", __FUNCTION__, ptr->ipaddr, ptr->monthlyp);
738 n
= nvram_get_int("cstats_offset");
739 if ((n
< 1) || (n
> 31)) n
= 1;
740 mon
= now
+ ((1 - n
) * (60 * 60 * 24));
741 tms
= localtime(&mon
);
742 bump(ptr
->monthly
, &ptr
->monthlyp
, MAX_NMONTHLY
,
743 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8), counter
);
751 // cleanup entries for next time
752 TREE_FORWARD_APPLY(&tree
, _Node
, linkage
, Node_housekeeping
, NULL
);
754 // remove/exclude history (if we still have any data previously stored)
756 nvp
= nv
= strdup(nvram_safe_get("cstats_exclude"));
758 while ((b
= strsep(&nvp
, ",")) != NULL
) {
759 _dprintf("%s: check exclude='%s'\n", __FUNCTION__
, b
);
760 strncpy(test
.ipaddr
, b
, INET_ADDRSTRLEN
);
761 ptr
= TREE_FIND(&tree
, _Node
, linkage
, &test
);
763 _dprintf("%s: excluding '%s'\n", __FUNCTION__
, ptr
->ipaddr
);
764 TREE_REMOVE(&tree
, _Node
, linkage
, ptr
);
772 // todo: total > user ???
773 if (uptime
>= save_utime
) {
775 save_utime
= uptime
+ get_stime();
776 _dprintf("%s: uptime = %lum, save_utime = %lum\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
779 _dprintf("%s: ====================================\n", __FUNCTION__
);
782 static void sig_handler(int sig
) {
800 int main(int argc
, char *argv
[]) {
806 printf("cstats - Copyright (C) 2011 Augusto Bott\n");
807 printf("based on rstats - Copyright (C) 2006-2009 Jonathan Zarate\n\n");
809 if (fork() != 0) return 0;
811 openlog("cstats", LOG_PID
, LOG_USER
);
815 if (strcmp(argv
[1], "--new") == 0) {
821 unlink("/var/tmp/cstats-load");
823 sa
.sa_handler
= sig_handler
;
825 sigemptyset(&sa
.sa_mask
);
826 sigaction(SIGUSR1
, &sa
, NULL
);
827 sigaction(SIGUSR2
, &sa
, NULL
);
828 sigaction(SIGHUP
, &sa
, NULL
);
829 sigaction(SIGTERM
, &sa
, NULL
);
830 sigaction(SIGINT
, &sa
, NULL
);
834 z
= uptime
= get_uptime();
839 if (unlink("/var/tmp/cstats-load") == 0) load_new();
844 save(!nvram_match("cstats_sshut", "1"));
848 save_speedjs(z
- get_uptime());
851 else if (gotuser
== 2) {
855 uptime
= get_uptime();