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.
25 #include <sys/types.h>
26 #include <sys/sysinfo.h>
28 #include <sys/ioctl.h>
43 #define M (1024 * 1024)
44 #define G (1024 * 1024 * 1024)
47 #define SHOUR (60 * 60)
48 #define SDAY (60 * 60 * 24)
49 #define Y2K 946684800UL
53 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
55 #define MAX_NMONTHLY 25
56 #define MAX_SPEED_IF 10
57 #define MAX_ROLLOVER (225 * M)
66 #define ID_V0 0x30305352
67 #define ID_V1 0x31305352
68 #define CURRENT_ID ID_V1
74 uint64_t counter
[MAX_COUNTER
];
80 data_t daily
[MAX_NDAILY
];
83 data_t monthly
[MAX_NMONTHLY
];
100 unsigned long speed
[MAX_NSPEED
][MAX_COUNTER
];
101 unsigned long last
[MAX_COUNTER
];
107 speed_t speed
[MAX_SPEED_IF
];
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)
129 t
= nvram_get_int("rstats_stime");
131 else if (t
> 8760) t
= 8760;
136 static int comp(const char *path
, void *buffer
, int size
)
140 if (f_write(path
, buffer
, size
, 0, 0) != size
) return 0;
142 sprintf(s
, "%s.gz", path
);
145 sprintf(s
, "gzip %s", path
);
146 return system(s
) == 0;
149 static void save(int quick
)
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__);
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);
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__
);
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
);
197 nvram_set("rstats_data", bo
);
198 if (!nvram_match("debug_nocommit", "1")) nvram_commit();
200 _dprintf("%s: nvram commit\n", __FUNCTION__
);
207 else if (save_path
[0] != 0) {
208 strcpy(tmp
, save_path
);
211 for (i
= 15; i
> 0; --i
) {
212 if (!wait_action_idle(10)) {
213 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
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")) {
222 tms
= localtime(&now
);
223 if (lastbak
!= tms
->tm_yday
) {
224 strcpy(bak
, save_path
);
226 if ((n
> 3) && (strcmp(bak
+ (n
- 3), ".gz") == 0)) n
-= 3;
228 for (b
= HI_BACK
-1; b
> 0; --b
) {
229 sprintf(bkp
+ n
, "_%d.bak", b
+ 1);
230 sprintf(bak
+ n
, "_%d.bak", b
);
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__
);
245 // might not be ready
252 static int decomp(const char *fname
, void *buffer
, int size
, int max
)
257 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
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
);
270 _dprintf("%s: %s != 0\n", __FUNCTION__
, s
);
273 memset((char *)buffer
+ (size
* n
), 0, (max
- n
) * size
);
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
)
287 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
289 if ((decomp(fname
, &hist
, sizeof(hist
), 1) != 1) || (hist
.id
!= CURRENT_ID
)) {
292 if ((decomp(fname
, &v0
, sizeof(v0
), 1) != 1) || (v0
.id
!= ID_V0
)) {
293 _dprintf("%s: load failed\n", __FUNCTION__
);
297 // --- temp conversion ---
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
;
309 memcpy(&history
, &hist
, sizeof(history
));
312 _dprintf("%s: dailyp=%d monthlyp=%d\n", __FUNCTION__
, history
.dailyp
, history
.monthlyp
);
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
)
328 if ((n
> 3) && (strcmp(fn
+ (n
- 3), ".gz") == 0))
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
);
339 static void load_new(void)
343 sprintf(hgz
, "%s.gz.new", history_fn
);
344 if (load_history(hgz
)) save(0);
348 static void load(int new)
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
)) {
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
;
389 sprintf(hgz
, "%s.gz", history_fn
);
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__
);
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);
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);
441 if ((i
*= 2) > 900) i
= 900; // 15m
449 syslog(LOG_WARNING
, "Problem loading %s. Still trying...", save_path
);
456 static void save_speedjs(long next
)
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
) {
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
) {
495 fprintf(f
, ",\n %cx: [", c
);
497 for (k
= 0; k
< MAX_NSPEED
; ++k
) {
498 p
= (p
+ 1) % MAX_NSPEED
;
500 fprintf(f
, "%s%lu", k
? "," : "", n
);
502 if (n
> tmax
) tmax
= 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
);
515 rename("/var/tmp/rstats-speed.js", "/var/spool/rstats-speed.js");
519 static void save_datajs(FILE *f
, int mode
)
526 fprintf(f
, "\n%s_history = [\n", (mode
== DAILY
) ? "daily" : "monthly");
529 data
= history
.daily
;
534 data
= history
.monthly
;
535 p
= history
.monthlyp
;
539 for (k
= max
; k
> 0; --k
) {
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
);
549 static void save_histjs(void)
553 if ((f
= fopen("/var/tmp/rstats-history.js", "w")) != NULL
) {
554 save_datajs(f
, DAILY
);
555 save_datajs(f
, MONTHLY
);
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
)
567 if (data
[t
].xtime
!= xnow
) {
568 for (i
= max
- 1; i
>= 0; --i
) {
569 if (data
[i
].xtime
== xnow
) {
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)
591 unsigned long counter
[MAX_COUNTER
];
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;
613 if ((ifname
= strrchr(buf
, ' ')) == NULL
) ifname
= buf
;
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;
621 for (i
= speed_count
; i
> 0; --i
) {
622 if (strcmp(sp
->ifname
, ifname
) == 0) break;
626 if (speed_count
>= MAX_SPEED_IF
) continue;
628 _dprintf("%s: add %s as #%d\n", __FUNCTION__
, ifname
, speed_count
);
632 memset(sp
, 0, sizeof(*sp
));
633 strcpy(sp
->ifname
, ifname
);
638 _dprintf("%s: sync %s\n", __FUNCTION__
, ifname
);
641 memcpy(sp
->last
, counter
, sizeof(sp
->last
));
642 memset(counter
, 0, sizeof(counter
));
647 tick
= uptime
- sp
->utime
;
650 _dprintf("%s: %s is a little early... %d < %d\n", __FUNCTION__
, ifname
, tick
, INTERVAL
);
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
) {
661 diff
= (0xFFFFFFFF - sc
) + c
;
662 if (diff
> MAX_ROLLOVER
) diff
= 0;
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;
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
);
703 // cleanup stale entries
704 for (i
= 0; i
< speed_count
; ++i
) {
706 if (sp
->sync
== -1) {
710 if (((uptime
- sp
->utime
) > (10 * SMIN
)) || (find_word(exclude
, sp
->ifname
))) {
711 _dprintf("%s: #%d removing. > time limit or excluded\n", __FUNCTION__
, i
);
713 memcpy(sp
, sp
+ 1, (speed_count
- i
) * sizeof(speed
[0]));
716 _dprintf("%s: %s not found setting sync=1\n", __FUNCTION__
, sp
->ifname
, i
);
721 // todo: total > user
722 if (uptime
>= save_utime
) {
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
)
749 int main(int argc
, char *argv
[])
755 printf("rstats\nCopyright (C) 2006-2009 Jonathan Zarate\n\n");
757 if (fork() != 0) return 0;
759 openlog("rstats", LOG_PID
, LOG_USER
);
763 if (strcmp(argv
[1], "--new") == 0) {
770 unlink("/var/tmp/rstats-load");
772 sa
.sa_handler
= sig_handler
;
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
);
783 z
= uptime
= get_uptime();
788 if (unlink("/var/tmp/rstats-load") == 0) load_new();
793 save(!nvram_match("rstats_sshut", "1"));
797 save_speedjs(z
- get_uptime());
800 else if (gotuser
== 2) {
804 uptime
= get_uptime();