#!/usr/local/bin/perl
#
#  Copyright (c) 2004, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of SWITCH nor the names of its contributors may be
#     used to endorse or promote products derived from this software without
#     specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#
#  $Author: peter $
#
#  $Id: NfSenRRD.pm 52 2006-03-24 10:38:43Z peter $
#
#  $LastChangedRevision: 52 $

package NfSenRRD;

use RRDs;
use Log;
use NfSen;

our @RRD_DS = (
	'flows',
	'flows_tcp',
	'flows_udp',
	'flows_icmp',
	'flows_other',
	'packets',
	'packets_tcp',
	'packets_udp',
	'packets_icmp',
	'packets_other',
	'traffic',
	'traffic_tcp',
	'traffic_udp',
	'traffic_icmp',
	'traffic_other',
);

sub SetupRRD {

	my $path	= shift;
	my $db		= shift;
	my $start 	= shift;
	my $force	= shift;

	$Log::ERROR = undef;
	my @DS;
	foreach my $ds ( @RRD_DS ) {
		push @DS, "DS:$ds:ABSOLUTE:600:U:U";
	}

	my $ERR;

	# Create the RRD DB only if it not exists, or we are forced
	# to delete the old one, and create a new one
	unlink "$path/$db.rrd" if $force;
	if ( -f "$path/$db.rrd" ) {
		print  "RRD DB '$db.rrd' already exists!\n";
		return;
	} 
	
	# RRD DB layout:
	#   1 x 5min =  5 min samples	 30 * 288 ( per day ) = 8640 => 30 days
	#   6 x 5min = 30 min samples 	 30 *  48 ( per day ) = 1440 => 30 days	
	#  24 x 5min =  2 hour samples 	 30 *  12 ( per day ) = 360  => 30 days
	# 288 x 5min =	1 day samples	700 *   1 ( per day ) = 700  => 700 days
	# Total data available 790 days
	my $rrd_filename = "$path/$db.rrd";
	RRDs::create ( $rrd_filename, "--start", $start,
		@DS,
		"RRA:AVERAGE:0.5:1:8640",
		"RRA:AVERAGE:0.5:6:1440",
		"RRA:AVERAGE:0.5:24:360",
		"RRA:AVERAGE:0.5:288:700",
		"RRA:MAX:0.5:1:8640",
		"RRA:MAX:0.5:6:1440",
		"RRA:MAX:0.5:24:360",
		"RRA:MAX:0.5:288:700"
	);

	$ERR=RRDs::error;
	if ( $ERR ) {
		$Log::ERROR = "ERROR while creating RRD DB $db.rrd: $ERR";
	}
	# System Errors are not reported detailed enough, so report system errno, if 
	# file does not exists or is empty
	if ( !-f $rrd_filename || -z $rrd_filename ) {
		$err = $!;
		warn "Unable to create DB file: $err\n";
		if ( !defined $Log::ERROR) {
			$Log::ERROR = "Unable to create DB file: $err";
		}
	}

} # End of SetupRRD

sub UpdateDB {
	my $path 		= shift;
	my $db 			= shift;
	my $timestamp	= shift;
	my $srclist 	= shift;
	my $valuelist 	= shift;

	RRDs::update ("$path/$db.rrd", "--template", 
		"$srclist",
		"$timestamp:$valuelist"
	);
	
	my $ERR=RRDs::error;
	$Log::ERROR = $ERR ? "ERROR while updating RRD DB $db.rrd: $ERR" : undef;

	return defined $Log::ERROR ? 0 : 1;

} # End of UpdateDB

# 
# static sub - used internally only
sub GenGraph {
	my $profile 	= shift;
	my $vlabel  	= shift;
	my $what 		= shift;
	my $extension 	= shift;
	my $title		= shift;
	my $tstart		= shift;
	my $tend		= shift;
	
	my $PROFILEDIR = "$NfConf::PROFILESTATDIR/$profile";
	my %profileinfo = NfSen::ReadProfile($profile);
	my @DEFS;
	foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
		if ( $what eq 'traffic' ) {
			push @DEFS, "DEF:tmp${source}=${PROFILEDIR}/${source}.rrd:$what:AVERAGE";
			push @DEFS, "CDEF:$source=tmp${source},8,*";
		} else {
			push @DEFS, "DEF:$source=${PROFILEDIR}/${source}.rrd:$what:AVERAGE";
		}
	}

	my @STACK;
	my @AllSources = split /:/, $profileinfo{'sourcelist'};
	my $First = shift @AllSources;
	my $colour = $NfConf::sources{$First}{'col'};
	push @STACK, "AREA:${First}${colour}:${First}";

	foreach my $source ( @AllSources ) {
		$colour = $NfConf::sources{$source}{'col'};
		push @STACK, "STACK:${source}${colour}:${source}";
	}

	my $graph_filename = "$PROFILEDIR/${what}${extension}.gif";
	my ($averages,$xsize,$ysize) = RRDs::graph $graph_filename,
		@DEFS,
		"--title", 			"$title" ,
		"--vertical-label", "$vlabel",
		"--start",  		"$tstart",
		"--end",  			"$tend" ,
		"-w",  "576",  "-h",  "200",
		@STACK;

	my $ERR=RRDs::error;

	# System Errors are not reported detailed enough, so report system errno, if 
	# file does not exists or is empty
	if ( !-f $graph_filename || -z $graph_filename ) {
		warn "Unable to create graph: $!\n";
	}

	return $ERR ? $ERR : undef;

} # End of GenGraph

sub UpdateGraphs {
	my $profile   = shift;
	my $tend	  = shift;
	my $force_all = shift;

	my %graphs = (
		'flows'		=> 'Flows/s',
		'packets'	=> 'Packets/s',
		'traffic'	=> 'Bits/s'
	);

	my $timeslot = NfSen::UNIX2ISO($tend);
	my ($year, $month, $day, $hour, $min ) = $timeslot =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/;

	# convert to integer
	$hour += 0;
	$min  += 0;

	my $title;
	my @statinfo;
	my $err = undef;
	$Log::ERROR = undef;
	foreach my $type ( keys %graphs ) {
		# Update every cycle
		$title = "$graphs{$type}: " . scalar localtime ($tend-86400) . " - " . scalar localtime $tend;
		$err = GenGraph($profile, $graphs{$type}, $type, '-day', $title, $tend - 86400, $tend);
		if ( $err ) {
			warn "Error GenGraph: Profile: $profile, $type-day: $err";
			$Log::ERROR = "Error GenGraph: Profile: $profile, $type-day: $err";
		}

		# update every 30 min
		if ( $force_all || ($min == 30 || $min == 0) ) {
			$title = "$graphs{$type}: " . scalar localtime ($tend-604800) . " - " . scalar localtime $tend;
			$err = GenGraph($profile, $graphs{$type}, $type, '-week', $title, $tend - 604800, $tend);
			if ( $err ) {
				warn "Error GenGraph: Profile: $profile, $type-day: $err";
				$Log::ERROR = "Error GenGraph: Profile: $profile, $type-day: $err";
			}
		}

		# update every 2h
		if ( $force_all || ($min == 0 && ( $hour % 2 ) == 0) ) {
			$title = "$graphs{$type}: " . scalar localtime ($tend-2592000) . " - " . scalar localtime $tend;
			$err = GenGraph($profile, $graphs{$type}, $type, '-month', $title, $tend - 2592000, $tend);
			if ( $err ) {
				warn "Error GenGraph: Profile: $profile, $type-day: $err";
				$Log::ERROR = "Error GenGraph: Profile: $profile, $type-day: $err";
			}
		}

		# update every 24h
		if ( $force_all || ($hour == 0 && $min == 0) ) {
			$title = "$graphs{$type}: " . scalar localtime ($tend-31536000) . " - " . scalar localtime $tend;
			$err = GenGraph($profile, $graphs{$type}, $type, '-year', $title, $tend - 31536000, $tend);
			if ( $err ) {
				warn "Error GenGraph: Profile: $profile, $type-day: $err";
				$Log::ERROR = "Error GenGraph: Profile: $profile, $type-day: $err";
			}
		}
	}

	return defined $Log::ERROR ? 1 : 0;

} # End of UpdateGraphs

