#!/usr/bin/perl -w 
# Author: Nigel Kukard  <nkukard@lbsd.net>
# Date: 23/12/2005
# Desc: Linux interface bandwidth monitor
# License: GPL


use strict;
use Getopt::Long;

use RRDs;
use POSIX ();

my %optctl = ();
GetOptions(\%optctl, "monitor", "graph", "create-rrd", "populate-rrd", "start:s", "end:s");

# Check if user wants usage
if (defined($optctl{'help'})) {
	displayUsage();
}


my $dev = "eth0";
my $STEP = 300;
my $STEP_ACCURACY = $STEP / 5;
my $RRDFile = "/var/log/bandwidth.rrd";


# Check if we generating our rrd
if (defined($optctl{'create-rrd'})) {
	my $startTime;

	# Check and sanitize start time
	if (defined($optctl{'start'})) {
		$startTime = decodeTimeStamp($optctl{'start'});	

		if (!$startTime) {
			print("Error: Invalid start time specified!\n");
			displayUsage();	
		}
	} else {
		$startTime = "now-10s";
	}

	# Options for rrdtool
	my @options = (
		"--step", $STEP,
		"--start", $startTime,

		"DS:traffic_in:ABSOLUTE:600:0:U",
		"DS:traffic_out:ABSOLUTE:600:0:U",

		"RRA:AVERAGE:0.5:1:600", # 5 min, 2day 2hr
		"RRA:MIN:0.5:1:600",  # 5 min, 2day 2hr
		"RRA:MAX:0.5:1:600",  # 5 min, 2day 2hr
		
		"RRA:AVERAGE:0.5:6:600", # 30 min, 12.5 day
		"RRA:MIN:0.5:6:600",  # 30 min, 12.5 day
		"RRA:MAX:0.5:6:600",  # 30 min, 12.5 day
		
		"RRA:AVERAGE:0.5:24:600", # 2 hr, 50 day
		"RRA:MIN:0.5:24:600",  # 2 hr, 50 day
		"RRA:MAX:0.5:24:600",  # 2 hr, 50 day
		
		"RRA:AVERAGE:0.5:288:732", # 1 day, 732 days
		"RRA:MIN:0.5:288:732",  # 1 day, 732 days
		"RRA:MAX:0.5:288:732",  # 1 day, 732 days
	);

	# Create rrd file
	print("Creating RRD file...");
	RRDs::create($RRDFile,@options);
	my $error = RRDs::error;
	if ($error) {
		print("Error: $error\n");
		exit 1;
	}
	print("done\n");

# Populate our RRD file
} elsif (defined($optctl{'generate-rrd'})) {
	my $startTime;
	my $endTime;

	# Check and sanitize start time
	if (defined($optctl{'start'})) {
		$startTime = decodeTimeStamp($optctl{'start'});	

		if (!$startTime) {
			print("Error: Invalid start time specified!\n");
			displayUsage();	
		}
	} else {
		print("Error: No start time specified!\n");
		displayUsage();	
	}

	# Now check for optional end time
	if (defined($optctl{'end'})) {
		$endTime = decodeTimeStamp($optctl{'end'});	

		if (!$endTime) {
			print("Error: Invalid end time specified!\n");
			displayUsage();	
		}
	}

	# Populate rrd file
	open(LOGFILE,"< /var/log/bandwidth.brt") or die "Failed to open bandwidth log: $!";
	print("Populating RRD file...");
	my $records = 0;
	my @values = ();
	my @in_values = ();
	my @out_values = ();
	my $max_in = 0;
	my $max_out = 0;	
	while (my $line = <LOGFILE>) {
		# Chomp newline
		chomp($line);

		# Split off timestamp so we can check below
		my ($timestamp,$in,$out) = split /:/,$line;

		# Make sure timestamp is in our range
		if ($timestamp > $startTime && (!defined($endTime) || $timestamp < $endTime)) {
			# Max's
			$max_in = $in if ($in > $max_in);
			$max_out = $out if ($out > $max_out);

			# Push data
			push(@values,$line);
			push(@in_values,$in);
			push(@out_values,$out);

			# Add multiple values at once
			if (@values > 25) {
				RRDs::update($RRDFile,@values);
				if (my $error = RRDs::error) {
					print("Error: $error\n");
					exit 1;
				}

				@values = ();
			}
			$records++;
		}
	}

	@in_values = sort {$b <=> $a} @in_values;
	my $num = ( int (     $records * ((100 - 95) / 100))  );
	splice(@in_values,0,$num);	
print("NUM: $num\n");
	print("LAST: ".$in_values[0]); 



	# Flush
	RRDs::update($RRDFile,@values);
	if (my $error = RRDs::error) {
		print("Error: $error\n");
		exit 1;
	}

	print("done, $records records\n");
	close(LOGFILE); 

# Graph
} elsif (defined($optctl{'graph'})) {
	my $startTime;
	my $endTime;

	# Check and sanitize start time
	if (defined($optctl{'start'})) {
		$startTime = decodeTimeStamp($optctl{'start'});	

		if (!$startTime) {
			print("Error: Invalid start time specified!\n");
			displayUsage();	
		}
	} else {
		print("Error: No start time specified!\n");
		displayUsage();	
	}

	# Now check for optional end time
	if (defined($optctl{'end'})) {
		$endTime = decodeTimeStamp($optctl{'end'});	

		if (!$endTime) {
			print("Error: Invalid end time specified!\n");
			displayUsage();	
		}
	}

	# Sanitize
	$endTime = "now" if (!defined($endTime));

	print("Graphing...");
	RRDs::graph(
		"/tmp/graph.png",
		
		"--start",$startTime,
		"--end",$endTime,

		"DEF:ti=$RRDFile:traffic_in:AVERAGE",
		"DEF:to=$RRDFile:traffic_out:AVERAGE",

		"VDEF:ti_max=ti,MAXIMUM",
		"VDEF:ti_min=ti,MINIMUM",
		"VDEF:ti_avg=ti,AVERAGE",
		"VDEF:ti_95pct=ti,95,PERCENT",
	
		"VDEF:to_max=to,MAXIMUM",
		"VDEF:to_min=to,MINIMUM",
		"VDEF:to_avg=to,AVERAGE",
		"VDEF:to_95pct=to,95,PERCENT",

		"COMMENT:           ",
		"COMMENT:Maximum     ",
		"COMMENT:Minimum    ",
		"COMMENT:Average    ",
		
		"COMMENT:95%\\n",
		"AREA:ti#00c000:Inbound ",
		"GPRINT:ti_max:\%6.2lf \%Sbps",
		"GPRINT:ti_min:\%6.2lf \%Sbps",
		"GPRINT:ti_avg:\%6.2lf \%Sbps",
		"GPRINT:ti_95pct:\%6.2lf \%Sbps\\n",
		"LINE1:ti_95pct#ff0000",

		"LINE1:to#0000ff:Outbound",
		"GPRINT:to_max:\%6.2lf \%Sbps",
		"GPRINT:to_min:\%6.2lf \%Sbps",
		"GPRINT:to_avg:\%6.2lf \%Sbps",
		"GPRINT:to_95pct:\%6.2lf \%Sbps\\n",
		"LINE1:to_95pct#ff0000",

	);
	if (my $error = RRDs::error) {
		print("Error: $error\n");
		exit 1;
	}


# Just sit back and monitor traffic, logging at same time
} elsif (defined($optctl{'monitor'})) {
	# -1 to start off
	my $lastTraffic_in = -1;
	my $lastTraffic_out = -1;
	
	
	# Loop
	do {
		open(DEVFILE,"< /proc/net/dev");
	
		while (my $line = <DEVFILE>) {
			# Look for device
			if ($line =~ s/^\s*$dev://) {
				my @counters = (split /\s+/, $line);
				print("Counters:@counters\n");	
		
				# Peel off counters	
				my ($dev_in, $dev_out) = ($counters[0], $counters[8]);
				print("Dev_in = $dev_in, Dev_out = $dev_out\n");
	
				# Get actual traffic usage
				if ($lastTraffic_in > 0 && $lastTraffic_out > 0) {
					my ($use_in, $use_out);
	
					# Check if we've mashed our counter
					if (($dev_in - $lastTraffic_in) >= 0) {
						$use_in = $dev_in - $lastTraffic_in;
					} else {
						$use_in = $dev_in + (2**32 - $lastTraffic_in);
					}
	
					# Check if we've mashed our counter
					if (($dev_out - $lastTraffic_out) >= 0) {
						$use_out = $dev_out - $lastTraffic_out;
					} else {
						$use_out = $dev_out  + (2**32 - $lastTraffic_out);
					}
	
					my $time = time();
					print("$time: Traffic => In: $use_in, Out: $use_out\n");

					open(LOGFILE,">> /var/log/bandwidth.brt");
					print(LOGFILE "$time:$use_in:$use_out\n");
					close(LOGFILE);
			
					RRDs::update($RRDFile,"$time\@$use_in:$use_out");
					if (my $error = RRDs::error) {
						print("Error updating RRD: $error\n");
						exit 1;
					}
				}
	
				# Reset last counters
				$lastTraffic_in = $dev_in;
				$lastTraffic_out = $dev_out 
			}
		}
	
		close(DEVFILE);
	
	} while (sleep($STEP_ACCURACY));
} else {
	displayUsage();
}





# Display usage
sub displayUsage {
	print("Usage: $0 [--create-rrd] [--monitor] [--populate-rrd] [--graph --start <timespec> [--end <timespec>]]\n");
	exit 0;
} 


# Decipher a command line time spec
sub decodeTimeStamp {
	my $timespec = shift;

	# Time format - xxxx/xx/xx xx:xx
	if (my ($year,$month,$day,$hour,$minute) = ($timespec =~ /^(\d{4})(?:\/(\d{1,2})(?:\/(\d{1,2})(?: (\d{1,2})(?::(\d{1,2}))?)?)?)?/)) {
	
		# Sanitize
		$year = $year - 1900 if (defined($month));
		$month-- if (defined($month));
		$day-- if (defined($day));

		#print("$year/$month/$day $hour:$minute\n");

		return POSIX::mktime(0,$minute,$hour,$day,$month,$year);
	}

	return undef;
}



# vim: ts=4

