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>
36 // #define DEBUG_NOISY
37 // #define DEBUG_STIME
41 #define _dprintf(args...) cprintf(args)
43 #define _dprintf(args...) do { } while (0)
49 #define M (1024 * 1024)
50 #define G (1024 * 1024 * 1024)
53 #define SHOUR (60 * 60)
54 #define SDAY (60 * 60 * 24)
55 #define Y2K 946684800UL
59 #define MAX_NSPEED ((24 * SHOUR) / INTERVAL)
61 #define MAX_NMONTHLY 25
62 #define MAX_SPEED_IF 10
63 #define MAX_ROLLOVER (225 * M)
72 #define ID_V0 0x30305352
73 #define ID_V1 0x31305352
74 #define CURRENT_ID ID_V1
79 uint64_t counter
[MAX_COUNTER
];
85 data_t daily
[MAX_NDAILY
];
88 data_t monthly
[MAX_NMONTHLY
];
105 unsigned long speed
[MAX_NSPEED
][MAX_COUNTER
];
106 unsigned long last
[MAX_COUNTER
];
112 speed_t speed
[MAX_SPEED_IF
];
118 volatile int gothup
= 0;
119 volatile int gotuser
= 0;
120 volatile int gotterm
= 0;
122 const char history_fn
[] = "/var/lib/misc/rstats-history";
123 const char speed_fn
[] = "/var/lib/misc/rstats-speed";
124 const char uncomp_fn
[] = "/var/tmp/rstats-uncomp";
125 const char source_fn
[] = "/var/lib/misc/rstats-source";
128 static int get_stime(void)
134 t
= nvram_get_int("rstats_stime");
136 else if (t
> 8760) t
= 8760;
141 static int comp(const char *path
, void *buffer
, int size
)
145 if (f_write(path
, buffer
, size
, 0, 0) != size
) return 0;
147 sprintf(s
, "%s.gz", path
);
150 sprintf(s
, "gzip %s", path
);
151 return system(s
) == 0;
154 static void save(int quick
)
164 static int lastbak
= -1;
166 _dprintf("%s: quick=%d\n", __FUNCTION__
, quick
);
168 f_write("/var/lib/misc/rstats-stime", &save_utime
, sizeof(save_utime
), 0, 0);
170 comp(speed_fn
, speed
, sizeof(speed
[0]) * speed_count
);
173 if ((now = time(0)) < Y2K) {
174 _dprintf("%s: time not set\n", __FUNCTION__);
179 comp(history_fn
, &history
, sizeof(history
));
181 _dprintf("%s: write source=%s\n", __FUNCTION__
, save_path
);
182 f_write_string(source_fn
, save_path
, 0, 0);
188 sprintf(hgz
, "%s.gz", history_fn
);
190 if (strcmp(save_path
, "*nvram") == 0) {
191 if (!wait_action_idle(10)) {
192 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
196 if ((n
= f_read_alloc(hgz
, &bi
, 20 * 1024)) > 0) {
197 if ((bo
= malloc(base64_encoded_len(n
) + 1)) != NULL
) {
198 n
= base64_encode(bi
, bo
, n
);
200 nvram_set("rstats_data", bo
);
201 if (!nvram_match("debug_nocommit", "1")) nvram_commit();
203 _dprintf("%s: nvram commit\n", __FUNCTION__
);
210 else if (save_path
[0] != 0) {
211 strcpy(tmp
, save_path
);
214 for (i
= 15; i
> 0; --i
) {
215 if (!wait_action_idle(10)) {
216 _dprintf("%s: busy, not saving\n", __FUNCTION__
);
219 _dprintf("%s: cp %s %s\n", __FUNCTION__
, hgz
, tmp
);
220 if (eval("cp", hgz
, tmp
) == 0) {
221 _dprintf("%s: copy ok\n", __FUNCTION__
);
223 if (!nvram_match("rstats_bak", "0")) {
225 tms
= localtime(&now
);
226 if (lastbak
!= tms
->tm_yday
) {
227 strcpy(bak
, save_path
);
229 if ((n
> 3) && (strcmp(bak
+ (n
- 3), ".gz") == 0)) n
-= 3;
230 sprintf(bak
+ n
, "_%d.bak", ((tms
->tm_yday
/ 7) % 3) + 1);
231 if (eval("cp", save_path
, bak
) == 0) lastbak
= tms
->tm_yday
;
235 _dprintf("%s: rename %s %s\n", __FUNCTION__
, tmp
, save_path
);
236 if (rename(tmp
, save_path
) == 0) {
237 _dprintf("%s: rename ok\n", __FUNCTION__
);
243 // might not be ready
250 static int decomp(const char *fname
, void *buffer
, int size
, int max
)
255 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
260 sprintf(s
, "gzip -dc %s > %s", fname
, uncomp_fn
);
261 if (system(s
) == 0) {
262 n
= f_read(uncomp_fn
, buffer
, size
* max
);
263 _dprintf("%s: n=%d\n", __FUNCTION__
, n
);
268 _dprintf("%s: %s != 0\n", __FUNCTION__
, s
);
271 memset((char *)buffer
+ (size
* n
), 0, (max
- n
) * size
);
275 static void clear_history(void)
277 memset(&history
, 0, sizeof(history
));
278 history
.id
= CURRENT_ID
;
281 static int load_history(const char *fname
)
285 _dprintf("%s: fname=%s\n", __FUNCTION__
, fname
);
287 if ((decomp(fname
, &hist
, sizeof(hist
), 1) != 1) || (hist
.id
!= CURRENT_ID
)) {
290 if ((decomp(fname
, &v0
, sizeof(v0
), 1) != 1) || (v0
.id
!= ID_V0
)) {
291 _dprintf("%s: load failed\n", __FUNCTION__
);
295 // --- temp conversion ---
299 history
.id
= CURRENT_ID
;
300 memcpy(history
.daily
, v0
.daily
, sizeof(history
.daily
));
301 history
.dailyp
= v0
.dailyp
;
302 memcpy(history
.monthly
, v0
.monthly
, sizeof(v0
.monthly
)); // v0 is just shorter
303 history
.monthlyp
= v0
.monthlyp
;
307 memcpy(&history
, &hist
, sizeof(history
));
310 _dprintf("%s: dailyp=%d monthlyp=%d\n", __FUNCTION__
, history
.dailyp
, history
.monthlyp
);
314 static void load_new(void)
318 sprintf(hgz
, "%s.gz.new", history_fn
);
319 if (load_history(hgz
)) save(0);
323 static void load(int new)
330 char sp
[sizeof(save_path
)];
331 unsigned char mac
[6];
333 uptime
= get_uptime();
335 strlcpy(save_path
, nvram_safe_get("rstats_path"), sizeof(save_path
) - 32);
336 if (((n
= strlen(save_path
)) > 0) && (save_path
[n
- 1] == '/')) {
337 ether_atoe(nvram_safe_get("et0macaddr"), mac
);
338 sprintf(save_path
+ n
, "tomato_rstats_%02x%02x%02x%02x%02x%02x.gz",
339 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
342 if (f_read("/var/lib/misc/rstats-stime", &save_utime
, sizeof(save_utime
)) != sizeof(save_utime
)) {
345 t
= uptime
+ get_stime();
346 if ((save_utime
< uptime
) || (save_utime
> t
)) save_utime
= t
;
347 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
351 sprintf(hgz
, "%s.gz", speed_fn
);
352 speed_count
= decomp(hgz
, speed
, sizeof(speed
[0]), MAX_SPEED_IF
);
353 _dprintf("%s: speed_count = %d\n", __FUNCTION__
, speed_count
);
355 for (i
= 0; i
< speed_count
; ++i
) {
356 if (speed
[i
].utime
> uptime
) {
357 speed
[i
].utime
= uptime
;
364 sprintf(hgz
, "%s.gz", history_fn
);
372 f_read_string(source_fn
, sp
, sizeof(sp
)); // always terminated
373 _dprintf("%s: read source=%s save_path=%s\n", __FUNCTION__
, sp
, save_path
);
374 if ((strcmp(sp
, save_path
) == 0) && (load_history(hgz
))) {
375 _dprintf("%s: using local file\n", __FUNCTION__
);
379 if (save_path
[0] != 0) {
380 if (strcmp(save_path
, "*nvram") == 0) {
381 if (!wait_action_idle(60)) exit(0);
383 bi
= nvram_safe_get("rstats_data");
384 if ((n
= strlen(bi
)) > 0) {
385 if ((bo
= malloc(base64_decoded_len(n
))) != NULL
) {
386 n
= base64_decode(bi
, bo
, n
);
387 _dprintf("%s: nvram n=%d\n", __FUNCTION__
, n
);
388 f_write(hgz
, bo
, n
, 0, 0);
397 if (wait_action_idle(10)) {
399 // cifs quirk: try forcing refresh
400 eval("ls", save_path
);
402 if (load_history(save_path
)) {
403 f_write_string(source_fn
, save_path
, 0, 0);
410 if ((i
*= 2) > 900) i
= 900; // 15m
418 syslog(LOG_WARNING
, "Problem loading %s. Still trying...", save_path
);
425 static void save_speedjs(long next
)
436 if ((f
= fopen("/var/tmp/rstats-speed.js", "w")) == NULL
) return;
438 _dprintf("%s: speed_count = %d\n", __FUNCTION__
, speed_count
);
440 fprintf(f
, "\nspeed_history = {\n");
442 for (i
= 0; i
< speed_count
; ++i
) {
444 fprintf(f
, "%s'%s': {\n", i
? " },\n" : "", sp
->ifname
);
445 for (j
= 0; j
< MAX_COUNTER
; ++j
) {
447 fprintf(f
, "%sx: [", j
? ",\n t" : " r");
449 for (k
= 0; k
< MAX_NSPEED
; ++k
) {
450 p
= (p
+ 1) % MAX_NSPEED
;
452 fprintf(f
, "%s%lu", k
? "," : "", n
);
454 if (n
> tmax
) tmax
= n
;
459 fprintf(f
, " %cx_avg: %llu,\n %cx_max: %llu,\n %cx_total: %llu",
460 c
, total
/ MAX_NSPEED
, c
, tmax
, c
, total
);
463 fprintf(f
, "%s_next: %ld};\n", speed_count
? "},\n" : "", ((next
>= 1) ? next
: 1));
466 rename("/var/tmp/rstats-speed.js", "/var/spool/rstats-speed.js");
470 static void save_datajs(FILE *f
, int mode
)
477 fprintf(f
, "\n%s_history = [\n", (mode
== DAILY
) ? "daily" : "monthly");
480 data
= history
.daily
;
485 data
= history
.monthly
;
486 p
= history
.monthlyp
;
490 for (k
= max
; k
> 0; --k
) {
492 if (data
[p
].xtime
== 0) continue;
493 fprintf(f
, "%s[0x%lx,0x%llx,0x%llx]", kn
? "," : "",
494 (unsigned long)data
[p
].xtime
, data
[p
].counter
[0] / K
, data
[p
].counter
[1] / K
);
500 static void save_histjs(void)
504 if ((f
= fopen("/var/tmp/rstats-history.js", "w")) != NULL
) {
505 save_datajs(f
, DAILY
);
506 save_datajs(f
, MONTHLY
);
508 rename("/var/tmp/rstats-history.js", "/var/spool/rstats-history.js");
513 static void bump(data_t
*data
, int *tail
, int max
, uint32_t xnow
, unsigned long *counter
)
518 if (data
[t
].xtime
!= xnow
) {
519 for (i
= max
- 1; i
>= 0; --i
) {
520 if (data
[i
].xtime
== xnow
) {
526 *tail
= t
= (t
+ 1) % max
;
527 data
[t
].xtime
= xnow
;
528 memset(data
[t
].counter
, 0, sizeof(data
[0].counter
));
531 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
532 data
[t
].counter
[i
] += counter
[i
];
536 static void calc(void)
542 unsigned long counter
[MAX_COUNTER
];
556 exclude
= nvram_safe_get("rstats_exclude");
558 if ((f
= fopen("/proc/net/dev", "r")) == NULL
) return;
559 fgets(buf
, sizeof(buf
), f
); // header
560 fgets(buf
, sizeof(buf
), f
); // "
561 while (fgets(buf
, sizeof(buf
), f
)) {
562 if ((p
= strchr(buf
, ':')) == NULL
) continue;
564 if ((ifname
= strrchr(buf
, ' ')) == NULL
) ifname
= buf
;
566 if ((strcmp(ifname
, "lo") == 0) || (find_word(exclude
, ifname
))) continue;
568 // <rx bytes, packets, errors, dropped, fifo errors, frame errors, compressed, multicast><tx ...>
569 if (sscanf(p
+ 1, "%lu%*u%*u%*u%*u%*u%*u%*u%lu", &counter
[0], &counter
[1]) != 2) continue;
572 for (i
= speed_count
; i
> 0; --i
) {
573 if (strcmp(sp
->ifname
, ifname
) == 0) break;
577 if (speed_count
>= MAX_SPEED_IF
) continue;
579 _dprintf("%s: add %s as #%d\n", __FUNCTION__
, ifname
, speed_count
);
583 memset(sp
, 0, sizeof(*sp
));
584 strcpy(sp
->ifname
, ifname
);
589 _dprintf("%s: sync %s\n", __FUNCTION__
, ifname
);
592 memcpy(sp
->last
, counter
, sizeof(sp
->last
));
593 memset(counter
, 0, sizeof(counter
));
598 tick
= uptime
- sp
->utime
;
601 _dprintf("%s: %s is a little early... %d < %d\n", __FUNCTION__
, ifname
, tick
, INTERVAL
);
605 sp
->utime
+= (n
* INTERVAL
);
606 _dprintf("%s: %s n=%d tick=%d\n", __FUNCTION__
, ifname
, n
, tick
);
608 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
612 diff
= (0xFFFFFFFF - sc
) + c
;
613 if (diff
> MAX_ROLLOVER
) diff
= 0;
622 for (j
= 0; j
< n
; ++j
) {
623 sp
->tail
= (sp
->tail
+ 1) % MAX_NSPEED
;
624 for (i
= 0; i
< MAX_COUNTER
; ++i
) {
625 sp
->speed
[sp
->tail
][i
] = counter
[i
] / n
;
630 // todo: split, delay
633 if (get_wan_proto() == WP_DISABLED
) {
634 if ((nvram_get_int("wan_islan") == 0) || (!nvram_match("wan_ifnameX", ifname
))) continue;
637 if (!nvram_match("wan_iface", ifname
)) continue;
640 tms
= localtime(&now
);
641 bump(history
.daily
, &history
.dailyp
, MAX_NDAILY
,
642 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8) | tms
->tm_mday
, counter
);
644 n
= nvram_get_int("rstats_offset");
645 if ((n
< 1) || (n
> 31)) n
= 1;
646 mon
= now
+ ((1 - n
) * (60 * 60 * 24));
647 tms
= localtime(&mon
);
648 bump(history
.monthly
, &history
.monthlyp
, MAX_NMONTHLY
,
649 (tms
->tm_year
<< 16) | ((uint32_t)tms
->tm_mon
<< 8), counter
);
654 // cleanup stale entries
655 for (i
= 0; i
< speed_count
; ++i
) {
657 if (sp
->sync
== -1) {
661 if (((uptime
- sp
->utime
) > (10 * SMIN
)) || (find_word(exclude
, sp
->ifname
))) {
662 _dprintf("%s: #%d removing. > time limit or excluded\n", __FUNCTION__
, i
);
664 memcpy(sp
, sp
+ 1, (speed_count
- i
) * sizeof(speed
[0]));
667 _dprintf("%s: %s not found setting sync=1\n", __FUNCTION__
, sp
->ifname
, i
);
672 // todo: total > user
673 if (uptime
>= save_utime
) {
675 save_utime
= uptime
+ get_stime();
676 _dprintf("%s: uptime = %dm, save_utime = %dm\n", __FUNCTION__
, uptime
/ 60, save_utime
/ 60);
681 static void sig_handler(int sig
)
700 int main(int argc
, char *argv
[])
706 printf("rstats\nCopyright (C) 2006-2009 Jonathan Zarate\n\n");
708 if (fork() != 0) return 0;
710 openlog("rstats", LOG_PID
, LOG_USER
);
714 if (strcmp(argv
[1], "--new") == 0) {
721 unlink("/var/tmp/rstats-load");
723 sa
.sa_handler
= sig_handler
;
725 sigemptyset(&sa
.sa_mask
);
726 sigaction(SIGUSR1
, &sa
, NULL
);
727 sigaction(SIGUSR2
, &sa
, NULL
);
728 sigaction(SIGHUP
, &sa
, NULL
);
729 sigaction(SIGTERM
, &sa
, NULL
);
730 sigaction(SIGINT
, &sa
, NULL
);
734 z
= uptime
= get_uptime();
739 if (unlink("/var/tmp/rstats-load") == 0) load_new();
744 save(!nvram_match("rstats_sshut", "1"));
748 save_speedjs(z
- get_uptime());
751 else if (gotuser
== 2) {
755 uptime
= get_uptime();