#!/usr/bin/perl
##
# @file generatePerf.pl
# @brief Takes text format Flow files generated by xflowPerfDataProcessor.pl & generates MARS format perf and conf files.
# @author Manojkumar M Waghmare
# @version 1.0
# @date 2013-07-18
#
# @usage generatePerf.pl -d <dir containing raw text files> -s <number of raw files> -a <aggregation interval> 
#		-n <device name> -i <device ip> -p <probe site> -v <probe version> -o <output dir where MARS perf files are written>
use strict; 

use Getopt::Std;
use File::Basename;
use File::Path;
use Time::HiRes qw(gettimeofday); #for current date and time.
use POSIX "strftime";

###########################################################
# Subroutines Forward Declarations
##########################################################{#
sub dumpPerfData;
sub dumpConfData;
sub printProbeMetaInfo;
sub printProbeMetaInfoForConf;
sub logMsg;
sub numBytes;

#}##########################################################

###########################################################
# Globals & Constants
##########################################################{#
use constant PROBE_TYPE	=> "xFlow";
use constant BANDFACTOR => 1024;

#use constant MEGHA_DEPLOY_DIR	=>	/usr/local/megha
use constant MEGHA_DEPLOY_DIR	=>	"/usr/local/megha";
use constant MEGHA_LOG_DIR		=>	&MEGHA_DEPLOY_DIR . "/logs";

#Constants For Cmd Line Args Switches.
use constant {
	RAWFILES_DIR	=> 'd',
	NUM_SAMPLES		=> 's',
	AGGR_INTERVAL	=> 'a',
	PROBE_VERSION	=> 'v',
	DEVICE_IP		=> 'i',
	DEVICE_NAME		=> 'n',
	PROBE_SITENAME	=> 'p',
	OUTPUT_DIR		=> 'o'
};

my %CmdLineArgsDesc = ( 
	&RAWFILES_DIR	=> "Directory Containing Raw Text Files",
	&NUM_SAMPLES	=> "Number of Samples/Raw Files to be processed",
	&AGGR_INTERVAL	=> "Aggregation Interval in Minutes",
	&PROBE_VERSION	=> "Version of xFlow Probe",
	&DEVICE_IP		=> "IP of the xFlow Device",
	&DEVICE_NAME	=> "Name of the xFlow Device",
	&PROBE_SITENAME	=> "Probe Site Name",	
	&OUTPUT_DIR		=> "Output Directory where MARS perf files are written"
);

my %UnitMultiplier = (
	'M'	=>	"1000000",
	'G'	=>	"1000000000",
	'T'	=>	"1000000000000"
);

my %CmdLineArgs;

#Constants for Conf File Attributes
use constant {
	TIMESTAMP_TAG	=> 't:',
	RESOURCE_TAG	=> 'R:',
	ATTRIBUTE_TAG	=> 'a:',
	RELATION_TAG	=> 'r:',
	ATTR_NAME		=> 'name',
	ATTR_SRCIP		=> 'sourceIP',
	ATTR_DSTIP		=> 'destinationIP',
	ATTR_SRCAS		=> 'sourceAS',
	ATTR_DSTAS		=> 'destinationAS',
	ATTR_SRCVLAN	=> 'sourceVLan',
	ATTR_DSTVLAN	=> 'destinationVLan'
};

#constants for log level
use constant {
	DEBUG      =>  "<DEBUG>",
	INFO       =>  "<INFO>",
	WARNING    =>  "<WARNING>",
	ERROR      =>  "<ERROR>",
	FATAL      =>  "<FATAL>"   
};

my $Usage = "\n$0 ";
foreach my $key (sort keys %CmdLineArgsDesc) {
	$Usage = $Usage . " -$key <$CmdLineArgsDesc{$key}>\n";	
}
$Usage = $Usage . " [-h]\n";

my %UDPHash = ();
my %TCPHash = ();
my %ICMPHash = ();
my %ICMP6Hash = ();
my %IGMPHash = ();
my %ConfHash = ();

my %SupportedProtocols = ( 
	TCP		=> 	\%TCPHash,
	UDP		=>	\%UDPHash,
	ICMP	=>	\%ICMPHash,
	ICMP6	=>	\%ICMP6Hash,
	IGMP	=>	\%IGMPHash
);

############################################################
# MAIN Program
##########################################################{#
getopts('hd:s:a:v:i:n:p:o:', \%CmdLineArgs);

#if help requested i.e. -h switch is present 
if( $CmdLineArgs{h} ) {
	print $Usage;
	exit 1;
}

#if any of the required options are missing
if(keys %CmdLineArgs < keys %CmdLineArgsDesc) {
	print STDERR "\nOne or more required options are missing.\n";
	print STDERR $Usage;
	exit 1;
}

my ($seconds, $microseconds) = gettimeofday;
my $LOGDAY = strftime("%Y%m%d", localtime($seconds));
my $LOGFile = &MEGHA_LOG_DIR . "/xflowPerGen_$LOGDAY". ".log";
my $LOGFILE_HANDLE;
open($LOGFILE_HANDLE, ">>", $LOGFile) or die "[FATAL ERROR]: Failed to open the LOG file: [$LOGFile].";

my $OutFilesDir = $CmdLineArgs{&OUTPUT_DIR};
if(!-d $OutFilesDir) {
	logMsg(FATAL, "Output Files Dir: $OutFilesDir does NOT exist.");
	exit 1;
}

my $RawFilesDir = $CmdLineArgs{&RAWFILES_DIR};
if(!-d $RawFilesDir) {
	logMsg(FATAL,"Raw Files Dir: $RawFilesDir does NOT exist.");
	exit 1;
}

my $NumberOfSamples = $CmdLineArgs{&NUM_SAMPLES};
my $AggrInterval = $CmdLineArgs{&AGGR_INTERVAL};
my $ProbeSite = $CmdLineArgs{&PROBE_SITENAME};
my $XFlowDeviceName = $CmdLineArgs{&DEVICE_NAME};
my $XFlowDeviceIP = $CmdLineArgs{&DEVICE_IP};

my %ProbeMetaInfoHash = ( 
	"probe_id" => $XFlowDeviceIP,
	"probe_type" => &PROBE_TYPE,
	"probe_site" => $ProbeSite,
	"probe_server" => $XFlowDeviceIP,
	"probe_version" => $CmdLineArgs{&PROBE_VERSION},
	"collection_interval" => $AggrInterval * 60,
	"number_of_samples" => $NumberOfSamples
);

my $probeMetaInfoHeader = "\%meta\n" . 
		"probe_id" . ":" . $ProbeMetaInfoHash{'probe_id'} . "\n" .
		"probe_type" . ":" . $ProbeMetaInfoHash{'probe_type'} . "\n" .
		"probe_site" . ":" . $ProbeMetaInfoHash{'probe_site'} . "\n" .
		"probe_server" . ":" . $ProbeMetaInfoHash{'probe_server'} . "\n" .
		"probe_version" . ":" . $ProbeMetaInfoHash{'probe_version'} . "\n" .
		"collection_interval" . ":" . $ProbeMetaInfoHash{'collection_interval'} . "\n" .
		"number_of_samples" . ":" . $ProbeMetaInfoHash{'number_of_samples'} . "\n" .
		"\%meta\n";

my $probeMetaInfoHeaderForConf = "\%meta\n" .
		"probe_id" . ":" . $ProbeMetaInfoHash{'probe_id'} . "\n" .
		"probe_type" . ":" . $ProbeMetaInfoHash{'probe_type'} . "\n" .
		"probe_site" . ":" . $ProbeMetaInfoHash{'probe_site'} . "\n" .
		"probe_server" . ":" . $ProbeMetaInfoHash{'probe_server'} . "\n" .
		"probe_version" . ":" . $ProbeMetaInfoHash{'probe_version'} . "\n" .
		"collection_interval" . ":" . $ProbeMetaInfoHash{'collection_interval'} . "\n" .
		"\%meta\n";

my @rawFiles = glob($RawFilesDir."/*.raw");
@rawFiles = sort @rawFiles;

if(scalar @rawFiles != $NumberOfSamples) {
	logMsg(WARNING, "Number files in $RawFilesDir does NOT match with number of samples passed in as argument.");
}

my $SampleStartTime = "";
my $firstRawFile = basename($rawFiles[0]);
if($firstRawFile =~ /^nfcapd.(\d{8})(\d{4}).raw$/) {
	$SampleStartTime = $1 . "_" . $2 . "00";
}
else {
	logMsg(FATAL, "Raw File Name:$firstRawFile is not as expected.");
	exit 1;
}

for(my $fileNum=0; $fileNum < $NumberOfSamples; $fileNum++) {
	my $rawFile = $rawFiles[$fileNum];
	my $rawFileBaseName = basename($rawFile);
	logMsg(DEBUG, "Processing RAW file: $rawFile");

	open(RAW_HANDLE,"<$rawFile") || die "\nFailed to open file $rawFile in read mode";

	while(<RAW_HANDLE>) {
		chomp;
		$_ =~ tr/ //ds; 
		my @values = split(',',$_);
		if( !exists $SupportedProtocols{$values[0]} ) {
			logMsg(DEBUG, "Skipping corrupt line: $_");
			next;
		}

		#For ICMP strangely port numbers have dots & we got to get rid of them.
		my $srcPort = ($values[1] =~ /(^\d{1,})\.\d*/) ? $1 : $values[1];
		my $dstPort = ($values[2] =~ /(^\d{1,})\.\d*/) ? $1 : $values[2];
		my $Key1 = $srcPort . "_" . $dstPort;  #SrcPort_DstPort
		my $Key2 = $values[3] . "_" . $values[4];  #SrcIP_DstIP	

		if( !exists $ConfHash{$Key2} ) {
			$ConfHash{$Key2}->{&ATTR_NAME} = $Key2;
			$ConfHash{$Key2}->{&ATTR_SRCIP} = $values[3];
			$ConfHash{$Key2}->{&ATTR_DSTIP} = $values[4];
			$ConfHash{$Key2}->{&ATTR_SRCAS} = $values[6];
			$ConfHash{$Key2}->{&ATTR_DSTAS} = $values[7];
			$ConfHash{$Key2}->{&ATTR_SRCVLAN} = $values[8];
			$ConfHash{$Key2}->{&ATTR_DSTVLAN} = $values[9];
		}

		my $ProtoHashRef = $SupportedProtocols{$values[0]};
		my $dataSize = numBytes($values[5]);
		#$dataSize /= &BANDFACTOR;
		#Calculate Bytes/sec by dividing dataSize by aggr interval in seconds
		$dataSize = $dataSize / ($AggrInterval * 60); 
		@{$ProtoHashRef->{$Key1}->{$Key2}}[$fileNum] = $dataSize;

	} #foreach line in the rawfile

	close(RAW_HANDLE);
} #foreach raw file

my $PERFDATA_DIRNAME = $OutFilesDir . "/Perf_" . $SampleStartTime;
system("rm -rf $PERFDATA_DIRNAME") if(-d $PERFDATA_DIRNAME);

mkdir $PERFDATA_DIRNAME || logMsg(FATAL, "Failed to create directory: $PERFDATA_DIRNAME") && die "Failed to create dir: $PERFDATA_DIRNAME";

my $resPerfDir = $PERFDATA_DIRNAME . "/xFlow";
mkdir $resPerfDir || logMsg(FATAL, "Failed to create directory: $resPerfDir") && die "Failed to create dir: $resPerfDir";

my $numPerfFiles = 0;
foreach my $proto (keys %SupportedProtocols) { #dump perf files for each protocol
	if( %{$SupportedProtocols{$proto}} ) { #if protocol hash is not empty
		$numPerfFiles += &dumpPerfData($SupportedProtocols{$proto}, $proto, $resPerfDir);
	}
}

if( $numPerfFiles != 0 ) {
	my @CMD_mvPerfDir = ("mv", $PERFDATA_DIRNAME, $PERFDATA_DIRNAME . ".DONE");
	if(system(@CMD_mvPerfDir) != 0) { 
		logMsg(FATAL, "Failed to rename $PERFDATA_DIRNAME to $PERFDATA_DIRNAME.DONE");
		die "\nFailed to rename $PERFDATA_DIRNAME to $PERFDATA_DIRNAME.DONE\n";
	}
	logMsg(INFO, "Number of perf files created in $PERFDATA_DIRNAME = $numPerfFiles");
}
else {
	logMsg(INFO, "Empty perf file directory $PERFDATA_DIRNAME");
	my @CMD_rmPerfDir = ("rm", "-rf", $PERFDATA_DIRNAME);
	if(system(@CMD_rmPerfDir) != 0) { 
		logMsg(ERROR, "Failed to delete empty perf files directory: $PERFDATA_DIRNAME");
	}	
}


if(%ConfHash) { #if Config Hash is not empty
	#my $confFileName = $ProbeSite . "_" . $XFlowDeviceName . "_snapshot_" . $SampleStartTime . ".txt";
	my $confFileName = "Conf_" . $SampleStartTime . "_" . $ProbeSite . "_" . $XFlowDeviceName . "_minisnapshot_xFlow.txt";
	my $confFile = $OutFilesDir . "/$confFileName";

	&dumpConfData(\%ConfHash, $confFile);
	rename($confFile, $confFile . ".DONE") || 
		logMsg(FATAL, "Failed to rename $PERFDATA_DIRNAME to $PERFDATA_DIRNAME.DONE") && die "Failed to rename";
}

exit 0;
#}##########################################################


###########################################################
# Subroutine Definitions
###########################################################

sub numBytes
{
	my ($dataSize) = @_;
	my $unit = substr $dataSize,-1,1;
	if( $unit eq 'M' or $unit eq 'G' or $unit eq 'T' ) {
		chop($dataSize);
		return $dataSize * $UnitMultiplier{$unit};
	}

	return $dataSize;
}

sub printProbeMetaInfoForConf
{
	my($fileHandle) = @_;
	print $fileHandle $probeMetaInfoHeaderForConf;
}

sub printProbeMetaInfo
{
	my($fileHandle, $isConf) = @_;
	print $fileHandle $probeMetaInfoHeader;
}

sub dumpPerfData
{
	my($hashRef, $protocol, $PERFDATA_DIRNAME) = @_;
	print STDOUT "\nNumber of $protocol port combinations: ", scalar keys %$hashRef, "\n";

	my $lcProtocol = lc($protocol);
	my $numPerfFiles = 0;
	foreach my $PortCombination (keys(%$hashRef)) {
		my $perfFileName = "byteFlow_" . $PortCombination . "_" . $lcProtocol . ".txt";

		open(my $PERF_HANDLE, '>', "$PERFDATA_DIRNAME/$perfFileName") || die "\nFailed to open file $perfFileName in write mode"; 
		&printProbeMetaInfo($PERF_HANDLE);

		foreach my $IPCombination (keys %{$hashRef->{$PortCombination}}) {
			print $PERF_HANDLE "xFlow#";
			print $PERF_HANDLE "$IPCombination";

			for(my $i=0; $i < $NumberOfSamples; ++$i) { 
				my $counterVal = $hashRef->{$PortCombination}->{$IPCombination}->[$i];
				if( !defined $counterVal) {
					$counterVal = "";	#put a data hole
				}

				print $PERF_HANDLE "," . $counterVal;
			}
			print $PERF_HANDLE "\n";
		}#foreach IP Combination	

		print STDERR ".";
		close($PERF_HANDLE);
		$numPerfFiles++;
	}#foreach Port Combination
	return $numPerfFiles;
}

sub dumpConfData
{
	my ($hashRef, $ConfFile) = @_;
	print STDOUT "\nNumber of Resources/IP combinations: ", scalar keys %$hashRef, "\n";

	open(my $CONF_HANDLE, '>', "$ConfFile") || die "\nFailed to open conf file $ConfFile in write mode"; 
	&printProbeMetaInfoForConf($CONF_HANDLE);

	my $configData = &TIMESTAMP_TAG . $SampleStartTime . "\n"; 

	foreach my $IPCombination (keys %$hashRef) {
		$configData = $configData . &RESOURCE_TAG . "xFlow#$IPCombination\n"; 

		my $ResRef = $hashRef->{$IPCombination};
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_NAME . "=" . $ResRef->{&ATTR_NAME} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_SRCIP . "=" . $ResRef->{&ATTR_SRCIP} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_DSTIP . "=" . $ResRef->{&ATTR_DSTIP} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_SRCAS . "=" . $ResRef->{&ATTR_SRCAS} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_DSTAS . "=" . $ResRef->{&ATTR_DSTAS} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_SRCVLAN . "=" . $ResRef->{&ATTR_SRCVLAN} . "\n";
		$configData = $configData . &ATTRIBUTE_TAG . &ATTR_DSTVLAN . "=" . $ResRef->{&ATTR_DSTVLAN} . "\n";

	}#foreach IP Combination	

	print $CONF_HANDLE $configData;
	close($CONF_HANDLE);
}

###
# Logs a message to the log file in the following format:
# [YYYY-MM-DD HH:MM:ss] <INFO|DEBUG|WARNING|ERROR|FATAL> [DeviceName] File(Line): <Log message>
sub logMsg
{
	my( $file, $line )= ( caller )[1,2];
	my($loglevel, $msg) = @_;

	if(	($loglevel eq FATAL) or ($loglevel eq ERROR) or 
		($loglevel eq DEBUG)) {
		print STDERR "\n$msg\n";
	}

    my ($seconds, $microseconds) = gettimeofday;
    my $currentTime = strftime("%Y-%m-%d %H:%M:%S", localtime($seconds));
    my $LogMsg = "[$currentTime,$microseconds] $loglevel [$XFlowDeviceName] $file($line): $msg\n";

    print $LOGFILE_HANDLE $LogMsg;
    return 0;
}

#
#%UDPHash = 
#{
#	'10_53' => 
#	{
#		'192.168.1.171_192.168.20.66' => [
#					457,
#					97
#			]
#	};
#
#	'10_20' => 
#	{
#		'192.168.1.171_192.168.20.66' => [
#					68,
#					69
#				],
#		'192.168.20.66_192.168.1.41' => [
#					100,
#					undef,
#					200
#				]
#	};
#
#}; UDPHash Ends Here
