
#include <bwmon.h>
#include <curses.h>


/********************************************************
    Linux Network Bandwidth Monitor by Kimmo Nupponen
    $Revision: 1.3 $
    $Date: 2002/05/08 06:33:09 $

    $Log: bwmon.c,v $
    Revision 1.3  2002/05/08 06:33:09  kimmo
    Fixed some bugs with signal handling when handling threads. Previous version suffered
    some problems with linux thread -library.

    Revision 1.2  2001/06/13 17:41:37  nupponen
    Added simply Revision tag to SIGWINCH signal handler

    Revision 1.1  2001/06/13 14:57:50  nupponen
    Initial version of Linux Bandwidth Monitor by Kimmo Nupponen

 ********************************************************/

#ifdef __THREADS
#include <pthread.h>

static pthread_t exit_thread;
#endif

static int print_bytes = 0;

void 
print_help(char * binary) 
{
	fprintf(stderr, "\nLinux Network Bandwidth Monitor $Revision: 1.3 $\n"
			"by Kimmo Nupponen (kimmoon@users.sourceforge.net)\n"
			"$Date: 2002/05/08 06:33:09 $\n\n"
			"usage: %s [-b] [-h] [-a] [-m] [-u seconds]\n"
			"\t-a Print bandwidth utiliasation in Kbytes rather than Kbits. The default\n"
			"\t   is to use Kbits\n"
			"\t-a Print also average bandwidth since last boot per interface\n"
			"\t-m Print maximum bandwidth since launch of this utility\n"
			"\t-h Print this help message\n"
			"\t-u Update timeout (integer value)\n\n"
			"\tUse <space-bar> to refresh the screen before update timeout expires\n"
			"\tUse 'q' or 'Q' to exit this utility\n\n"
			"Note that you have to have proc mounted to allow this software\n"
			"to work!\n\n", binary );
	return;
}

/* 
 * comments are for weenies..
 */

int 
main(int argc, char ** argv) 
{
	
	int i = 0, first_pass = 1, j = 0, opt, average = 0, timeout = 1, max = 0;
	
	char buffer[256], host[50];
	FILE * fp = NULL;

	struct winsize size;
	float uptime = 0;
	
	interface_t interface[MAX_INTERFACES] = { NULL };
	total_interface_t sum_if;
	struct sigaction ract, roldact, eact, eoldact, aact;

	if (argc > 0) {
		while ((opt = getopt(argc, argv, "bahu:m")) != -1) {
			switch (opt) {
				case 'b':
					print_bytes = 1;
					break;
				case 'a':  
					average = 1;             
					break; 
				case 'm': 
					max = 1;
					break;
				case 'h':
					print_help(argv[0]);
					exit(0);
				case 'u':
					if ((timeout = atoi(optarg)) < 1)
						fatal("timeout must be larger than 0");
			}
		}
	}
	gethostname(host, sizeof(host));

	aact.sa_handler = sigalrm_handler;
	sigemptyset(&aact.sa_mask);
	aact.sa_flags = 0;
	if (sigaction(SIGALRM, &aact, NULL) < 0)
		fatal("sigaction failed in %s", __FUNCTION__);

	eact.sa_handler = exit_handler;
	sigemptyset(&eact.sa_mask);
	eact.sa_flags = 0;
	if (sigaction(SIGINT, &eact, &eoldact) < 0)
		fatal("sigaction failed in %s", __FUNCTION__);

#ifdef __THREADS
	if (pthread_create(&exit_thread, NULL, wait_for_exit, (void *) getpid())) 
		fatal("pthread_create failed in %s", __FUNCTION__);
#endif
	
	if (isatty(STDIN_FILENO) == 0)
		fatal("isatty failed in %s. not on terminal", __FUNCTION__);

	ract.sa_handler = sigwinch_handler;
	sigemptyset(&ract.sa_mask);
	ract.sa_flags = 0;
	if (sigaction(SIGWINCH, &ract, &roldact) < 0)
		fatal("sigaction failed in %s", __FUNCTION__);
	
	initscr(); halfdelay(1); noecho();
	clear(); move(0,0);
	attron(A_BOLD);  
	printw("Linux Network Bandwidth Monitor $Revision: 1.3 $ on %s\n", host);
	attroff(A_BOLD);
	attron(A_STANDOUT);
	printw("\n   Interface    Received(K%cps) Transmit(K%cps)     Total(K%cps)",
		   print_bytes == 1 ? 'B': 'b', print_bytes == 1 ? 'B': 'b',
		   print_bytes == 1 ? 'B': 'b');
  
	if (ioctl( STDIN_FILENO, TIOCGWINSZ, (char *) &size) >= 0)
		for (i = 61; i < size.ws_col; i++)
			printw(" ");

	printw("\n\n");
	attroff(A_STANDOUT);

	for ( ;; ) {
		initialize_total(&sum_if);

		if ((fp = fopen(UPTIME, "r")) == NULL)
			fatal("fopen failed in %s", __FUNCTION__);
		fscanf(fp, "%f", &uptime);
		fclose(fp);

		if ((fp = fopen(DEVFILE, "r")) == NULL)
			fatal("fopen() failed  in %s", __FUNCTION__);
		fgets(buffer, 255, fp); fgets(buffer, 255, fp);
		i = 0;

		while (fgets(buffer, 255, fp) != NULL && i < MAX_INTERFACES - 1) {
			if (!do_interface(buffer, &interface[i], first_pass))
				fatal("do_interface failed in %s", __FUNCTION__);
			if (average == 1)
				count_average(&interface[i], &uptime);
			i++;
		}
		fclose(fp);
		
		for (j = 0; j < i; j++)
			if (!do_total(&sum_if, &interface[j], first_pass))
				fatal("do_total failed in %s", __FUNCTION__);

		if (!first_pass) {
			for (j = 0; j < i; j++) {
				print_interface(&interface[j]);
				if (max == 1)
					print_max(&interface[j]);
				if (average == 1)
					print_ut_ave(&interface[j]);
				if ((max == 1) || (average == 1))
					printw("\n");
			}
			printw("\n");
			print_total("All", &sum_if);
			print_uptime(&uptime);
		    printw("\n");   move(3,0); refresh();
			alarm(timeout); pause();

		} else {
			move(3,0);
			printw("Loading, please wait...");
			first_pass = 0;
			move(3,0); refresh();
			alarm(1);  pause();
		}
    }
    endwin();
    exit(0);
}


bool_t
do_interface(char * buffer, interface_t * interface, int first_pass)
{
	char * pbuffer = NULL;
	int    field_number = 0;
	unsigned long int conv_field = 0;
	
	pbuffer = buffer;
	pbuffer = strtok(pbuffer, " :");
	
	if (first_pass) {
		if ((*interface = (interface_t) malloc(sizeof(struct interface))) == NULL)
			return(FALSE);
		strncpy((*interface)->name, pbuffer, 11);
	} 
	(*interface)->time_old = (*interface)->time_new;
	gettimeofday(&((*interface)->time_new), NULL);
	
	(*interface)->time_diff_ms = (ulong) 
		((*interface)->time_new.tv_sec * 1000 + (*interface)->time_new.tv_usec / 1000) -
		((*interface)->time_old.tv_sec * 1000 + (*interface)->time_old.tv_usec / 1000);
	
	field_number = 0;

	while ((pbuffer = strtok(NULL, " :") ) != NULL) {
		conv_field = strtoul(pbuffer, NULL, 10);
		field_number++;
		
		switch (field_number) {
			case 1:
				(*interface)->rx_bytes_old = (*interface)->rx_bytes_new;
				if (print_bytes == 0)
					(*interface)->rx_bytes_new = conv_field * 8;
				else
					(*interface)->rx_bytes_new = conv_field;
				(*interface)->rx_kbytes_dif = ( (*interface)->rx_bytes_new -
												(*interface)->rx_bytes_old) * 1000 / 1024;
				(*interface)->rx_rate_whole = (*interface)->rx_kbytes_dif / 
					(*interface)->time_diff_ms;
				(*interface)->rx_rate_part = bwm_calc_remainder((*interface)->rx_kbytes_dif,
																(*interface)->time_diff_ms);

				if ((*interface)->rx_rate_whole >= (*interface)->rx_max_whole) {
					if (((*interface)->rx_rate_part > (*interface)->rx_max_part) ||
						((*interface)->rx_rate_whole >= (*interface)->rx_max_whole)) {
						(*interface)->rx_max_part  = (*interface)->rx_rate_part;
						(*interface)->rx_max_whole = (*interface)->rx_rate_whole;
					}
				}
				break;

			case 9:
				(*interface)->tx_bytes_old = (*interface)->tx_bytes_new;
				if (print_bytes == 0)
					(*interface)->tx_bytes_new = conv_field * 8;
				else
					(*interface)->tx_bytes_new = conv_field;
				(*interface)->tx_kbytes_dif = ((*interface)->tx_bytes_new -
											   (*interface)->tx_bytes_old) * 1000 / 1024;
				(*interface)->tx_rate_whole = (*interface)->tx_kbytes_dif / 
					(*interface)->time_diff_ms;
				(*interface)->tx_rate_part = bwm_calc_remainder((*interface)->tx_kbytes_dif,
																(*interface)->time_diff_ms);

				if ((*interface)->tx_rate_whole >= (*interface)->tx_max_whole) {
					if (((*interface)->tx_rate_part > (*interface)->tx_max_part) ||
						((*interface)->tx_rate_whole >= (*interface)->tx_max_whole) ) {
						(*interface)->tx_max_part  = (*interface)->tx_rate_part;
						(*interface)->tx_max_whole = (*interface)->tx_rate_whole;
					}
				}
				(*interface)->tot_rate_whole = (*interface)->rx_rate_whole +
					(*interface)->tx_rate_whole;
				(*interface)->tot_rate_part = (*interface)->rx_rate_part +
					(*interface)->tx_rate_part;
			
				if ((*interface)->tot_rate_part > 1000) {
					(*interface)->tot_rate_whole++;
					(*interface)->tot_rate_part -= 1000;
				}
				break;
		}
	}
	return(TRUE);
}

bool_t
count_average(interface_t * interface, float * uptime)
{
	(*interface)->rx_average = (float)((*interface)->rx_bytes_new / *uptime / 1024); 
	(*interface)->tx_average = (float)((*interface)->tx_bytes_new / *uptime / 1024); 
	return(TRUE);
}

bool_t
print_interface(interface_t * interface) 
{
	printw("%12s      %7lu.%03u     %7lu.%03u     %7lu.%03u\n", (*interface)->name,
		   (*interface)->rx_rate_whole,  (*interface)->rx_rate_part,
		   (*interface)->tx_rate_whole,  (*interface)->tx_rate_part,  
		   (*interface)->tot_rate_whole, (*interface)->tot_rate_part );
	return(TRUE);
}

bool_t
print_max(interface_t * interface)
{
	ulong max_whole = (*interface)->rx_max_whole + (*interface)->tx_max_whole;
	ulong max_part  = (*interface)->rx_max_part + (*interface)->tx_max_part;

	if (max_part >= 1000) {
		max_whole++;
		max_part -= 1000;
	}
	printw("        max:      %7lu.%03u     %7lu.%03u     %7lu.%03u \n",
		   (*interface)->rx_max_whole, (*interface)->rx_max_part,
		   (*interface)->tx_max_whole, (*interface)->tx_max_part,
		   max_whole, max_part);
	return(TRUE);
}


bool_t 
print_ut_ave(interface_t * interface) 
{
	printw("       aver:          %7.3f         %7.3f         %7.3f\n",  
		   (*interface)->rx_average, (*interface)->tx_average ,
		   (*interface)->rx_average + (*interface)->tx_average );
	return(TRUE);
}


void 
sigwinch_handler(int signo) 
{

	int i = 0;
	struct winsize size;
	char host[50];
	
	gethostname(host, sizeof(host));
   	endwin(); initscr(); halfdelay(1); noecho();
	clear(); move(0,0); refresh();
	attron(A_BOLD);  
	printw("Linux Network Bandwidth Monitor $Revision: 1.3 $ on %s\n", host);
	attroff(A_BOLD);
	attron(A_STANDOUT);

	printw("\n   Interface    Received(K%cps) Transmit(K%cps)     Total(K%cps)",
		   print_bytes == 1 ? 'B': 'b', print_bytes == 1 ? 'B': 'b',
		   print_bytes == 1 ? 'B': 'b');

	if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) >= 0)
		for (i = 61; i < size.ws_col; i++)
			printw(" ");

	attroff(A_STANDOUT);
	return;
}



unsigned long 
bwm_calc_remainder(unsigned long num, unsigned long den)
{
	unsigned long long n = num, d = den;
	return (((n - (n / d) * d) * 1000) / d);
}

void
initialize_total(total_interface_t * total_if)
{
	total_if->rx_bw_total_whole = total_if->tx_bw_total_whole = 0;
	total_if->tot_bw_total_whole = total_if->rx_bw_total_part = 0;
	total_if->tx_bw_total_part = total_if->tot_bw_total_part  = 0;
}

bool_t
do_total(total_interface_t * total_if, interface_t * interface, int first_pass)
{
	total_if->rx_bw_total_whole += (*interface)->rx_rate_whole;
	total_if->rx_bw_total_part += (*interface)->rx_rate_part;
	
	if (total_if->rx_bw_total_part > 1000) {
		total_if->rx_bw_total_whole++;
		total_if->rx_bw_total_part -= 1000;
	}
	total_if->tx_bw_total_whole += (*interface)->tx_rate_whole;
	total_if->tx_bw_total_part += (*interface)->tx_rate_part;
	
	if (total_if->tx_bw_total_part > 1000) {
		total_if->tx_bw_total_whole++;
		total_if->tx_bw_total_part -= 1000;
	}
	total_if->tot_bw_total_whole = total_if->rx_bw_total_whole + total_if->tx_bw_total_whole;
	total_if->tot_bw_total_part = total_if->rx_bw_total_part + total_if->tx_bw_total_part;
  
	if (total_if->tot_bw_total_part > 1000) {
		total_if->tot_bw_total_whole++;
		total_if->tot_bw_total_part -= 1000;
	}
	return(TRUE);
}

bool_t
print_total(char * method, total_interface_t * total_if)
{
	printw("%12s     %8lu.%03u    %8lu.%03u    %8lu.%03u\n", method,
		   total_if->rx_bw_total_whole,  total_if->rx_bw_total_part,
		   total_if->tx_bw_total_whole,  total_if->tx_bw_total_part,
		   total_if->tot_bw_total_whole, total_if->tot_bw_total_part);
    return(TRUE);
} 

bool_t 
print_uptime(float * uptime) 
{
	unsigned int tmp = 0, hour = 0, min = 0, sec = 0;
	char hostname[30];

	tmp = (uint)(*uptime);
	sec = tmp % 60;
	tmp = (tmp - sec) / 60;
	min = tmp % 60;
	tmp = (tmp - min) / 60;
	hour = tmp % 24;
	tmp = (tmp - hour) / 24;

	if (gethostname(hostname, strlen(hostname)) != 0)
		strcpy(hostname, "System");

	printw("\n%s uptime: %u day%c %u hour%c %u minutes and %u seconds\n", 
		   hostname, tmp, (tmp==1?' ':'s'), hour, (hour==1?' ':'s'), min, sec);
    return(TRUE);
}


#ifdef __THREADS
void *
wait_for_exit(void * pid)
{
	sigset_t blocked;
	char     q = ' ';
	
    if (sigemptyset(&blocked) < 0)
        fatal("sigemptyset failed in %s", __FUNCTION__);

    if (sigaddset(&blocked, SIGINT) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGQUIT) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGTERM) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGABRT) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGHUP) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGALRM) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGPIPE) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGSEGV) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);
    if (sigaddset(&blocked, SIGINT) < 0)
        fatal("sigaddset failed in %s", __FUNCTION__);

    if (pthread_sigmask(SIG_BLOCK, &blocked, NULL) < 0)
        fatal("pthread_sigmask failed in %s", __FUNCTION__);

    if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
        fatal("pthread_setcancelstate failed in %s", __FUNCTION__);

    if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0)
        fatal("pthread_setcanceltype failed in %s", __FUNCTION__);

	for( ; ; ) {
		q = getc(stdin);
		if (q == ' ') {
			kill((int)pid, SIGALRM);
			fflush(stdin);
		}
		else if ((q == 'q') || (q == 'Q'))
			kill((int)pid, SIGINT);
		else {
			move(3,0); refresh();
			printw("\rUse 'q' or 'Q' to quit. Use <space_bar> to update.\n");
			move(3,0); refresh();
		}
	}
	exit(0);
}
#endif

void 
sigalrm_handler(int signo) 
{
	return;
}

void 
exit_handler(int signo)
{
	int retval = 0;
	void * status;

	endwin();
	printf("Exiting from Linux bandwidth monitor!\n\n");

    if ((retval = pthread_cancel(exit_thread)) != 0) {
        if (retval == ESRCH)
            exit(signo);
        else
            fatal("pthread_cancel failed in %s", __FUNCTION__);
    }

    if ((retval = pthread_join(exit_thread, &status)) != 0) {
        if (retval == ESRCH)
            exit(signo);
        else
            fatal("pthread_join failed in %s", __FUNCTION__);
    }

    if (status != PTHREAD_CANCELED)
        fatal("PTHREAD_CANCEL failed in %s", __FUNCTION__);

    exit(signo);
}



/*
 * Local variables:
 *  tab-width: 4
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */

