#!/usr/bin/perl -w
#--------------------------------------------------------------------------------------------------------------
#
# Convert Notre Dame data to the tabbed format.
# only generate event_trace.tab
#
# usage: 
# ./grid5k2tab.pl -input ~/research/fta/data/notre_dame07_raw -output ~/research/fta/data/notre_dame07_tab
#
# Contact: Derrick Kondo derrick.kondo :: inria fr
#
# Copyright 2009 by Failure Trace Archive site (http://fta.inria.fr)
#
#--------------------------------------------------------------------------------------------------------------





# limit scope of variables
use strict;

use Switch;
use FTA_Format;


# Module to help handle command line switches
use Getopt::Long;

# method headers
sub main();
sub extract_parameters();

sub create_platform ($);
sub create_node ($);
sub create_node_perf ($);
sub create_component ($);
sub create_event_trace ($$);
sub create_event_state ($);
sub process_probe ($$$$$);
sub get_uniq_rand ($$$);

# platform globals
my ($PLATFORM_ID, $PLATFORM_NAME, $PLATFORM_LOCATION, $PLATFORM_TYPE);
 
$PLATFORM_ID = 6;
$PLATFORM_NAME = "Notre Dame";
$PLATFORM_LOCATION = "The University of Notre Dame, USA";
$PLATFORM_TYPE = "Desktop Grid"; 

# node globals

# DATA STRUCTURES

# %NODE is a hash table that points to a array reference,
# %which points to an array of events.

# node_name -> events arr (node_id, reference to event_traces array of
# (event_id, event_type, start time, stop time))

my (%NODE, $NODE_ID_COUNTER);
my (@HOST_IDS,@NEW_HOST_IDS);

$NODE_ID_COUNTER = 0;

# component globals
my ($COMPONENT_ID, $COMPONENT_CITE, $RESOLUTION);
$COMPONENT_ID = 0;
$RESOLUTION = 960; # 16 min


# creator globals
my ($CREATOR_ID, $CREATOR, $CITE, $COPYRIGHT);
$CREATOR_ID = 0;
# assume no spaces here
$CREATOR = "Brent Rood";
# bibtex entry difficult to parse in tabbed format. Thus put
# bibtex citation instead.
$CITE = "rood_grid07";
$COPYRIGHT = "Brent Rood";

# event_trace globals
my($START_TIME_MEAS, $END_TIME_MEAS);


# for interpretation of availability/unavilability ($CPU_LOAD must be 1)
#1: state(1) is availability and other four states are unavailability (like cpu availability)
#0: state(1-4) are availability and only state(5) is unvailability (like host availability)
my($CPU_NO_HOST) = 1;

# for interpretation of availability/unavilability
# if is large than or equal CPU_LOAD and in the User Present state (state 2) then we have unavailablility
#other wise availablility
my($CPU_LOAD) = -1;
#
#              $CPU_NO_HOST  $CPU_LOAD
#host               0           -1
#cpu_1              1           -1
#cpu_2              1           0.1
#four states        1            1
#
#four states: {Available, User Present, CPU Threshold Exceeded, Unavailable}
main;

sub main (){
    my (@args, $start, $stop, $cwd);

    my ($inputdir, $outputdir);

    ($inputdir, $outputdir) = extract_parameters();
    
    createDir ($outputdir);
#    create_platform ($outputdir);
    create_event_trace ($inputdir, $outputdir);
#    create_event_state ($outputdir);
# must be called after create_event_trace
#    create_node ($outputdir);
#    create_node_perf ($outputdir);
#    create_component ($outputdir);
#    create_creator ($outputdir);

}

sub create_platform ($){
    my ($outputdir) = @_;

    open (OUT, ">$outputdir/platform.tab") or die "could not open $outputdir/platform.tab";

    print_header (*OUT, "platform");

    print_ln (*OUT, "platform", [$PLATFORM_ID, $PLATFORM_NAME,
		     $PLATFORM_LOCATION, $PLATFORM_TYPE, "NULL"]);

    close (OUT);
}
 

sub create_creator ($){
  my ($outputdir) = @_;
  
  open (OUT, ">$outputdir/creator.tab") or die "could not open $outputdir/creator.tab";
  
  print_header (*OUT, "creator");
  
  my (@node_names) = keys (%NODE);
  @node_names = sort @node_names;
  
  @node_names > 0 or die "no nodes\n";
  
  my ($node_name);
  foreach $node_name (@node_names){
    my ($events, $node_id, $event_traces, $event_info);
    
    
  print_ln (*OUT, "creator", [$CREATOR_ID, $COMPONENT_ID,
		$NODE{$node_name}->[0], $PLATFORM_ID, $CREATOR, $CITE, $COPYRIGHT]);
    
  }
  
  close (OUT);
}


# create_event_trace must be called before
sub create_node ($){
    my ($outputdir) = @_;


    my ($node_name,$node_info,$node_ch);

    open (NODE, ">$outputdir/node.tab") or die "could not open $outputdir/node.tab";

    print_header (*NODE, "node");

    my ($location,$timezone,$node_name_id);
    $timezone = "-18000";

    my (@node_names) = keys (%NODE);
    @node_names = sort @node_names;

    @node_names > 0 or die "no nodes\n";

#	$node_info = [$disk_size, $num_proc, $proc_type, $os_type, $memory_size, $ip_addr, $fp_ops];


    foreach $node_name (@node_names){
	
	$node_info = $NODE{$node_name}->[2];

	$node_ch = $node_info->[-1];
	$node_name_id = $NODE{$node_name}->[3];
	
	print_ln (*NODE, "node",
		    [$NODE{$node_name}->[0], $PLATFORM_ID,
		     $node_name_id, (defined($node_ch->[5])?$node_ch->[5]:"NULL"), "NULL", $timezone, (defined($node_ch->[2])?$node_ch->[2]:"NULL"), 
		     (defined($node_ch->[3])?$node_ch->[3]:"NULL"),"NULL", (defined($node_ch->[1])?$node_ch->[1]:"NULL"), 
		     (defined($node_ch->[4])?$node_ch->[4]:"NULL"), (defined($node_ch->[0])?$node_ch->[0]:"NULL"), "NULL",
		     "NULL", "NULL", "NULL"]);
    }

    close (NODE);
}

# create_node_perf
sub create_node_perf($){
    my ($outputdir) = @_;

    open (NODE_PERF, ">$outputdir/node_perf.tab") or die "could not open $outputdir/node_perf.tab";

    print_header (*NODE_PERF, "node_perf");
    my ($node_name,$node_info,$node_ch);

    my (@node_names) = keys (%NODE);
    @node_names = sort @node_names;

    my($metric_id);
    $metric_id = 0;

    foreach $node_name (@node_names){
	
	$node_info = $NODE{$node_name}->[2];

	$node_ch = $node_info->[-1];
	
	print_ln (*NODE_PERF, "node_perf", 
	     [$metric_id, $NODE{$node_name}->[0], $PLATFORM_ID,
	      "NULL", (defined($node_ch->[6])? $node_ch->[6]:"NULL"), (defined($node_ch->[7])? $node_ch->[7]:"NULL"), "NULL", "NULL", "NULL"]);
    }

    close (NODE_PERF);

}

# create_event_state
sub create_event_state($){
    my ($outputdir) = @_;

    open (EVENT_STATE, ">$outputdir/event_state.tab") or die "could not open $outputdir/event_state.tab";

    print_header (*EVENT_STATE, "event_state");

    close (EVENT_STATE);

}

# create_event_trace must be called before

sub create_component ($){
    my ($outputdir) = @_;
    

    open (COMPONENT, ">$outputdir/component.tab") or die "could not open $outputdir/component.tab";
    
    print_header (*COMPONENT, "component");

    my (@node_names) = keys (%NODE);
    @node_names = sort @node_names;

    @node_names > 0 or die "no nodes\n";
    
    my ($node_name);
    foreach $node_name (@node_names){
	my ($events, $node_id, $event_traces, $event_info);
	
	$events = $NODE{$node_name};

	$node_id = $events->[0];
	$event_traces = $events->[1];

	my ($trace_start, $trace_end);

# get start time of first event
	$trace_start = $event_traces->[0]->[2];
# get stop time of last event
	$trace_end = $event_traces->[-1]->[3];
 
	my ($component_type);

	if ($CPU_NO_HOST){
	    $component_type = 1;
	}
	else{
	    $component_type = 0;
	}
	    
	

	print_ln (*COMPONENT, "component", [$COMPONENT_ID,
					    $node_id,
					    $PLATFORM_ID,
					    $CREATOR_ID,
					    $node_name,
					    $component_type,
					    $trace_start,
					    $trace_end,
					    $RESOLUTION]);

    }

    close (COMPONENT);
    
}



sub create_event_trace ($$){
    my ($inputdir, $outputdir) = @_;
    
    my ($event_id, $component_id, $node_id,
	$event_type, $event_state, $event_start_time,
	$event_end_time, $event_end_reason,$timestamp);

    my (@dirs, @files, $f, $dir);

    $event_id = 0;
 
# open input directory   
    opendir (INPUTDIR, $inputdir);
    @dirs= readdir(INPUTDIR);

    @dirs = sort @dirs;

    foreach $dir (@dirs)
    {
	unless ( ($dir eq ".") || ($dir eq "..") )
	{
  
	    print "$dir \n";

	    if ($dir =~ /\d/){
		my ($f_dir) = $inputdir . $dir;
# open subdirectory
		opendir (INPUTSUBDIR, $f_dir);
		@files = grep { /.*\.txt$/ } readdir (INPUTSUBDIR);
    
		print "Number of trace files: " . @files . "\n";
		
		@files = sort @files;

		my ($node_name,$fname);
		foreach $f (@files){
	
		    $fname = $f_dir . '/' . $f;
		    print "\tProcessing file $fname\n";
		    open (IN, "$fname") or die "$fname could not be opened";
	
		    my (@info, $line);
		    $node_name = $f;
		    $node_name =~ s/.txt//;
   
		    while ($line = <IN>){
			
			chomp ($line);

# start reading from the first line
			if ($line =~ /^\d/){

			    my ( @line_info);
			    
			    (@line_info) = split /\s+/, $line;

# ****************************************************
#			    if (!defined($line_info[1])){
#				next;
#			    }

			    $timestamp = $line_info[0];
			    my($event_type) = $line_info[1];
			    my($cpu_load) = $line_info[2];

			    process_probe($inputdir,$node_name,$event_type,$timestamp,$cpu_load);

			} # if line

		    } # while line
	
		    close (IN);

		} # foreach files
	    }# if dir
	}# unless

    close INPUTSUBDIR;
    }# foreach dir
    close INPUTDIR;

# at end of parsing last probe file, insert stop time for
# last interval of each node

    my (@node_names) ;
    @node_names = keys (%NODE);
    
#     print "num node names: " . @node_names . "\n";

    my ($nn);
    foreach $nn (@node_names) {
	my ($events, $event_info);

	$events = $NODE{$nn}->[1];
	
# get most recent event type
	!defined($events->[-1]->[3]) or die "last intv shouldn't be defined";
	$event_info = $events->[-1];

	defined ($event_info->[1]) or die "event_type undef";

	# ASSUMES LAST TIME STAMP FOR ALL HOSTS IS THE SAME,
	# add stop time to last event
	$event_info->[3] = $timestamp;
    }

    my (@nodes_names) ;
    @nodes_names = keys (%NODE);
    
     print "No. of nodes: " . @nodes_names . "\n";

    
    print_event_trace($outputdir);

}

sub print_event_trace($){
    my ($outputdir)=@_;
 
    my (@node_names) = keys (%NODE);
    @node_names = sort @node_names;

    my ($node_name);

    open (EVENT_TRACE, ">$outputdir/event_trace.tab") or die
	"file $outputdir/event_trace.tab could not be used for output";

    print_header (*EVENT_TRACE, "event_trace");

    foreach $node_name (@node_names){
	my ($events, $node_id, $event_traces, $event_info);
	
	$events = $NODE{$node_name};
	defined ($events) or die "events undef";
	$node_id = $events->[0];

	$event_traces = $events->[1];
	
# 	print "node_name: $node_name\n";

	foreach $event_info (@$event_traces){
	    my ($event_id, $event_type, $start_time,
		$stop_time, $stop_reason);

	    defined ($event_info) or die "event_info undef";
# 	    print "event_info: " . @$event_info . "\n";
# 	    print "event_id: " . $event_info->[1] . "\n";

	    $event_id = $event_info->[0];
	    $event_type = $event_info->[1];
	    $start_time = $event_info->[2];
	    $stop_time = $event_info->[3];
	    $stop_reason = "NULL";


#	    if ($start_time >= $stop_time){
#	        print "node: $node_name, $start_time, $stop_time \n";
#	    }
	    defined ($event_id) or die "event id undef";
	    defined ($COMPONENT_ID) or die "COMPONENT_ID undef";
	    defined ($node_id) or die "node_id undef";
	    defined ($PLATFORM_ID) or die "platform_id undef";
	    defined ($event_type) or die "event_type undef";
	    defined ($start_time) or die "start_time undef";
	    defined ($stop_time) or die "stop_time undef";

	    print_ln (*EVENT_TRACE, "event_trace", 
		 [$event_id, $COMPONENT_ID,
		 $node_id, $PLATFORM_ID, $node_name, $event_type, 
		 $start_time, $stop_time,
		 $stop_reason]);

	}
    }
    close (EVENT_TRACE);
}

sub process_probe ($$$$$){
    my ($inputdir,$node_name, $event, $timestamp,$cpu_load) = @_;

    my ($event_type, $spec_dir,$fname);
    
    if ($CPU_NO_HOST==1 && $CPU_LOAD==1){
    # four states availability model
    switch ($event){
	case 1 {$event_type = 1}
	case 2 {$event_type = 2}
	case 3 {$event_type = 3}
	case 5 {$event_type = 0}
    }
    }
    else{
    # two states availability model
    switch ($event){
	case 1 {$event_type = 1}
	case 2 {if(($CPU_NO_HOST) && ($cpu_load>$CPU_LOAD)){$event_type = 0}else{$event_type = 1}}
	case 3 {if($CPU_NO_HOST){$event_type = 0}else{$event_type = 1}}
	case 5 {$event_type = 0}
    }
    }
    my ($event_info, $events);

    if (!defined($event_type)){
	print "$event \n";
    }
    defined ($event_type) or die "event_type undef";

    my ($line, @info,@line_info);

# Process first event
    if (!exists($NODE{$node_name})){

# check for time
#	my($year,$month,$day, $hour,$min,$sec);

#	($year,$month,$day, $hour,$min,$sec) = Time_to_Date($timestamp);
#	print "	($timestamp):$year,$month,$day, $hour,$min,$sec \n";
#	$month = $month+1;
#	my($ttime) = Date_to_Time($year,$month,$day, $hour,$min,$sec);
#	print "$ttime \n";

# Allocate array dynamically and store reference
	$events = [];
# pointer to array
	
	$event_info = [0, $event_type, $timestamp, undef];
	push (@$events, $event_info);
	
# ****************************************************

	$NODE{$node_name} = [$NODE_ID_COUNTER, $events];
	$NODE_ID_COUNTER++; 
	close (INP);
# Process later event
    } else {
	
	$events = $NODE{$node_name}->[1];

# get most recent event type
	$event_info = $events->[-1];

	my ($prev_event_type) = $event_info->[1];

# went from unavail -> avail or vice versa
	if ($prev_event_type != $event_type){

	    # add end time to last event: stop = timestamp
	    $event_info->[3] = $timestamp;

	    # add new event
	    $event_info = [$event_info->[0]+1, $event_type, $timestamp, undef];
	    push (@$events, $event_info);
	}
    }
}


# If the dir doesn't exist, create it                                                                                         
                                                                                                                              
sub createDir ($){
    my ($dir) = @_;                                                                                                           
                                                                                                                              
    if (! -e $dir){                                                                                                           
        print "Creating directory $dir\n";                                                                                    
        my $retval = mkdir($dir, 0777);                                                                                       
        if ($retval == 0)                                                                                                     
        {                                                                                                                     
            die "Fatal Error: $dir could not be created\n Message is: $!\n. Program Terminating....\n";                       
        }                                                                                                                     
    }                                                                                                                         
                                                                                                                              
}                                                                                                                             


# sort by probe number
sub by_pb_num {                                                                                                                                  
    my ($text_a, $pb_num_a) = split /\_+/, $a;
    my ($text_b, $pb_num_b) = split /\_+/, $b;
    
   if ($pb_num_a  > $pb_num_b){                                                                                                                              
        return 1;                                                                                                                                        
    } elsif ($pb_num_a == $pb_num_b){                                                                                                                        
        return 0;                                                                                                                                        
    } else {                                                                                                                      
        return -1;                                                                                                                                       
    }                                                                                                                                                    
}

sub extract_parameters()
{
	my %params = (
		inputdir => \(my $inputdir = undef),
		outputdir => \(my $outputdir = undef),

	);

	# Call Library function which will extract and remove all switches and
	# their corresponding values.
	# These parameters will be removed from @ARGV

	my $errorval = &GetOptions(\%params, "inputdir=s", "outputdir=s");
                        # the exclaimation point allows for the negation
                        # of the switch (i.e. -nobackup/-nobody is a switch)

	# Error value should have returned 1 if nothing went wrong
	# Otherwise, an unlisted parameter was specified.
	if($errorval !=1)
	{
		# Expecting GetOptions to state error.

		print "Exiting Program...\n";
		exit 0;
	}

	defined ($inputdir) or die "inputdir not defined\n";
	defined ($outputdir) or die "outputdir not defined\n";

	return ($inputdir, $outputdir);
	
}
sub get_uniq_rand ($$$){
    my ($n, $low, $high) = @_;

    my (%nums, $new_num);

    while (keys (%nums) < $n){
	$new_num = random_uniform_integer(1, $low, $high);
	$nums{$new_num} = "";
    }
    return keys (%nums);
}
