# Copyright 2008-2012 Cumulus Systems Incorporated.
# All Rights Reserved.

# This file contains the code to process the FusionIO Raw Configuration data and convert it into the MARS Probe data format.
# The Raw configuration file is passed as input to this file. and an output file is obtained which contains all the configuration data in MARS format.

#!/usr/bin/perl -w

# It is used for strict compilation.
use strict;

# It is used to get base file name from the complete file name.
use File::Basename;

# It is used to get the current date and time.
use Time::HiRes qw(gettimeofday);
use POSIX "strftime";

# This is used to create folder recursively.
use File::Path qw(mkpath);

# ----------------------------------------------------------------------------------------------------------------------------------------------------
# GLOBAL Variables
# ----------------------------------------------------------------------------------------------------------------------------------------------------

# This variable is used to store the return status of function calls.
my $status = 0;

# This variable is used to store the base folder path on the MARS probe.
my $baseFolder = "";

# This variable is used to store the full name of the raw configuration file contained in the input zip folder.
my $fioConfRawFile = "";

# This variable is used to store the full name of the final processed file in MARS format contained in the output zip folder.
my $fioConfOutputFile = "";

# This variable is used to store the full name of the raw configuration file which is input as argument to the scipt.
my $fioConfRawZipFile = "";

# This variable is used to store the full name of the final processed file in MARS format which is the output argument of the script.
my $fioConfOutputZipFile = "";

# This is a temporary file to store the processed data in MARS format.
my $fioTempFile = "";

# This is a temporary folder to store the temp file.
my $fioTempFolder = "";

# This variable is used to store the file handle for FIO temp file.
my $DATA_HANDLE;

# This variable is used to store the file handle for FIO raw input file.
my $CONF_DUMP;

# This variable is used to store the file handle for FIO output data.
my $OUT_FILE;

# This variable is used to store the file handle of log file.
my $LOG_HANDLE;

# This variable is used to indicate whether meta tags are encountered.
my $isMeta = 0;

# This variable is used to indicate whether __MAINBEGIN__ tag is encountered.
my $isMainBegin = 0;

# This variable is used to indicated the start tag, i.e. command name.
my $startTag = "";

# This variable is used to indicate whether a command has begun.
my $isCmdBegin = 0;

# This variable is used to store the probe server.
my $probeServer = "";

# This variable is used to store the probe site.
my $probeSite = "";

# This variable is used to store the type of OS
my $osType = "";

# This variable is used to store the size of drive
my $driveSize = "";

# This variable is used to store the start label of the fiostatus command output.
my $label = "";

# This variable is used to store the total number of arguments expected by this script.
my $ARG_COUNT = 5;

# This variable is used to store the complete path of log folder.
my $fioLogFolder = "";

# This variable is used to store the FusionIO IP address.
my $fioIpAddress = "";

# This variable is used to store the return code for this function.
my $retVal = 0;	

# This variable is used to store the log file.
my $logFile = "";
		
# This variable is used to store the timestamp.
my $startTime;

# This variable is used to store if ioDimm modules exist.
my $isIoDimm = 0;

my $isVm = 0;
my $vmWare = "";
my $isVmOutput = 0;

# ----------------------------------------------------------------------------------------------------------------------------------------------------
# Main function
# ----------------------------------------------------------------------------------------------------------------------------------------------------

# Set MARS base folder.
$baseFolder = "/usr/local/megha";

# Set FIO log folder path.
$fioLogFolder = $baseFolder."/logs";
	
# Set the temp folder path
$fioTempFolder = $baseFolder."/temp/fio/data/conf/";
	
# If process folder does not exist, then create it.
unless (defined eval {mkpath($fioTempFolder)})
{
	logMsg("Error: Not able to create folder: [$fioTempFolder].");
	return 1;
}
			
# If log folder does not exist then create it.
if (!(-e $fioLogFolder))
{
    # Create the folder for log folder.
    unless (defined eval {mkpath($fioLogFolder)})
    {
        print("Error: Not able to create folder: [$fioLogFolder].");
        return 1;
    }
}
	
# If number of command line arguments are not correct then quit.
if ($ARG_COUNT != ($#ARGV + 1))
{
    logMsg("Error: Number of arguments to fioRawConfDataProcessor.pl are not correct. Total input arguments = ".($#ARGV + 1));
    $retVal = 1;
    return 1;
}
	
# Take the input arguments in variables.
$fioConfRawZipFile = $ARGV[0];
$fioConfOutputZipFile = $ARGV[4];

# extracting only filename i.e /usr/local/ab.zip => ab.zip
my $zipFileName = substr($fioConfRawZipFile, rindex($fioConfRawZipFile, '/')+1 );

# extracting filename ab.zip => ab
$zipFileName = substr ($zipFileName, 0, rindex ($zipFileName, '.'));   
			
$status = processRawConfData();
if (0 != $status)
{
	logMsg("Error: Call to processRawConfData() failed.");
	$retVal = 1;
}
logMsg("Info: processing raw conf data in MARS format complete.");
	
close($LOG_HANDLE);

logMsg("Info: Return status: [$status].");
exit $status;

# ----------------------------------------------------------------------------------------------------------------------------------------------------
# Sub-routines
# ----------------------------------------------------------------------------------------------------------------------------------------------------

# This function is used to process FIO raw output data and put it in MARS format.
#
# @return :
#   0 if Success
#   1 if Error
sub processRawConfData
{	
	# This variable is used to store the start index of the string "Product Number".
	my $idxProdNo = 0;

	# This variable is used to store the substring from idxProdNo upto the end of $_.
	my $subUniqueAdap = "";
	
	# This variable is used to store the product number
	my $prodNo = "";
	
	# This variable is used to store the start index of the string SN.
	my $idxSerNo = 0;
	
	# This variable is used to store the serial number(SN).
	my $serialNo = "";
	
	# This variable is used to store the start index of the ioDimm modules' ID.
	my $idxDimm = 0;
	
	# This variable is used to store the substring value of the ioDimm modules' ID.
	my $blockDev = "";
	
	# This variable is used to store the 0th element of the @driveVals array.
	my $driveInfo = "";
	
	# This variable is used to store the index of the string "Fusion-io".
	my $idxDrive = 0;
	
	# This variable is used to store the index of ",".
	my $idxComma = 0;
	
	# This variable is used to store the model.
	my $model = "";

	# This variable is used to store the mfgr.
	my $mfgr = "";

	# This variable is used to store the date.
	my $mDate = "";

	# This variable is used to store the list of files in a directory.
	my @tmpFileList;

	# This variable is used to store a single line from the input file.
	my $line;

	# This variable is used to store the occurrences of driver in adapter label.
	my $count = 0;
	
	# These variables are used to store the min, avg and max values.
	my $Min;
	my $Max;
	my $Avg;

	# -----------------------------------------------------------------------------------------------------
	# These variables are used to derive the relation of adapter with the fio dimm.
	# -----------------------------------------------------------------------------------------------------

	# This variable is used to indicate whether fio-status command output is complete.
	my $isFioComplete = 0;

	# This variable is used to indicate whether the label encountered is 'Adapter'.
	my $isAdapter = 0;

	# This variable is used to store the signature of intermediate fIoDimms
	my $fIoDimm = "";
	
	# This variable is used to store the fIoDimm label.
	my $fIoDimmLabel = "";

	# This variable is used to store the block device value.
	my $fIoDimmDev = "";

	if($zipFileName =~ /^Conf([0-9_]+)_.*_FusionIo$/) {
		$startTime = $1;			
	}
	else
	{	
		logMsg("Error: Filename not in proper format: $!.");
		return 1;
	}
	
	# create location where extracted files should be kept
	my $tmpDataDir = $baseFolder."/temp/fio/extract/conf/".$zipFileName."/";

	# If folder does not exist, then create it.
	unless (defined eval {mkpath($tmpDataDir)})
	{
		logMsg("Error: Not able to create folder: [$tmpDataDir]: $!.");
		$retVal = 1;
	}

	
	# extracting zip file to same location in tmp dir
	$retVal = system("unzip -o $fioConfRawZipFile -d $tmpDataDir");
	if($retVal)
	{
		logMsg("Error: Cannot unzip file: [$fioConfRawZipFile]: $!.");
		return 1;
	}
	logMsg("Info: Unzipped file: [$fioConfRawZipFile]."); 

	# read all the files form the tmp dir into an array.	
	opendir(DIR, $tmpDataDir) or die logMsg("Error: Cannot open directory: $!.");
	my @tmpDirFilesList = readdir(DIR);
	closedir(DIR);
	
	foreach my $tempFile(@tmpDirFilesList) 
	{
		# to skip hidden files starting with "."
		if ($tempFile =~ m/^\./) {   
			next;
		}
		# traversing temp dir: if file is a dir, then skip it.
		if(-d $tempFile) 
		{ 	
			next;
		}
		
		logMsg("Info: Processing File: [$tempFile]");
		logMsg("Info: Reading FIO raw configuration data from file: [$tmpDataDir$tempFile].");
		open(CONF_DUMP,"<",$tmpDataDir."/".$tempFile) or die logMsg("Error: Not able to open the file: [$tempFile].");

		# Dump the file line by line into an array, also extract some values into variable for future use.
		my @tempArray;
		while(<CONF_DUMP>)
		{
			if($_ =~ /^(fct.+)\s+(.*)\s\'(.*)\'\s(.*)/)
			{
				# Form the signature of the fIoDimms.
				$label = $1;
				$blockDev = $3;
				$label =~ s/fct(.+)\s+(.*)/fct$1/;

				$fIoDimmLabel = $label;
				$fIoDimmDev = $blockDev;
	
				# Concat the intermediate fIoDimms into a string.
				$fIoDimm .= ",fIoDimm#$fIoDimmLabel"."_$fIoDimmDev";
			}
			push(@tempArray,$_);
		}
		close(CONF_DUMP);

		# Set the name for temp file.
		$fioTempFile = $fioTempFolder."TempFile_".$startTime.".txt";

		open(DATA_HANDLE,">",$fioTempFile) or die logMsg("Error: file1 can't be created: [$fioTempFile].");
		
		# Loop through the content of FIO raw  output file.
		foreach my $ele (@tempArray)
		{
			chomp($ele);
			if($ele =~ /%meta/)
			{
				if($isMeta == 0)
				{
					$isMeta = 1;
				}
				else
				{
					$isMeta = 0;
					print DATA_HANDLE "$ele\n";
				}
			}

			# If 1st '%meta' tag is encountered.
			if($isMeta == 1)
			{
				if($ele =~ /probe_server:(.*)/)
				{
					$probeServer = $1;
					$fioIpAddress = $probeServer;
				}
				if($ele =~ /probe_site:(.*)/)
				{
					$probeSite = $1;
				}
				# replace the probe type from 'FIO' to'FusionIo'.
				if($ele =~ /(probe_type):(.*)/)
				{
					my $probeType = $2;
					$ele =~ s/$2/FusionIo/g;
				}
				print DATA_HANDLE "$ele\n";
			}
			else
			{
				# If timestamp encountered.
				if($ele =~ "t:([0-9_]+)")
				{
					print DATA_HANDLE "$ele\n";
				}
		
				# If '__MAINBEGIN__' tag encountered.
				if($ele =~ '__MAINBEGIN__')
				{
					$isMainBegin = 1;
					next;
				}
			}

			# If any '__BEGIN__' command tag is encountered, assign the command to startTag variable..
			if($isMainBegin == 1 && $ele =~ '__BEGIN__ .*')
			{
				$isCmdBegin = 1;
				$startTag = $ele;
			}

			if($isCmdBegin == 1)
			{
				if($_ =~ /^__BEGIN__ VMWARE_VERSION$/)
				{
					$isVmOutput = 1;
				}

				if($isVmOutput == 1)
				{
					if($_ !~ /^__END__ VMWARE_VERSION$/)
					{
						$vmWare = $_;
						if(defined ($vmWare) && $vmWare ne "")
						{
							$isVm = 1;
						}
						else
						{
							$isVm = 0;
						}
					}
				}

				# if startTag is UNAME, then parse the operating system/server type from it.
				if($startTag =~ '__BEGIN__ UNAME')
				{
					if($ele !~ '__END__ UNAME')
					{						
						if($ele =~ /^(.*)\s(.*)$/)
						{
							$osType = $1;
						}
					}
				}
				
				# If startTag is FIOSTATUS, then parse the command output to get different resources and attributes.
				if($startTag =~ '__BEGIN__ FIOSTATUS')
				{
					if($ele !~ '__END__ FIOSTATUS')
					{
						if($ele =~ /Adapter: .*/)
						{
							$label = "Adapter";
							my @modelArr = split(':',$ele);
							$model = $modelArr[1];
							$model =~ s/^\s+//;
							$isAdapter = 1;
						}

						if($ele =~ /^(fct.+)\s+(.*)\s\'(.*)\'\s(.*)/)
						{
							$label = $1; 
							$blockDev = $3;
							$label =~ s/fct(.+)\s+(.*)/fct$1/;
					
							if($isAdapter == 1)
							{
								if($isVm == 1)
								{
									print DATA_HANDLE "r:h#$probeServer$fIoDimm\n";
								}
								else
								{
									print DATA_HANDLE "r:lHost#$probeServer$fIoDimm\n";
								}
								$isAdapter = 0;
							}
							print DATA_HANDLE "R:fIoDimm#$label"."_"."$blockDev\n";
						}
					
						# If Adapter label encountered, parse resource and attribute values related to Adapter label.
						if($label =~ 'Adapter')
						{
							# If "Product Number" and "SN" strings are present.
							if($ele =~ /\s+Fusion-io\s+([\w\s]+)\s+(\d+\w+),\sProduct Number:(.*)\sSN:(.*)/)
							{
								if($count < 1)
								{
									$driveSize = $2;
									$subUniqueAdap = $3;
									$serialNo = $4;
									
									print DATA_HANDLE "R:fIoAdapter#"."$subUniqueAdap"."_"."$serialNo\n";
									print DATA_HANDLE "a:ip_address=$fioIpAddress\n";
									print DATA_HANDLE "a:operating_system=$osType\n";
									print DATA_HANDLE "a:name=$subUniqueAdap"."_"."$serialNo\n";
									print DATA_HANDLE "a:model=$model\n";
									print DATA_HANDLE "a:size=$driveSize\n";
									print DATA_HANDLE "a:product_number=$subUniqueAdap\n";
									print DATA_HANDLE "a:serial_number=$serialNo\n";
									$count += 1;
								}
							}

							# If "Mfr" and "Date" strings are present.
							elsif($ele =~ /\s+(.*),\sMfr:(.*),\sDate:(.*)/)
							{
								$mfgr = $2;
								$mDate = $3;

								print DATA_HANDLE "a:manufacturer=$mfgr\n";
								print DATA_HANDLE "a:mfgdate=$mDate\n";
							}

							# If pcie bus--> voltage,current, power strings are present.
							elsif($ele =~ /\s+PCIE Bus voltage:\s(.*),\s(.*),\s(.*)/)
							{
								$Avg = $1;
								$Min = $2;
								$Max = $3;
								$Avg =~ s/avg\s(.*)/$1/;
								$Min =~ s/min\s(.*)/$1/;
								$Max =~ s/max\s(.*)/$1/;

								print DATA_HANDLE "a:pcie_bus_voltage_avg=$Avg\n";
								print DATA_HANDLE "a:pcie_bus_voltage_min=$Min\n";
								print DATA_HANDLE "a:pcie_bus_voltage_max=$Max\n";								
							}

							elsif($ele =~ /\s+PCIE Bus current:\s(.*),\s(.*)/)
							{
								$Avg = $1;
								$Max = $2;
								$Avg =~ s/avg\s(.*)/$1/;
								$Max =~ s/max\s(.*)/$1/;	
									
								print DATA_HANDLE "a:pcie_bus_current_avg=$Avg\n";
								print DATA_HANDLE "a:pcie_bus_current_max=$Max\n";
							}

							elsif($ele =~ /\s+PCIE Bus power:\s(.*),\s(.*)/)
							{
								$Avg = $1;
								$Max = $2;
								$Avg =~ s/avg\s(.*)/$1/;
								$Max =~ s/max\s(.*)/$1/;
						
								print DATA_HANDLE "a:pcie_bus_power_avg=$Avg\n";
								print DATA_HANDLE "a:pcie_bus_power_max=$Max\n";
							}

							# pcie available power 
							elsif($ele =~ /\s+PCIE slot available power:\s(.*)/)
							{
								my $power = $1; 
								print DATA_HANDLE "a:pcie_slot_available_power=$power\n";
							}
						}
	
						# If 'fct+' label encountered, parse resource and attribute values related to fct label.
						if($label =~ 'fct.+')
						{
							if($ele =~ /\s+Fusion-io\s+([\w\s]+)\s+(\d+\w+),\sProduct Number:(.*)\sSN:(.*)/)
							{
								#prod no#serial no#model
								$model = $1;
								$driveSize = $2;
								$prodNo = $3;	
								$serialNo = $4;
                                                                
								print DATA_HANDLE "a:name=$prodNo"."_"."$serialNo\n";
								print DATA_HANDLE "a:model=$model\n";
								print DATA_HANDLE "a:ip_address=$fioIpAddress\n";
								print DATA_HANDLE "a:operating_system=$osType\n";
								print DATA_HANDLE "a:size=$driveSize\n";
								print DATA_HANDLE "a:product_number=$prodNo\n";
								print DATA_HANDLE "a:serial_number=$serialNo\n";	
							}	

							elsif($ele =~ /\s+(.*),\sMfr:(.*),\sDate:(.*)/)
							{
								$mfgr = $2;
								$mDate = $3;

								print DATA_HANDLE "a:manufacturer=$mfgr\n";
								print DATA_HANDLE "a:mfgdate=$mDate\n";
							}

							elsif($ele =~ /\s+Firmware\s(.*),\srev\s(.*)/)
							{
								my $firmware = $1;
								my $rev = $2;

								print DATA_HANDLE "a:firmware=$firmware\n";
								print DATA_HANDLE "a:revision=$rev\n";
							}

							elsif($ele =~ /\s+Media status:\s(.*);\sReserves:\s(.*)%,\s(.*)\s(.*)%/)
							{
								my $media = $1;
								my $reserves = $2;
								my $warnAt = $4;
								$reserves -= $warnAt;	

								print DATA_HANDLE "a:media_status=$media\n";
								print DATA_HANDLE "a:reserves=$reserves\n";
							}	

							elsif($ele =~ /\s+Physical bytes written:\s(.*)/)
							{
								my $bytesWrit = $1;
								print DATA_HANDLE "a:bytes_written=$bytesWrit\n";
							}
				
							elsif($ele =~ /\s+Physical bytes read\s+:\s(.*)/)
							{
								my $bytesRead = $1;
								print DATA_HANDLE "a:bytes_read=$bytesRead\n";
							}
							elsif($ele =~ /\s+Current:\s(.*)\s(.*)/)
							{
								my $cRam = $1;
								$cRam =~ s/,//g;
								$cRam = ($cRam /= 1024) / 1024;
								print DATA_HANDLE "a:current_ram_usage_mbytes=$cRam\n";
							}

							elsif($ele =~ /\s+Peak\s+:\s(.*)\s(.*)/)
							{
								my $peak = $1;
								$peak =~ s/,//g;
								$peak = ($peak /= 1024) / 1024;
								print DATA_HANDLE "a:peak_ram_usage_mbytes=$peak\n";
								
								$isFioComplete = 1;
							}

							elsif($ele =~ /\s+Board temperature:\s(.*)/)
							{
								my $boardTemp = $1;
								print DATA_HANDLE "a:board_degC=$boardTemp\n";
							}

							elsif($ele =~ /\s+Internal temperature:\s(.*)\sdegC,\smax\s(.*)\sdegC/)
							{
								my $tempAvg = $1;
								my $tempMax = $2;

								print DATA_HANDLE "a:internal_avg_degC=$tempAvg\n";
								print DATA_HANDLE "a:internal_max_degC=$tempMax\n";
							}

							elsif($ele =~ /\s+Internal voltage:\s(.*)\s(.*),\s(.*)\s(.*)/)
							{
								my $voltAvg = $2;
								my $voltMax = $4;

								print DATA_HANDLE "a:internal_voltage_avg=$voltAvg\n";
								print DATA_HANDLE "a:internal_voltage_max=$voltMax\n";
							}

							elsif($ele =~ /\s+Aux voltage:\s(.*)\s(.*),\s(.*)\s(.*)/)
							{
								my $voltAvg = $2;
								my $voltMax = $4;

								print DATA_HANDLE "a:aux_voltage_avg=$voltAvg\n";
								print DATA_HANDLE "a:aux_voltage_max=$voltMax\n";
							}

							#Add relations for fIoDimm label: h, lhost

							if($isFioComplete == 1)
							{
								if($isVm == 1)
								{
									print DATA_HANDLE "r:h#$probeServer\n";
								}
								else
								{
									print DATA_HANDLE "r:lHost#$probeServer\n";
								}
								$isFioComplete = 0;
							}
						}
					}					
				}
			}
		}
		logMsg("Info: completed dumping into raw format.");	
	}
	
	$status = writeToOutputFile();
	if (0 != $status)
	{
        	logMsg("Error: Call to writeToOutputFile() failed.");
		$retVal = 1;
	}
	logMsg("Info: Write to out file complete.");
	close(DATA_HANDLE);
	close(CONF_DUMP);
	return $retVal;
}

# This function is used to log a message in the log file. It logs the message in the following format:
# [YYYY-MM-DD HH:MM:ss,SSS]  [FIO-<IP Address>] <Log message>
#
# @param :
#   $_[0] - Log message.
#
# @return :
#   Nothing
sub logMsg
{
    # Get the current date and time.
    my ($seconds, $microseconds) = gettimeofday;
    my $currentTime = strftime("%Y-%m-%d %H:%M:%S", localtime($seconds));

    # Get the date from the given date and time .
    my($date, $time) = split(' ', $currentTime, 2);

    # Set the name of log file.
    $logFile = $fioLogFolder."/fioServer".$date.".log";

    # Open the log file.
    open($LOG_HANDLE, ">>", $logFile) or die logMsg("Error: Not able to open the file: [$logFile].");
	
    # Read input parameters.
    my $logMsg = $_[0];

    # For getting time in a given format.
    $currentTime = "[".$currentTime.",".$microseconds."]";

    # Construct the final log message.
    my $finalMsg = $currentTime." [FIO] ".$logMsg."\n";

    # Dump the final log message in log file.
    print $LOG_HANDLE $finalMsg;
}

# This function is used to write processed data in MARS format to the final output file.
#
# @return :
#   0 if Success
#   1 if Error
sub writeToOutputFile
{
	# Create Snapshot file
	
	# If folder does not exist, then create it.
	 unless (defined eval {mkpath($fioTempFolder)})
	{
		logMsg("Error: Not able to create folder: [$fioTempFolder].");
		$retVal = 1;
		return;
	}

	chomp($fioIpAddress);
	chomp($probeSite);

	# Design name of output file
	$fioConfOutputFile = "Conf_".$startTime."_snapshot_".$probeSite."_".$fioIpAddress."_FusionIo.txt";

	# Copy temp file to new file.
	$status = system("cp $fioTempFile $fioConfOutputFile");
	if(0 != $status)
	{
		logMsg("Error: copy failed to out file:[$fioConfOutputFile].");
		$retVal = 1;
	}	
	logMsg("Info: Copy to out file complete.");
	
	# Zipping processed file.
	$status = system("zip $fioConfOutputZipFile $fioConfOutputFile"); 
	if (0 != $status)
	{
		logMsg("Error: zipping failed.");
		$retVal = 1;
	}
	logMsg("Info: Zipping complete.");

	# Delete temporary file.
	$status = system("rm $fioTempFile");
	if (0 != $status)
	{
		logMsg("Error: Unable to remove file: $fioTempFile.");
	    $retVal = 1;
	}
	logMsg("Info: $fioTempFile deleted successfully.");

	# Delete extracted files from folder recursively.
	$status = system("rm -r $baseFolder/temp/fio/extract/conf/$zipFileName");
	if(0 != $status)
	{
		logMsg("Error: Cannot remove file: $zipFileName.");
		$retVal = 1;
	}
	logMsg("Info: Deleted file: $zipFileName.");

	close(DATA_HANDLE);
	return $retVal;
}
