View Single Post
  #6   (View Single Post)  
Old 14th June 2008
ephemera's Avatar
ephemera ephemera is offline
Knuth's homeboy
 
Join Date: Apr 2008
Posts: 537
Default

Quote:
Originally Posted by centerstage View Post
Hello all,

I am trying to find a simple solution to a seemingly common requirement. We need to keep track of total bandwidth by month on our external interface as our
new ISP charges $15/gb over our monthly cap

We also don't need fancy graphs just text is fine.
I have written a daemon for my own amusement. You can try it out.

It can track the b/w usage correctly across reboots or even an OS crash.

the log file /var/log/bwmon.[ifn] can be used to find the b/w usage across any time range.

startup and error messages are logged to /var/log/daemon.

Source code:
Code:
/*
 *      Persistant net bandwidth usage monitor daemon 
 *      
 *      Copyright: ephemera @ daemonforums.org
 */
  
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef BSD
# include <sys/socket.h>
# include <ifaddrs.h>
#endif /* BSD */
#ifdef SOLARIS
# include <kstat.h>
#endif /* SOLARIS */

#include <net/if.h>

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#ifdef LINUX
# include <regex.h>
#endif /* LINUX */
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#define ARGV0   "bwmond"

typedef unsigned long long      Bwbyte_t;

static char     LOGFILE[PATH_MAX] = "/var/log/"ARGV0".";
static char     RECFILE[PATH_MAX] = "/var/spool/"ARGV0".";
static char     interface[IFNAMSIZ];

static void     usage(void);
static int      update(Bwbyte_t *, Bwbyte_t *);
static void     logpr(int, char *, ...);
static void     err_sys(int, char *, char *);
static void     sig_term(int);

int
main(int argc, char *argv[])
{
        int             i, recfd, logfd;
        time_t          t, t2;
        Bwbyte_t        u, d, last_up, last_down, hr_up, hr_down;
        Bwbyte_t        *record;
        enum            {UP, DOWN};
        int    		record_len = 2 * sizeof(*record);
        const int       interval = 60;     
        int		log_interval = 60 * 5; 
        pid_t           pid;
        sigset_t        sigset;
        struct sigaction       sigact;

        switch (--argc) {
        case 2:
		log_interval = 60 * strtoul(*++argv, (char **)NULL, 10);
                if (log_interval <= 0) 
                        usage();
                /* FALLTHROUGH */

        case 1:
		strncpy(interface, *++argv, sizeof(interface));
                interface[sizeof(interface) - 1] = '\0';
                strncat(RECFILE, interface, sizeof(interface) - 1);
                strncat(LOGFILE, interface, sizeof(interface) - 1);
                break;

        default:
		usage();
        }

        if ( (pid = fork()) < 0) 
                err_sys(1, "fork", "");

        if (pid > 0)
                exit(0);

        setsid();

        chdir("/");

        umask(0);

        for (i = getdtablesize(); i >= 0; --i) 
                close(i); 

        i = open("/dev/null", O_RDWR);
        dup2(i, STDIN_FILENO);
        dup2(i, STDOUT_FILENO);
        dup2(i, STDERR_FILENO);

        sigfillset(&sigset);    
        sigdelset(&sigset, SIGTERM);   
        sigprocmask(SIG_BLOCK, &sigset, NULL);
        sigact.sa_handler = sig_term;
        sigemptyset(&sigact.sa_mask);
        sigact.sa_flags = 0;
        sigaction(SIGTERM, &sigact, NULL);

        openlog(ARGV0, LOG_PID, LOG_DAEMON);
        syslog(LOG_INFO, "Started on %s.", interface);

        if (access(LOGFILE, F_OK) != 0) 
                syslog(LOG_NOTICE, "Creating Logfile: %s. "
			       "If this is not the first time that "ARGV0" is being run then this indicates a Problem."
			       , LOGFILE);

        logfd = open(LOGFILE, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
        if (logfd < 0) 
                err_sys(1, "open", LOGFILE);

        if (access(RECFILE, F_OK) != 0) {
                recfd = open(RECFILE
                             ,O_RDWR | O_CREAT | O_SYNC
                             ,S_IRUSR | S_IWUSR);

                if (recfd >= 0) {
                        syslog(LOG_NOTICE, "Creating Recordfile: %s. "
			       "If this is not the first time that "ARGV0" is being run then this indicates a Problem."
			       , RECFILE);
                        if (lseek(recfd, record_len, SEEK_SET) < 0)
                                err_sys(1, "lseek", RECFILE);

                        if (write(recfd, "", 1) < 1)
                                err_sys(1, "write", RECFILE);
                }
        } else {
                recfd = open(RECFILE, O_RDWR | O_SYNC);
        }
        if (recfd < 0)
                err_sys(1, "open", RECFILE);

        record = (Bwbyte_t *) mmap(NULL
                                   ,record_len
                                   ,PROT_READ | PROT_WRITE
                                   ,MAP_SHARED
                                   ,recfd
                                   ,0);

        if ( (caddr_t)record == (caddr_t)-1)
                err_sys(1, "mmap", RECFILE);

        close(recfd);

        u = d = last_up = last_down = hr_up = hr_down = 0;
	t = time(NULL);

        while (!update(&hr_up, &hr_down) || !update(&last_up, &last_down))
		sleep(interval);

        for ( ; ; ) {
                if (update(&u, &d)) {
                         if (u < last_up || d < last_down) {
                                last_up = last_down = hr_up = hr_down = 0;
                                continue;
                         }
                         record[UP] += u - last_up;
                         record[DOWN] += d - last_down;
                         last_up = u;
                         last_down = d;
			 t2 = time(NULL);
			 if (t2 - t >= log_interval) {
                                 logpr(logfd
                                       ,"%6llu kb Up  %6llu kb Dn  %10llu / %llu\n"
                                       ,(last_up - hr_up) / 1024 
                                       ,(last_down - hr_down) / 1024
                                       ,record[UP] / 1024
                                       ,record[DOWN] / 1024);
			 	 t = t2;
                                 hr_up = last_up;
                                 hr_down = last_down;
                         }
                         msync((void *)record, record_len, MS_SYNC);
                }
                sleep(interval);
        }
        /* NOTREACHED */
}

static void
err_sys(int quit, char *fn, char *arg)
{

        if (!*arg)
                syslog(LOG_ERR, "Error: %s: %m\n", fn); 
        else
                syslog(LOG_ERR, "Error: %s: %s: %m\n", fn, arg); 

        if (quit) {
                syslog(LOG_ERR, "Terminated due to error on %s.", interface);
                closelog();
                exit(quit);
        }
}

static int
update(Bwbyte_t *up, Bwbyte_t *down)
{
        int    updated = 0;

        errno = 0;

#ifdef BSD
        struct ifaddrs *ifa, *f;

        if (getifaddrs(&ifa) == 0) {
                for (f = ifa; f; f = f->ifa_next) {
                        if (strcmp(interface, f->ifa_name) == 0 && 
                            f->ifa_addr->sa_family == AF_LINK) {
                                *down = ((struct if_data*)f->ifa_data)->ifi_ibytes;
                                *up =((struct if_data*)f->ifa_data)->ifi_obytes;
                                updated = 1;
                                break;
                        }
                }
                freeifaddrs(ifa);
        }
#endif  /* BSD */
#ifdef LINUX
        char            line[2048];
	static char	repatrn[128] = "";
        regmatch_t      rematch[3];
        static regex_t  regex;
        FILE            *pfd;

	if (!*repatrn) {
	        snprintf(repatrn, sizeof(repatrn)
       	        	 ,"^ *%s: *([0-9]+) +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+" 
       	        	  " +[0-9]+ +[0-9]+ +[0-9]+ +([0-9]+) "
       	        	 ,interface);

                if (regcomp(&regex, repatrn, REG_EXTENDED) != 0)
			repatrn[0] = 0;
	}
        if (*repatrn && (pfd = fopen("/proc/net/dev", "r")) != NULL) {
                while(fgets(line, sizeof(line), pfd) != NULL) {
                        if (regexec(&regex, line, 3, rematch, 0) == 0) {
                                *down = atoll(line + rematch[1].rm_so);
                                *up = atoll(line + rematch[2].rm_so);
                                updated = 1;
                                break;
                        }
                }
        	fclose(pfd);
        }
#endif  /* LINUX */
#ifdef SOLARIS
        kstat_ctl_t     *kc;
        kstat_t         *ksp;
        kstat_named_t   *knp;

        if ( (kc = kstat_open()) != NULL) {
                /* workaround? kstat_open sets errno even on success */
                errno = 0;
                for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
                        if (strcmp(ksp->ks_class, "net") == 0 &&
                            strcmp(ksp->ks_name, interface) == 0 &&
                            kstat_read(kc, ksp, NULL) != -1) {
                                if ((knp=kstat_data_lookup(ksp,"rbytes64")) != NULL)
                                        *down = knp->value.ui64;
                                if ((knp=kstat_data_lookup(ksp,"obytes64")) != NULL) {
                                        *up = knp->value.ui64;
                                        updated = 1;
                                        break;
                                }
                        } 
                }
                kstat_close(kc);
        }
#endif  /* SOLARIS */

        if (errno)
                err_sys(0, "update", "");

        return updated;
}

static void
logpr(int logfd, char *msg, ...)
{
#define LOG_LINE_MAX	256
        char    logent[LOG_LINE_MAX];        
	size_t	r;
        time_t  clk;
        va_list ap;

        va_start(ap, msg);
        clk = time(NULL);
        r = strftime(logent, sizeof(logent)
		     ,"%b %e %H:%M:%S %Y: "
		     ,localtime(&clk));
        vsnprintf(logent + r, sizeof(logent) - r, msg, ap);
	if (write(logfd, logent, strlen(logent)) < 0)
		err_sys(0, "logpr", "");

        va_end(ap);
}

static void
usage(void)
{

        fprintf(stderr, "Usage: %s [log interval] interface\n",
                ARGV0);
        exit(1);
}

static void
sig_term(int signo)
{
        
        syslog(LOG_INFO, "Stopped on %s.", interface);   
        closelog();
        exit(0);
}
save the above to a file bwmon.c.

then compile: cc -O2 -DBSD -o bwmond bwmon.c && strip bwmond

you might want to run it from rc.local at startup. Example:

/path/to/bwmond em0 && echo -n ' bwmon'

(note: the interface argument is mandatory.)

Last edited by ephemera; 22nd October 2008 at 04:04 PM.
Reply With Quote