#!/usr/bin/perl
#
# scan job execution log files from the SDSC Blue Horizon 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    = "US/Pacific";
my $max_procs = 1152;
my @queues    = ("interactive",
		 "express",
		 "high",
		 "normal",
		 "low",
		 "standby",
		);

log_init();
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 SDSC Blue workload

# the format of each entry includes the following space-separated fields:
# 0  user (sanitized as User1, User2, ...)
# 1  queue (interactive, express, high, normal, low, unknown)
# 2  total CPU time
# 3  wallclock time
# 4  service units (used in accounting)
# 5  nodes used
# 6  maximal nodes used
# 7  number of jobs run (hopefully always 1)
# 8  max memory (unused)
# 9  memory (unused)
# 10 I/O (unused)
# 11 disk space (unused)
# 12 connect time (unused)
# 13 waiting time
# 14 slowdown
# 15 priority (maps to queue)
# 16 submit date and time
# 17 start date and time
# 18 end date and time
# 19 requested wallclock time
# 20 requested memory (unused)
# 21 requested nodes
# 22 completion status

my $job_cnt;
my $ppn;


sub log_init {
##############

    # ppn is processors per node
    $ppn = 8;
}


sub get_jobs {
##############
# parse input format and create a hash for each job
#
# note: the original format provides times in human-readable format (hh:mm:ss),
# but these seem to be wrong, and specifically the result of using gmtime instead
# of localtime. so here we also need to use gmtime.

    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 != 22) {
	    $format_bugs .= "format_problem $_\n";
	    warn(">>>Format problem on $_\ngot $#line fields (should be 22)");
	    next;
	}

	# special checks
	if ($line[7] != 1) {
	    $format_bugs .= "many_jobs_problem [$line[7]] $_\n";
	}
	if ($line[6] < $line[5]) {
	    $format_bugs .= "maxpar_small_problem [mp=$line[6] n=$line[5]] $_\n";
	}
	if ($line[6] > $line[5]) {
	    $format_bugs .= "maxpar_big_problem [mp=$line[6] n=$line[5]] $_\n";
	}

	my %nj;

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

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


	# map status first
	my $status = 0;
	if ($line[22] == 2) {
	    $status = 5;
	}
	elsif ($line[22] == 1) {
	    $status = 1;
	}
	elsif ($line[22] != 3) {
	    # 3 means unknown...
	    $format_bugs .= "unknown_status_problem [$line[22]] $_\n";
	}
	if ($status != 0) {
	    $nj{status} = $status;
	}

	# submit time, in seconds
	my $submit;
	if ($line[16] =~ /(\d\d\d\d)-(\d\d)-(\d\d)--(\d\d):(\d\d):(\d\d)/) {
	    my $yr  = $1 - 1900;
	    my $mon = $2 - 1;
	    my $day = $3;
	    my $hr  = $4;
	    my $min = $5;
	    my $sec = $6;

	    $submit = &timegm($sec, $min, $hr, $day, $mon, $yr);
	}
	else {
	    $submit = -1;
	}
	$nj{submit} = $submit;

	# start time, in seconds
	my $start;
	if ($line[17] =~ /(\d\d\d\d)-(\d\d)-(\d\d)--(\d\d):(\d\d):(\d\d)/) {
	    my $yr  = $1 - 1900;
	    my $mon = $2 - 1;
	    my $day = $3;
	    my $hr  = $4;
	    my $min = $5;
	    my $sec = $6;

	    $start = &timegm($sec, $min, $hr, $day, $mon, $yr);
	    if ($start == 0) {
		if ($status == 1) {
		    # job is OK -- a real problem
		    $format_bugs .= "start_zero $_\n";
		}
		$start = -1;
	    }
	}
	else {
	    $start = -1;
	}
	$nj{start} = $start;

	# end time, in seconds
	my $end;
	if ($line[18] =~ /(\d\d\d\d)-(\d\d)-(\d\d)--(\d\d):(\d\d):(\d\d)/) {
	    my $yr  = $1 - 1900;
	    my $mon = $2 - 1;
	    my $day = $3;
	    my $hr  = $4;
	    my $min = $5;
	    my $sec = $6;

	    $end = &timegm($sec, $min, $hr, $day, $mon, $yr);
	}
	else {
	    $end = -1;
	}
	$nj{end} = $end;


	# more checks
	if (($start != -1) && ($end != -1)) {
	    my $wallclk = $end - $start;
	    if (($wallclk > $line[3]) && ($line[3] > 0)) {
		$format_bugs .= "wallclock_short_problem [e-s=$wallclk wc=$line[3]] $_\n";
	    }
	    my $wait = $line[13];
	    if (($submit > 0) && ($start > 0) && ($wait != -1)) {
		my $calc_wait = $start - $submit;
		if ($calc_wait > $wait) {
		    $format_bugs .= "wait_short_problem [s-s=$calc_wait w=$wait] $_\n";
		}
		if ($calc_wait < $wait) {
		    $format_bugs .= "wait_long_problem [s-s=$calc_wait w=$wait] $_\n";
		}
	    }
	}

	# number of processors:
	# allocation is in 8-processor nodes
	my $procs = $line[5] * $ppn;
	$nj{procs} = $procs;

	# divide total CPU by number of procs.
	my $cpu;
	if ($line[2] == 0) {
	    $cpu = 0;
	}
	elsif (($procs == -1) || ($procs == 0)) {
	    $cpu = -1;
	}
	else {
	    $cpu = ($line[2]/$procs);
	}
	$nj{cpu} = $cpu;

	# requested processors (also in 8-processor nodes)
	my $req_procs = $line[21] * $ppn;
	$nj{req_procs} = $req_procs;

	# requested runtime
	my $req_time = $line[19];
	$nj{req_time} = $req_time;

	# user ID
	$line[0] =~ /^User(\d+)$/;
	my $user = $1;
	$nj{user} = $user;

	# queue data
	my $queue = $line[1];
	$nj{queue} = $queue;

	$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 SP\n";
    $header .= "; Installation: SDSC Blue Horizon\n";

    $header .= "; Acknowledge: Travis Earheart and Nancy Wilkins-Diehr\n";
    $header .= "; Information: http://www.sdsc.edu/\n";
    $header .= "; Information: 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: -28800\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 .= sprintf("; MaxNodes: %d\n", $max_procs / $ppn);
    $header .= "; MaxProcs: $max_procs\n";

    $header .= "; Information: Each node is an 8-way SMP\n";
    $header .= "; Information: 375 MHz Power3 processors\n";
    $header .= "; Information: 4 GB memory per node (shared by 8 processors)\n";
    $header .= "; Information: express queue limit: 8 nodes for 2 hours\n";
    $header .= "; Information: high / normal queue limit: 36 hours\n";

    return $header;
}

