#!/usr/bin/perl
#
# scan job execution log files from the MetaCentrum system, and use convert2swf_v3.pm
# to convert the data into the standard workload format.
#

use warnings;
use strict;
use Time::Local;
use POSIX;
use convert2swf_v4;


############################
#
# DO THE CONVERSION:
#

my $tz_str    = "Europe/Prague";
my $max_procs = 806;

my @queues     = ("q1","q2","q3","q4","q5","q6","q7","q8","q9","q10","q11");
my @queue_desc = ("",
		  "(pri=62, 720hr)",
		  "(pri=70, 720hr)",
		  "(pri=50, 24hr)",
		  "(pri=60, 2hr)",
		  "(pri=80, 24hr)",
		  "(pri=65, 720hr)",
		  "(pri=70, 720hr)",
		  "(pri=70, 4hr)",
		  "(pri=70, 720hr)",
		  "(pri=99, 720hr)",
		  "(pri=65, 720hr)"
		 );

my @partitions     = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
my @partition_desc = ("",
		      "(Itanium2 1.5GHz,  8 nodes,   8 CPUs)",
		      "(Opteron  2.2GHz, 16 nodes,  16 CPUs)",
		      "(Xeon     3.2GHz, 10 nodes,  10 CPUs)",
		      "(Opteron  2.6GHz,  5 nodes,  80 CPUs)",
		      "(AthlonMP 1.6GHz, 16 nodes,  32 CPUs)",
		      "(Xeon     2.4GHz, 32 nodes,  64 CPUs)",
		      "(Xeon     2.7GHz, 36 nodes, 148 CPUs)",
		      "(Xeon     3.1GHz, 35 nodes,  70 CPUs)",
		      "(Opteron  1.6GHz, 10 nodes,  20 CPUs)",
		      "(Opteron  2.4GHz,  3 nodes,   6 CPUs)",
		      "(Opteron  2.0GHz, 23 nodes,  92 CPUs)",
		      "(Xeon     3.0GHz, 19 nodes, 152 CPUs)",
		      "(Xeon     2.7GHz,  8 nodes,  64 CPUs)",
		      "(Xeon     2.3GHz, 11 nodes,  44 CPUs)",
		     );
		  

$ENV{TZ} = $tz_str;
POSIX::tzset();

SWF::init( $tz_str, $max_procs, \@queues, \@partitions );
my $clusters = get_clusters();
my $format_bugs;
my $jobs_data = get_jobs();

my ($zero, $maxtime) = SWF::convert( $jobs_data, $format_bugs );

my $header = generate_header( $zero, $maxtime, $max_procs );

SWF::print( $header, \@queue_desc, \@partition_desc );



####################################
# SPECIFICS for MetaCentrum workload

# the format of each entry includes the following space-separated fields:
#  0. job_id		int
#  1. user		string	
#  2. queue_name	string	
#  3. used_CPUs		int	
#  4. used_nodes	int	
#  5. required_properties	[string;string;...]
#  6. used_main_memory(kB)	int	
#  7. arrival_time	int	
#  8. start_time	int	
#  9. end_time		int	
# 10. duration		int
# 11. exit_status	int	
# 12. ID(s)_of_assigned_CPUs	int int int ...

# in addition there is a separate file that contains information about the
# different clusters, and in particular which processors belong to each one.
# the format of that file is:
#  0. node_id		int	("node" actually means "cluster")
#  1. node_name		String
#  2. cpu_speed(MHz)	int
#  3. main_memory(kB)	int	(of a machine in the cluster)
#  4. CPU_type		String
#  5. operating_system	String
#  6. supported_properties	Strings,String,...
#  7. number_of_machines	int
#  8. total_CPUs	int
#  9. CPUs_per_machine	int,int,...
# 10. list_of_CPUs_ids	int,int,...

sub get_clusters {
##################
# read file with clusters data and create mapping from CPU ID to cluster ID

    open(C, "<metacentrum.mwf.machines") || die "can't open clusters data file";

    my %map;
    while (<C>) {

	chomp;
	my @line = split('\t');			# split into fields

	my $clust = $line[0];
	my @CPUs  = split(',', $line[10]);	# split CPUs list
	foreach my $cpu (@CPUs) {
	    $map{$cpu} = $clust;
	}
    }

    return \%map;
}

my $job_cnt;
my $multi_clust = 0;
my $bad_time    = 0;
my $bad_procs   = 0;

sub get_jobs {
##############
# parse input format and create a hash for each job

    my @list_of_jobs;

    #
    # scan trace and collect job info
    #

    while (<>) {


	#
	# parse line and extract info
	#

	if (/^\s*$/) {		# empty line
	    next;
	}

	$_ =~ /\s*(.*)\s*/;	# get rid of white space at ends
	$_ = $1;


	my @line = split('\t');	# split into fields

	if (($#line != 12)) {
	    $format_bugs .= "format_problem $_\n";
	    warn(">>>Format problem on $_\ngot $#line fields (should be 12)");
	    next;
	}

	my %nj;

	#
	# set the desired fields as specified in convert2swf.pl
	#

	$nj{trace}  = $_;	# used for error messages

	$nj{submit} = $line[7];
	$nj{start}  = $line[8];
	$nj{end}    = $line[9];

	$nj{procs} = $line[3];

	$nj{mem} = $line[6];

	if ($line[11]==0){
		$nj{status} = 1;
	}
	else{
		$nj{status} = 0;
	}

	$nj{user}= $line[1];

	$nj{queue} = $line[2];

	# figure out which cluster was used
	my $clust;
	my $other=0;
	my @CPUs  = split(' ', $line[12]);	# split CPUs list
	$clust = $clusters->{$CPUs[0]};
	$nj{partition} = $clust;
	foreach my $cpu (@CPUs) {
	    if ($clusters->{$cpu} != $clust) {
		warn "using more than one cluster! [$cpu=>$clusters->{$cpu} != $clust]\n";
		$other = 1;
	    }
	}
	$multi_clust += $other;

	# sanity checks
	if ($line[9] - $line[8] != $line[10]) {
	    $format_bugs .= "duration mismatch in $_\n";
	    $bad_time++;
	}
	if (@CPUs != $line[3]) {
	    $format_bugs .= "CPUs mismatch in $_\n";
	    $bad_procs++;
	}

	$list_of_jobs[$job_cnt++] = \%nj;
    }

    $format_bugs .= "\n\nmultiple clusters used in $multi_clust jobs\n";
    $format_bugs .= "time mismatch in $bad_time jobs\n";
    $format_bugs .= "CPUs mismatch in $bad_procs jobs\n\n";

    return \@list_of_jobs;
}


sub generate_header {
    my ($zero, $maxtime, $max_procs) = @_;

    my ($sec, $min, $hr, $mday, $mon, $year, $wday, $yday, $isdst);

    $header  = "; Version: 2.2\n";
    $header .= "; Computer: MetaCentrum grid\n";
    $header .= "; Installation: Czech national grid\n";

    $header .= "; Acknowledge: Dalibor Klusacek\n";
    $header .= "; Information: http://meta.cesnet.cz/\n";
    $header .= ";              http://www.cs.huji.ac.il/labs/parallel/workload/\n";

    $header .= sprintf("; Conversion: Nati Zakay (netanel.zakay\@mail.huji.ac.il) %s\n",
		       strftime("%d %b %Y", localtime()));


    $header .= sprintf("; MaxJobs: %d\n", $job_cnt);
    $header .= sprintf("; MaxRecords: %d\n", $job_cnt);
    $header .= "; Preemption: No\n";

    $header .= sprintf("; UnixStartTime: %d\n", $zero);
    $header .= "; TimeZone: 3600\n";
    $header .= sprintf("; TimeZoneString: %s\n", $tz_str);

    $header .= sprintf("; StartTime: %s\n",
	   strftime("%a %b %2d %H:%M:%S %Z %Y", localtime($zero)));

    $header .= sprintf("; EndTime:   %s\n",
	   strftime("%a %b %2d %H:%M:%S %Z %Y", localtime($maxtime)));

    $header .= "; MaxNodes: $max_procs\n";
    $header .= "; MaxProcs: $max_procs\n";

    $header .= "; Note: uses the PBSpro scheduler\n";
	
    $header .= "; Queues: for each queue there are two fields.\n";

    return $header;
}

