#!/usr/bin/perl
#
# scan job execution log files from the KTH SP2 system, and use convert2swf.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/Stockholm";
my $max_procs = 100;
my @queues    = ();

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

SWF::init( $tz_str, $max_procs, \@queues );

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 );



############################
# SPECIFICS for KTH workload

# the format of each entry includes the following space-separated fields:
#  0 user ID
#  1 accounting group
#  2 job ID
#  3 requested nodes by type (T/W/Z/U)
#  4 start date
#  5 start time
#  6 end date
#  7 end time
#  8 nodes allocated
#  9 requested time - runtime limit for EASY (hrmin)
# 10 wallclock time all nodes were allocated (hrmin)
# 11 requested total node time
# 12 used total node time
# 13 waiting time (hrmin)
# 14 status (only if killed)

my $job_cnt;

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('\s+');	# split into fields

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

	my %nj;

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

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

	# translate human readable start date and time into time array
	my @begtmarr;
	($begtmarr[5],$begtmarr[4],$begtmarr[3]) = split('-',$line[4]);
	($begtmarr[2],$begtmarr[1],$begtmarr[0]) = split(':',$line[5]);
	$begtmarr[4]--;
	$begtmarr[5] -= 1900;
	my $start = &timelocal(@begtmarr);

	$nj{start}  = $start;

	# wait time is given in < hours "h" minutes >
	my $submit;
	if ($line[13] =~ /(\d*)h(\d*)/) {
	    my $hours = (defined($1) && ($1 ne "")) ? $1 : 0;
	    my $mins  = (defined($2) && ($2 ne "")) ? $2 : 0;
	    my $wait = (($hours * 60) + $mins) * 60;
	    # need to check against artificial submit times used to boost priority
	    # done by comparing agains submit time encoded in job ID
	    $line[2] =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
	    $begtmarr[5] = $1 - 1900;
	    $begtmarr[4] = $2 - 1;
	    $begtmarr[3] = $3;
	    $begtmarr[2] = $4;
	    $begtmarr[1] = $5;
	    $begtmarr[0] = 0;
	    my $check = &timelocal(@begtmarr);
	    if (($start-$wait) < $check) {
		$wait = $start - $check;
		$format_bugs .= "modified_wait $_\n";
	    }

	    $submit = $start - $wait;
	}
	else {
	    $format_bugs .= "wait_format_problem $_\n";
	    warn("Problematic wait time: $line[13]");

	    $submit = $start;
	}

	$nj{submit} = $submit;

	my @endtmarr;
	($endtmarr[5],$endtmarr[4],$endtmarr[3]) = split('-',$line[6]);
	($endtmarr[2],$endtmarr[1],$endtmarr[0]) = split(':',$line[7]);
	$endtmarr[4]--;
	$endtmarr[5] -= 1900;
	my $end = &timelocal(@endtmarr);

	$nj{end}   = $end;

	my $procs = $line[8];

	$nj{procs} = $procs;

	# find sum of requested processors
	my $req_procs = 0;
	if ($line[3] =~ /(\d+)T/) {
	    $req_procs += $1;
	}
	if ($line[3] =~ /(\d+)W/) {
	    $req_procs += $1;
	}
	if ($line[3] =~ /(\d+)Z/) {
	    $req_procs += $1;
	}
	if ($line[3] =~ /(\d+)$/) {
	    $req_procs += $1;
	}

	my $req_U_procs = 0;
	if ($line[3] =~ /(\d+)U/) {
	    $req_U_procs += $1;
	}

	$nj{req_procs} = $req_procs;

	if ($req_U_procs > 0) {
	    if ($req_procs + $req_U_procs == $procs) {
		$nj{procs} -= $req_U_procs;
	    }
	    else {
		$format_bugs .= "req_procs_problem $_\n";
		warn("Problematic requested procs $line[3] vs. used $line[8]");
	    }
	}

	# requested runtime is given in < hours "h" minutes >
	if ($line[9] =~ /(\d*)h(\d*)/) {
	    my $hours = (defined($1) && ($1 ne "")) ? $1 : 0;
	    my $mins  = (defined($2) && ($2 ne "")) ? $2 : 0;
	    my $req_time = (($hours * 60) + $mins) * 60;

	    $nj{req_time} = $req_time;
	}
	else {
	    $format_bugs .= "req_time_format_problem $_\n";
	    warn("Problematic requested time: $line[9]");
	}

	# auto-released jobs have failed or were killed
	if ((defined($line[14])) && ($line[14] eq "autorel")) {
	    $nj{status} = 0;
	}
	else {
	    $nj{status} = 1;
	}

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

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

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

    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: IBM SP2\n";
    $header .= "; Installation: Swedish Royal Institute of Technology (KTH)\n";

    $header .= "; Acknowledge: Lars Malinowsky\n";
    $header .= "; Information: http://www.pdc.kth.se/\n";
    $header .= ";              http://www.cs.huji.ac.il/labs/parallel/workload/\n";

    $header .= sprintf("; Conversion: Dror Feitelson (feit\@cs.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 EASY scheduler\n";

    return $header;
}

