#!/usr/bin/perl
use strict; use warnings;

##############################################################################
# Copyright (C) 2003 Rohan Romanus Almeida
# email: arcofdescent@gmail.com
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##############################################################################

# $Id: ibmonitor,v 1.25 2004/12/17 19:02:54 arc_of_ascent Exp $

use Time::HiRes qw/usleep gettimeofday tv_interval/;
use Getopt::Long;
use Term::ANSIColor;
use Data::Dumper;
$| = 1;

# variables
my $debug = 0;
my $debug_file = 'ibmonitor-debug.log';
my $version = '1.3';
my $useReadKey = 0;
my $firstpass = 1;
my $avgpass;
my @proc_data = ();
my @tmp = ();
my $clearstr = `clear`;
my $interface = {};
my $interfaces_all = {};
my @interface_names = ();
my @interface_names_copy = ();
my %cmd_options = ();
my ($inter, $direction);
my $inputkey;
my $key_mess = '';
my $maxbytes_cnt = (2**32) - 1;
my $start_time;
my %colors = ();
my %movers = ();
my $elapsed_time;
my $ostr;
my $ihelp = 0;
my $ihelp_flg = 0;
my ($i, @index);
my ($t0, $t1, $t0_t1);

# functions
sub print_usage();
sub print_help();
sub create_interface($);
sub get_proc_data();
sub trim($);
sub alignstr($$$);
sub init_interfaces_all();
sub reset_all();
sub debug_it($);
sub show_help();
sub quit_it();
sub adjust_unit($);
sub get_run_time($);
sub process_userkey($);
sub order_interfaces();

# Check for installation of Term-ReadKey module
eval q/use Term::ReadKey;/;
if (!$@) {
	$useReadKey = 1;
}

if ($debug) {
	open FH, ">$debug_file" or die "Can't write $debug_file in current directory: $!";
	close FH;
}

%colors = (
	'max'	=> 'Red',
	'avg'	=> 'Green',
	'data'	=> 'Magenta',
);

%cmd_options = (
	'bits'		=> 0,
	'bytes'		=> 0,
	'avg'		=> 0,
	'max'		=> 0,
	'interval'	=> 2, 
	'colors'	=> 1,
	'data'		=> 0,
	'help'		=> 0,
	'version'	=> 0,
	'dev'		=> '',
	'file'		=> '/proc/net/dev',
);

%movers = (
	'init'	=> 0,
	'num'	=> 0,
	'dir'	=> 0,
);

# Parse command line options
my $result = GetOptions (
				'bits'			=> \$cmd_options{'bits'},
				'bytes'			=> \$cmd_options{'bytes'},
				'avg'			=> \$cmd_options{'avg'},
				'max'			=> \$cmd_options{'max'},
				'interval=i'	=> \$cmd_options{'interval'},	
				'colors!'		=> \$cmd_options{'colors'},
				'data'			=> \$cmd_options{'data'},
				'help'			=> \$cmd_options{'help'},
				'version'		=> \$cmd_options{'version'},
				'dev=s'			=> \$cmd_options{'dev'},
				'file=s'		=> \$cmd_options{'file'},
);

if (not $result) {
	print_usage();
	exit 1;
}
if ($cmd_options{'interval'} == 0) {
	print STDERR "0 not allowed as interval.\n";
	print_usage();
	exit 1;
}
if (not -e $cmd_options{'file'}) {
	print STDERR 'file '.$cmd_options{'file'}.' does not exist.'."\n";
	print STDERR "ibmonitor requires the /proc/net/dev file.\n";
	print STDERR "See the README file provided with ibmonitor.\n";
	exit 1;
}
if (`file $cmd_options{file}` !~ /empty/) {
	print STDERR "$cmd_options{file} does not appear to be a valid filetype\n";
	exit 1;
}
if ($cmd_options{'help'}) {
	print_help();
	exit 1;
}
if ($cmd_options{'version'}) {
	print STDERR "ibmonitor version $version\n";
	exit 1;
}
if ($cmd_options{'bits'} == 0 && $cmd_options{'bytes'} == 0) {
	$cmd_options{'bits'} = 1;
}

init_interfaces_all();
$start_time = time();

# Change to raw mode.
# ie. Disable control keys, and enables us to read a single keystroke
if ($useReadKey) {
	eval q/ReadMode 'raw';/;
}

# Start the never-ending story!
while (1) {

	$ostr = sprintf $clearstr;

	# print header
	$ostr .= sprintf alignstr('Interface', 10, 'r');
	if ($cmd_options{'bits'} == 1 && $cmd_options{'bytes'} == 1) {
		$ostr .= sprintf alignstr('Received   ', 22, 'r');
		$ostr .= sprintf alignstr('Sent     ', 22, 'r');
		$ostr .= sprintf alignstr('Total     ', 22, 'r');
		$ostr .= sprintf "\n";
		$ostr .= sprintf alignstr(' ', 10, 'r');
		for (1 .. 3) {
			$ostr .= sprintf alignstr('Kbps', 12, 'r');
			$ostr .= sprintf alignstr('KBps', 10, 'r');
		}
	}
	elsif ($cmd_options{'bits'} == 1) {
		$ostr .= sprintf alignstr('Received', 12, 'r');
		$ostr .= sprintf alignstr('Sent', 12, 'r');
		$ostr .= sprintf alignstr('Total', 12, 'r');
		$ostr .= sprintf "\n";
		$ostr .= sprintf alignstr(' ', 10, 'r');
		for (1 .. 3) {
			$ostr .= sprintf alignstr('Kbps', 12, 'r');
		}
	}
	elsif ($cmd_options{'bytes'} == 1) {
		$ostr .= sprintf alignstr('Received', 10, 'r');
		$ostr .= sprintf alignstr('Sent', 10, 'r');
		$ostr .= sprintf alignstr('Total', 10, 'r');
		$ostr .= sprintf "\n";
		$ostr .= sprintf alignstr(' ', 10, 'r');
		for (1 .. 3) {
			$ostr .= sprintf alignstr('KBps', 10, 'r');
		}
	}
	$ostr .= sprintf "\n\n";

	# Get data from /proc and populate interfaces hash
	@proc_data = get_proc_data();	
	$t0 = [gettimeofday];
	for (@proc_data) {
		($tmp[0], $tmp[1]) = map trim($_), split /:/;
		# Check if user wants to see some particular interface only (ie. --dev)
		if ($tmp[0] =~ /($cmd_options{'dev'})/) { 
			create_interface($tmp[0]) if (not exists $interface->{$tmp[0]});
			push @interface_names, $tmp[0] if (not grep /^$tmp[0]$/, @interface_names);
			$interface->{$tmp[0]}{'Received'}{'New'} = (split /\s+/, $tmp[1])[0];
			$interface->{$tmp[0]}{'Sent'}{'New'} = (split /\s+/, $tmp[1])[8];
		}
	}
	foreach $inter (keys %{$interface}) {
		delete $interface->{$inter} if (not grep /^$inter$/, @interface_names);
	}
	foreach $inter (@interface_names) {
		if (not grep /^$inter$/, @interface_names_copy) {
			push @interface_names_copy, $inter;
		}
	}

	# Arrange interface_names array according to user sort
	@index = ();
	for ($i=0;$i<scalar(@interface_names_copy);$i++) {
		if (not grep /^$interface_names_copy[$i]$/, @interface_names) {
			push @index, $i;
		}
	}
	for (@index) {
		splice @interface_names_copy, $_, 1;
	}
	if (not $firstpass) {
		@interface_names = @interface_names_copy;
	}

	if ($firstpass) {
		for (keys %{$interface}) {
			$interface->{$_}{'Received'}{'Old'} = $interface->{$_}{'Received'}{'New'};
			$interface->{$_}{'Sent'}{'Old'} = $interface->{$_}{'Sent'}{'New'};
		}
		$firstpass = 0;
		$avgpass = 1;
		@interface_names = sort @interface_names;
		if ($debug) {
			debug_it('Inside firstpass conditional:');
			debug_it('Interval: '.$cmd_options{'interval'});
			debug_it(Dumper($interface->{'ppp0'}{'Received'}));
		}
	}
	else {

		# reset interfaces_all hash
		foreach $direction (keys %{$interfaces_all}) {
			$interfaces_all->{$direction}{'Bandwidth'}{'Bps'} = 0;
			$interfaces_all->{$direction}{'Bandwidth'}{'KBps'} = 0;
			$interfaces_all->{$direction}{'Bandwidth'}{'Kbps'} = 0;
		}
				
		foreach $inter (@interface_names) {
			
			foreach $direction ('Received', 'Sent') {
				$interface->{$inter}{$direction}{'Bandwidth'}{'Bps'} = 
					($interface->{$inter}{$direction}{'New'} - 
					$interface->{$inter}{$direction}{'Old'}) / $cmd_options{'interval'};

				# Take care of negative values obtained,
				# due to the byte counter overflowing
				if ($interface->{$inter}{$direction}{'Bandwidth'}{'Bps'} < 0) {
					$interface->{$inter}{$direction}{'Bandwidth'}{'Bps'} = 
					($maxbytes_cnt - $interface->{$inter}{$direction}{'Old'} +
					$interface->{$inter}{$direction}{'New'}) / $cmd_options{'interval'};
				}

				# Put new into old
				$interface->{$inter}{$direction}{'Old'} = 
					$interface->{$inter}{$direction}{'New'};
			}

			# Calulate total of received and sent
			$interface->{$inter}{'Total'}{'Bandwidth'}{'Bps'} = 
				$interface->{$inter}{'Received'}{'Bandwidth'}{'Bps'} +
				$interface->{$inter}{'Sent'}{'Bandwidth'}{'Bps'};

			foreach $direction (keys %{$interface->{$inter}}) {
				# Bandwidth in KBps
				$interface->{$inter}{$direction}{'Bandwidth'}{'KBps'} = 
					sprintf('%.2f', 
					$interface->{$inter}{$direction}{'Bandwidth'}{'Bps'} / 1024);

				# Max Bandwidth in KBps
				$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'} = 
					sprintf( '%.2f', 
					(
					 $interface->{$inter}{$direction}{'Bandwidth'}{'KBps'} > 
					 $interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'}) ?
					 $interface->{$inter}{$direction}{'Bandwidth'}{'KBps'} :
					 $interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'}
					);

				# Total data in KB
				$interface->{$inter}{$direction}{'Amount'}{'KB'} =
				sprintf('%.2f',
				$interface->{$inter}{$direction}{'Amount'}{'KB'} +
				$interface->{$inter}{$direction}{'Bandwidth'}{'KBps'} * 
				$cmd_options{'interval'});

				# Average bandwidth in KBps
				$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'KBps'} = 
				sprintf('%.2f',
				$interface->{$inter}{$direction}{'Amount'}{'KB'} / get_run_time('s'));


				# Values in Kbps
				$interface->{$inter}{$direction}{'Bandwidth'}{'Kbps'} = 
					sprintf('%.2f', 
					$interface->{$inter}{$direction}{'Bandwidth'}{'KBps'} * 8);
				$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'Kbps'} = 
					sprintf('%.2f', 
					$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'KBps'} * 8);
				$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'Kbps'} = 
					sprintf('%.2f', 
					$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'} * 8);
			}

			# Sum of all interfaces
			foreach $direction (keys %{$interfaces_all}) {
				$interfaces_all->{$direction}{'Bandwidth'}{'KBps'} = 
					sprintf('%.2f',
					$interfaces_all->{$direction}{'Bandwidth'}{'KBps'} +
					$interface->{$inter}{$direction}{'Bandwidth'}{'KBps'});
				$interfaces_all->{$direction}{'Bandwidth'}{'Kbps'} = 
					sprintf('%.2f',
					$interfaces_all->{$direction}{'Bandwidth'}{'Kbps'} + 
					$interface->{$inter}{$direction}{'Bandwidth'}{'Kbps'});
			}

			# Display the interface stats
			$ostr .= sprintf alignstr(" $inter", 10, 'l');
			foreach $direction (sort keys %{$interface->{$inter}}) {

				# Bandwidth
				$ostr .= sprintf alignstr(
				$interface->{$inter}{$direction}{'Bandwidth'}{'Kbps'}, 12, 'r') 
				if ($cmd_options{'bits'});
				$ostr .= sprintf alignstr(
				$interface->{$inter}{$direction}{'Bandwidth'}{'KBps'}, 10, 'r') 
				if ($cmd_options{'bytes'});

			}
			$ostr .= sprintf "\n";

			# Display the max stats
			if ($cmd_options{'max'}) {
				if ($cmd_options{'colors'}) {
					$ostr .= sprintf colored alignstr('|---- Max', 10, 'r'), $colors{'max'};
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf colored alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'Kbps'}, 
						12, 'r'), $colors{'max'} if ($cmd_options{'bits'});
						$ostr .= sprintf colored alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'}, 
						10, 'r'), $colors{'max'} if ($cmd_options{'bytes'});
					}
				} else {
					$ostr .= sprintf alignstr('|---- Max', 10, 'r');
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'Kbps'}, 
						12, 'r') if ($cmd_options{'bits'});
						$ostr .= sprintf alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Max'}{'KBps'}, 
						10, 'r') if ($cmd_options{'bytes'});
					}
				}
				$ostr .= sprintf "\n";
			}

			# Display the avg stats
			if ($cmd_options{'avg'}) {
				if ($cmd_options{'colors'}) {
					$ostr .= sprintf colored alignstr('|---- Avg', 10, 'r'), $colors{'avg'};
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf colored alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'Kbps'}, 
						12, 'r'), $colors{'avg'} if ($cmd_options{'bits'});
						$ostr .= sprintf colored alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'KBps'}, 
						10, 'r'), $colors{'avg'} if ($cmd_options{'bytes'});
					}
				} else {
					$ostr .= sprintf alignstr('|---- Avg', 10, 'r');
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'Kbps'}, 
						12, 'r') if ($cmd_options{'bits'});
						$ostr .= sprintf alignstr(
						$interface->{$inter}{$direction}{'Bandwidth'}{'Avg'}{'KBps'}, 
						10, 'r') if ($cmd_options{'bytes'});
					}
				}
				$ostr .= sprintf "\n";
			}

			# Display the interface data usage stats
			if ($cmd_options{'data'}) {
				if ($cmd_options{'colors'}) {
					$ostr .= sprintf colored alignstr('|- Amount', 10, 'r'), $colors{'data'};
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf colored alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						22, 'r'), $colors{'data'}
						if ($cmd_options{'bits'} && $cmd_options{'bytes'});
						$ostr .= sprintf colored alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						12, 'r'), $colors{'data'} 
						if ($cmd_options{'bits'} && !$cmd_options{'bytes'});
						$ostr .= sprintf colored alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						10, 'r'), $colors{'data'} 
						if ($cmd_options{'bytes'} && !$cmd_options{'bits'});
					}
				} else {
					$ostr .= sprintf alignstr('|- Amount', 10, 'r');
					foreach $direction (sort keys %{$interface->{$inter}}) {
						$ostr .= sprintf alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						22, 'r') if ($cmd_options{'bits'} && $cmd_options{'bytes'});
						$ostr .= sprintf alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						12, 'r') if ($cmd_options{'bits'} && !$cmd_options{'bytes'});
						$ostr .= sprintf alignstr(
						adjust_unit($interface->{$inter}{$direction}{'Amount'}{'KB'}), 
						10, 'r') if ($cmd_options{'bytes'} && !$cmd_options{'bits'});
					}
				}
				$ostr .= sprintf "\n";
			}

			$ostr .= sprintf "\n";
		}

		# Stuff for all interfaces (interfaces_all hash)
		foreach $direction (keys %{$interfaces_all}) {

			# Total Data in KB for all interfaces
			$interfaces_all->{$direction}{'Amount'}{'KB'} = 
			sprintf('%.2f',
			$interfaces_all->{$direction}{'Amount'}{'KB'} +
			$interfaces_all->{$direction}{'Bandwidth'}{'KBps'} * 
			$cmd_options{'interval'});

			# Average data in KBps
			$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'KBps'} =
			sprintf('%.2f', 		
			$interfaces_all->{$direction}{'Amount'}{'KB'} / get_run_time('s'));
			# Average data in Kbps
			$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'Kbps'} =
			sprintf('%.2f',
			$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'KBps'} * 8);

			# Max of all interfaces in KBps
			$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'KBps'} =
				sprintf('%.2f',
				($interfaces_all->{$direction}{'Bandwidth'}{'KBps'} > 
				 $interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'KBps'}) ?
				 $interfaces_all->{$direction}{'Bandwidth'}{'KBps'} :
				 $interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'KBps'});
			# Max of all interfaces in Kbps
			$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'Kbps'} =
				sprintf('%.2f',
				($interfaces_all->{$direction}{'Bandwidth'}{'Kbps'} > 
				 $interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'Kbps'}) ?
				 $interfaces_all->{$direction}{'Bandwidth'}{'Kbps'} :
				 $interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'Kbps'});

		}

		# Display All Interfaces stats
		$ostr .= sprintf alignstr(' All', 10, 'l');
		foreach $direction (sort keys %{$interfaces_all}) {
			$ostr .= sprintf 
			alignstr($interfaces_all->{$direction}{'Bandwidth'}{'Kbps'}, 12, 'r') 
			if ($cmd_options{'bits'});
			$ostr .= sprintf 
			alignstr($interfaces_all->{$direction}{'Bandwidth'}{'KBps'}, 10, 'r') 
			if ($cmd_options{'bytes'});
		}
		$ostr .= sprintf "\n";

		# Display the max of all interfaces
		if ($cmd_options{'max'}) {
			if ($cmd_options{'colors'}) {			
				$ostr .= sprintf colored alignstr('|---- Max', 10, 'r'), $colors{'max'};
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf colored alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'Kbps'}, 
					12, 'r'), $colors{'max'} if ($cmd_options{'bits'});
					$ostr .= sprintf colored alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'KBps'}, 
					10, 'r'), $colors{'max'} if ($cmd_options{'bytes'});
				}
			} else {
				$ostr .= sprintf alignstr('|---- Max', 10, 'r');
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'Kbps'}, 
					12, 'r') if ($cmd_options{'bits'});
					$ostr .= sprintf alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Max'}{'KBps'}, 
					10, 'r') if ($cmd_options{'bytes'});
				}
			}
			$ostr .= sprintf "\n";
		}

		# Display the avg of all interfaces
		if ($cmd_options{'avg'}) {
			if ($cmd_options{'colors'}) {
				$ostr .= sprintf colored alignstr('|---- Avg', 10, 'r'), $colors{'avg'};
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf colored alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'Kbps'}, 
					12, 'r'), $colors{'avg'} if ($cmd_options{'bits'});
					$ostr .= sprintf colored alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'KBps'}, 
					10, 'r'), $colors{'avg'} if ($cmd_options{'bytes'});
				}
			} else {
				$ostr .= sprintf alignstr('|---- Avg', 10, 'r');
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'Kbps'}, 
					12, 'r') if ($cmd_options{'bits'});
					$ostr .= sprintf alignstr(
					$interfaces_all->{$direction}{'Bandwidth'}{'Avg'}{'KBps'}, 
					10, 'r') if ($cmd_options{'bytes'});
				}
			}
			$ostr .= sprintf "\n";
		}

		# Display the interfaces_all data usage stats
		if ($cmd_options{'data'}) {
			if ($cmd_options{'colors'}) {
				$ostr .= sprintf colored alignstr('|- Amount', 10, 'r'), $colors{'data'};
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf colored alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 22, 'r'),
					$colors{'data'} if ($cmd_options{'bits'} && $cmd_options{'bytes'});
					$ostr .= sprintf colored alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 12, 'r'),
					$colors{'data'} if ($cmd_options{'bits'} && !$cmd_options{'bytes'});
					$ostr .= sprintf colored alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 10, 'r'),
					$colors{'data'} if (!$cmd_options{'bits'} && $cmd_options{'bytes'});
				}
			} else {
				$ostr .= sprintf alignstr('|- Amount', 10, 'r');
				foreach $direction (sort keys %{$interfaces_all}) {
					$ostr .= sprintf alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 22, 'r') 
					if ($cmd_options{'bits'} && $cmd_options{'bytes'});
					$ostr .= sprintf alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 12, 'r') 
					if ($cmd_options{'bits'} && !$cmd_options{'bytes'});
					$ostr .= sprintf alignstr(
					adjust_unit($interfaces_all->{$direction}{'Amount'}{'KB'}), 10, 'r') 
					if (!$cmd_options{'bits'} && $cmd_options{'bytes'});
				}
			}
			$ostr .= sprintf "\n";
		}

		$elapsed_time = get_run_time('');
	
		$ostr .= sprintf "\n";
		if ($useReadKey) {
			$ostr .= sprintf ' Press \'q\' to quit...';
		} else {
			$ostr .= sprintf ' Press ctrl+c to quit...';
		}
		$ostr .= sprintf "\t\t".'Elapsed time: '.$elapsed_time;
		$ostr .= sprintf "\n";

		if ($debug) {
			debug_it("avgpass: $avgpass");
			debug_it('Interval: '.$cmd_options{'interval'});
			debug_it("elapsed_time: $elapsed_time");
			debug_it('%interface{\'ppp0\'}{\'Received\'} DUMP:');
			debug_it(Dumper($interface->{'ppp0'}{'Received'}));
		}
		
		if ($key_mess ne '') {
			$ostr .= sprintf "\n    $key_mess\n";
			$key_mess = '';
		}
		$avgpass++;

		if ($ihelp) {
			show_help();
		} else {
			print $ostr;
		}

		if ($useReadKey) {
			eval q/$inputkey = ReadKey(0.000000001);/;
			if (defined($inputkey)) {
				# User has pressed some key. Better process it.
				process_userkey($inputkey);
			}
		}
		
	}

	# Clear interface names
	@interface_names_copy = @interface_names;
	@interface_names = ();

	# Sleep for interval seconds
	$t1 = [gettimeofday];
	$t0_t1 = tv_interval $t0, $t1;
	if ($debug) {
		debug_it("\$t0_t1 = $t0_t1");
		debug_it($cmd_options{'interval'} - $t0_t1);
	}
	usleep (($cmd_options{'interval'} - $t0_t1) * 1000000);

} # End of never-ending story! :-)

sub print_usage()
{
	my $progname = `basename $0`;
	chop $progname;

	print STDERR "ibmonitor version $version\n";
	print STDERR 'usage: '.$progname.' [--bits] [--bytes] [--max] [--avg] '."\n".
			"\t\t".'[--data] [--interval n] [--colors | --nocolors] '."\n".
			"\t\t".'[--dev regex] [--file proc] [--help] [--version]'."\n";
}

sub create_interface($)
{
	my $inter = shift;

	for my $direction ('Received', 'Sent', 'Total') {

		$interface->{$inter}{$direction}{'New'} = 0;
		$interface->{$inter}{$direction}{'Old'} = 0;

		$interface->{$inter}{$direction}{'Bandwidth'} = 

									{
										'Bps'	=>	0,
										'KBps'	=>	0,
										'Kbps'	=>	0,
									};

		for my $type ('Max', 'Avg') {

			$interface->{$inter}{$direction}{'Bandwidth'}{$type} = 

									{
										'KBps'	=>	0,
										'Kbps'	=>	0,
									};
		}

		$interface->{$inter}{$direction}{'Amount'} = 
						
									{
										'KB'	=> 	0,
										'MB'	=>	0,
										'GB'	=>	0,
									};
	}
}

sub init_interfaces_all()
{
	for my $dir ('Received', 'Sent', 'Total') {

		$interfaces_all->{$dir}{'Bandwidth'} = 

											{
                                        		'Bps'   =>  0,
                                        		'KBps'  =>  0,
                                        		'Kbps'  =>  0,
            								};

		for my $type ('Max', 'Avg') {

			$interfaces_all->{$dir}{'Bandwidth'}{$type} = 
											{
                                        		'KBps'  =>  0,
                                        		'Kbps'  =>  0,
            								};
	
		}

		$interfaces_all->{$dir}{'Amount'} = 
											{	
												'KB'	=>	0,
												'MB'	=>	0,
												'GB'	=>	0,
											};
	}
}

# get_proc_data
# -------------
#	Return array of lines from file /proc/net/dev
#
sub get_proc_data()
{
	my @arr;
	#my $n_interfaces;
	
	open FH, "<$cmd_options{file}" or die "Couldn't open $cmd_options{file}!";
	@arr = <FH>;
	close FH;
	splice @arr, 0, 2;

	#$n_interfaces = scalar @arr;
	#if ($n_interfaces > $num_interfaces) {
		# An interface has been added/activated
		
	
	return @arr;
}

# trim
# ----
#	Remove leading/trailing whitespace from string	
#
sub trim($)
{
	my $str = $_[0];
	$str =~ s/^\s+//;
	$str =~ s/\s+$//;

	return $str;
}

# alignstr
# --------
#	Align the string	
#	arguments: 
#		1)	the string to align
#		2)	field length
#		3)	l -> align left
#			m -> align center
#			r -> align right
#
sub alignstr($$$)
{
	my ($str, $len, $pos) = @_;
	my $ret_str;
	my $num_spaces;
	my $left_spaces;
	my $right_spaces;

	$num_spaces = $len - length($str);
	if ($pos eq 'l') {
		# left align
		$ret_str = $str;
		$ret_str .= ' ' x $num_spaces;
	}
	elsif ($pos eq 'r') {
		# right align
		$ret_str = ' ' x $num_spaces;
		$ret_str .= $str;
	}
	elsif ($pos eq 'm') {
		# center align
		$left_spaces = int($num_spaces/2);
		$right_spaces = $num_spaces - $left_spaces;
		$ret_str = ' ' x $left_spaces;
		$ret_str .= $str;
		$ret_str .= ' ' x $right_spaces;
	}

	return $ret_str;
}

sub debug_it($)
{
	my $content = $_[0];

	open FH, ">>$debug_file";
	print FH $content."\n";
	close FH;
}

sub show_help()
{
	# display list of interactive commands
	if ($useReadKey) {
		print $clearstr;

		my $tmpstr =<<EOF;

        Help Screen for Interactive Commands
        ------------------------------------

        q    ->  [q]uit
        1-9  ->  Set sleep time interval (in seconds) to the digit entered
        m    ->  Toggle display of [m]ax bandwidth
        a    ->  Toggle display of [a]verage bandwidth
        i    ->  Toggle display of values in KB[i]ts/sec (Kbps)
        y    ->  Toggle display of values in KB[y]tes/sec (KBps)
        d    ->  Toggle display of [d]ata transferred
        s    ->  Shift interface up/down.
                 This should be followed by the interface number,
                 and then the direction (u or d)
        r    ->  [r]eset all values
        ?/h  ->  This help screen

		Press Enter to resume monitoring...

EOF

		print $tmpstr;
	}
}

sub quit_it()
{
	# restore terminal mode to whatever it was
	if ($useReadKey) {
		eval q/ReadMode 'restore';/;
		print "\nQuitting...\n";
		exit 0;
	}
}

sub reset_all()
{
	foreach my $inter_r (keys %{$interface}) {
		foreach my $direction (keys %{$interface->{$inter_r}}) {
			foreach my $type ('Avg', 'Max') {

				foreach my $unit 
				(keys %{$interface->{$inter_r}{$direction}{'Bandwidth'}{$type}})
				{
				
				$interface->{$inter_r}{$direction}{'Bandwidth'}{$type}{$unit} = 0;

				}

			}

			foreach my $unit (keys %{$interface->{$inter_r}{$direction}{'Amount'}}) {

				$interface->{$inter_r}{$direction}{'Amount'}{$unit} = 0;

			}
		}
	}

	foreach my $direction (keys %{$interfaces_all}) {
		foreach my $type ('Avg', 'Max') {
			foreach my $unit 
			(keys %{$interfaces_all->{$direction}{'Bandwidth'}{$type}})
			{
			
			$interfaces_all->{$direction}{'Bandwidth'}{$type}{$unit} = 0;
				}
			}
			foreach my $unit (keys %{$interfaces_all->{$direction}{'Amount'}}) {
				$interfaces_all->{$direction}{'Amount'}{$unit} = 0;
			}
	}

	$avgpass = 1;
	$start_time = time();
}

sub print_help()
{
	my $output;

	print "\n";
	print_usage();

	# print full help with usage
	$output =<<EOF;

The following command line options (and their explanation) are available:

--bits       -> Show output values in KBits/sec. This is the default.
--bytes      -> Show output values in KBytes/sec
--max        -> Show maximum values per interface
--avg        -> Show average values per interface
--interval n -> Set time interval as n seconds. The default is 2 seconds.	
--data       -> Show data transferred in KB/MB/GB
--colors     -> Show some fancy coloring! (This is the default)
--nocolors   -> No fancy coloring please!
--dev regex  -> Show output from device which matches regex
--file proc  -> Specify which file to use in the proc filesystem  
                for the interface byte counter
--help       -> Show help and exit
--version    -> Show version number and exit

While the program is running, the following keys
are recognized, which enables the user to change the output display
format of the program.

Note: ibmonitor responds directly to the single keystroke
      ie. the 'Enter' key need not be pressed

q    -> [q]uit
1-9  -> Set sleep time interval(in seconds) to the digit entered
m    -> Toggle display of [m]ax bandwidth
a    -> Toggle display of [a]verage bandwidth
i    -> Toggle display of values in KB[i]ts/sec (Kbps)
y    -> Toggle display of values in KB[y]tes/sec (KBps)
d    -> Toggle display of [d]ata transferred
s    -> Shift interface up/down.
        This should be followed by the interface number,
        and then the direction (u or d)
r    -> [r]eset all values
?/h  -> help screen for interactive commands

EOF

	print STDERR $output;

}

sub adjust_unit($)
{
	my $amt = $_[0];
	my $ret;

	if ($amt =~ /^\d{1,3}\b\.?/) {
		# return in KB
		$ret = $amt.' KB';
	}
	elsif ($amt =~ /^\d{1,6}\b\.?/) {
		# return in MB
		$ret = sprintf('%.2f', ($amt / 1024)).' MB';
	}
	elsif ($amt =~ /^\d{1,9}\b\.?/) {
		# return in GB
		$ret = sprintf('%.2f', ($amt / (1024*1024))).' GB';
	}

	return $ret;
}
			
sub get_run_time($)
{
	my $arg = shift;
	my $nowtime = time();
	my $diff = $nowtime - $start_time;
	my ($sec, $min, $hour);
	my $ret;

	return $diff if ($arg eq 's');
	$sec = $diff % 60;
	$min = ($diff / 60) % 60;
	$hour = ($diff / (60 * 60)) % 24;

	$ret = "$hour hrs, $min mins, $sec s";

	return $ret;
}

sub process_userkey($)
{
	my $ikey = $_[0];

	if ($movers{'init'}) {
		$movers{'num'} = $ikey;
		$movers{'init'} = 0;
		if ($movers{'num'} !~ /\d/) {
			$key_mess = 'Need a number!';
			$movers{'num'} = 0;
		} else {
			$key_mess = 'Interface to shift at position '.$ikey;
		}
	}
	elsif ($movers{'num'}) {
		$movers{'dir'} = $ikey;
		order_interfaces();
		$movers{'num'} = 0;
		if ($movers{'dir'} eq 'u') {
			$key_mess = 'Shifting interface up';
		}
		elsif ($movers{'dir'} eq 'd') {
			$key_mess = 'Shifting interface down';
		}
	}
	elsif ($ihelp_flg) {
		if ($ikey eq "\n") {
			$ihelp = 0;
			$ihelp_flg = 0;
		}
	}
	elsif ($ikey eq '?' || $ikey eq 'h') {
		$ihelp = 1;
		$ihelp_flg = 1;
	}
	elsif ($ikey eq 'q') {
		quit_it();
	}
	elsif ($ikey eq 'm') {
		$cmd_options{'max'} = abs($cmd_options{'max'}-1);
		if ($cmd_options{'max'}) { 
			$key_mess = 'm => Displaying [m]ax Bandwidth';
		} else {
			$key_mess = 'm => Not Displaying [m]ax Bandwidth';
		}
	}
	elsif ($ikey eq 'a') {
		$cmd_options{'avg'} = abs($cmd_options{'avg'}-1);
		if ($cmd_options{'avg'}) {
			$key_mess = 'a => Displaying [a]verage Bandwidth';
		} else {
			$key_mess = 'a => Not Displaying [a]verage Bandwidth';
		}
	}
	elsif ($ikey eq 'i') {
		$cmd_options{'bits'} = abs($cmd_options{'bits'}-1);
		if ($cmd_options{'bits'}) {
			$key_mess = 'i => Displaying Values in Kb[i]ts/sec (Kbps)';
		} else {
			$key_mess = 'i => Not Displaying Values in Kb[i]ts/sec (Kbps)';
		}
	}
	elsif ($ikey eq 'y') {
		$cmd_options{'bytes'} = abs($cmd_options{'bytes'}-1);
		if ($cmd_options{'bytes'}) {
			$key_mess = 'y => Displaying Values in KB[y]tes/sec (KBps)';
		} else {
			$key_mess = 'y => Not Displaying Values in KB[y]tes/sec (KBps)';
		}
	}
	elsif ($ikey eq 'd') {
		$cmd_options{'data'} = abs($cmd_options{'data'}-1);
		if($cmd_options{'data'}) {
			$key_mess = 'd => Displaying [d]ata transferred';
		} else {
			$key_mess = 'd => Not Displaying [d]ata transferred';
		}
	}
	elsif ($ikey eq 'r') {
		# Reset all values
		reset_all();
		$key_mess = 'r -> [r]esetting all values';
	}
	elsif ($ikey =~ /^\d$/ && $ikey != 0) {
		# If ikey is a digit then it is an interval
		$cmd_options{'interval'} = $ikey;
		$key_mess = $ikey.' => Refresh interval is now ['.$ikey.'] seconds';
	}
	elsif ($ikey eq 's') {
		# shift interface up/down
		$movers{'init'} = 1;
		$key_mess = $ikey.' => [s]hift interface up/down. Enter interface number ';
	}	
	else {
		$key_mess = $ikey.' => Invalid keystroke';
	}

	# something has to be shown!
	# resort to Kbits by default
	$cmd_options{'bits'} = 1 if ($cmd_options{'bits'} == 0 && $cmd_options{'bytes'} == 0);
}

sub order_interfaces()
{
	if ($debug) {
		debug_it('order_interfaces():');
		debug_it('Before:');
		debug_it('%movers DUMP:');
		debug_it(Dumper(%movers));
		debug_it('@interface_names DUMP:');
		debug_it(Dumper(@interface_names));
	}

	# Check for boundary conditions
	if (($movers{'dir'} eq 'u') && 
		($movers{'num'} > 1) && ($movers{'num'} <= $#interface_names+1))
	{
		($interface_names[$movers{'num'}-2], $interface_names[$movers{'num'}-1]) =
		($interface_names[$movers{'num'}-1], $interface_names[$movers{'num'}-2]);
	}
	elsif (($movers{'dir'} eq 'd') &&
		($movers{'num'} >= 1) && ($movers{'num'} <= $#interface_names))
	{
		($interface_names[$movers{'num'}], $interface_names[$movers{'num'}-1]) =
		($interface_names[$movers{'num'}-1], $interface_names[$movers{'num'}]);
	}
	
	if ($debug) {
		debug_it('order_interfaces():');
		debug_it('After:');
		debug_it('%movers DUMP:');
		debug_it(Dumper(%movers));
		debug_it('@interface_names DUMP:');
		debug_it(Dumper(@interface_names));
	}
}

=head1 NAME

ibmonitor - an interactive bandwidth monitor which runs on a linux console

=head1 DESCRIPTION

ibmonitor is an interactive linux console application which shows
bandwidth consumed and total data transferred on all interfaces.

Its main features are:
  - Shows received, transmitted and total bandwidth of each interface
  - Calculates and displays the combined value of all interfaces
  - Displays total data transferred per interface in KB/MB/GB
  - Bandwidth Values can be displayed in Kbits/sec(Kbps) and/or KBytes/sec(KBps)
  - Can show maximum bandwidth consumed on each interface since start of utility
  - Can show average bandwidth consumption on each interface since start of utility
  - The output with all features (max, avg and display in Kbps and KBps)
    easily fits on a 80x24 console or xterm
  - Can interactively change its output display format depending on key
    pressed by user.

=head1 README

ibmonitor is a command line program which will run
  on a linux console or xterm (rxvt, konsole, gnome-terminal, etc)

  usage: ibmonitor [--bits] [--bytes] [--max] [--avg]
                   [--interval n] [--data] [--colors | --nocolors]
                   [--dev regex] [--file proc] [--help] [--version]

  The following command line options (and their explanation) are available:

  --bits            -> Show output values in KBits/sec. This is the default.
  --bytes           -> Show output values in KBytes/sec
  --max             -> Show maximum values per interface
  --avg             -> Show average values per interface
  --interval n      -> Set time interval as n seconds. The default is 2 seconds.
  --data            -> Show data transferred in KB/MB/GB
  --colors          -> Show some fancy coloring! This is the default
  --nocolors        -> No fancy coloring please!
  --dev regex       -> Show output from device matching regex
  --file proc       -> Specify which file to use in the proc filesystem
                       for the interface byte counter
  --help            -> Show help and exit
  --version         -> Show version number and exit

  While running, ibmonitor can also read the input key from the user
  and dynamically change the output display format depending on the key
  pressed.

  The following keys are supported. Note that ibmonitor responds directly
  to the single keystroke. ie. 'Enter' key need not be pressed.
  NOTE: This will only work if you have the Term::ReadKey Perl module
        installed and working, hence the requirement.

  q         -> [q]uit
  1 - 9     -> Set sleep time interval(in seconds) to the digit entered
  m         -> Toggle display of [m]ax bandwidth
  a         -> Toggle display of [a]verage bandwidth
  i         -> Toggle display of values in KB[i]ts/sec (Kbps)
  y         -> Toggle display of values in KB[y]tes/sec (KBps)
  d         -> Toggle display of [d]ata transferred
  s         -> Shift interface up/down.
               This should be followed by the interface number,
               and then the direction (u or d)
  r         -> [R]eset all values
  ?/h       -> Help Screen for interactive commands

=head1 PREREQUISITES

This script requires the following modules:
C<Time::HiRes>
C<Getopt::Long>
C<Term::ANSIColor>
C<Data::Dumper>

=head1 COREQUISITES

C<Term::ReadKey>

=head1 AUTHOR

Rohan Romanus Almeida <arcofdescent@gmail.com>

=head1 SCRIPT CATEGORIES

Networking
Unix/System_administration

=cut