#!/usr/local/bin/perl

# This file runs one experiment at a time. It sets up the correct
# executable names, etc to be used and calls the oo7.tcl file with
# the approrpriate parameters

$K1 = 1024;

$OUTPUT_AUTOFLUSH = 1;
$user = $ENV{"USER"};
$home = $ENV{"HOME"};
$G_top = "$home/thor/alpha";
$G_finaldir = "NULL"; # The directory name in the results directory

# $G_dbdir = "$G_top/or/tests"; # TTT
$G_dbname = "small";
$G_db1k_fname = "db.1k"; # Name of the db files
$G_db4k_fname = "db.4k";
$G_db8k_fname = "db.8k";
$G_db8k_bigslots_fname = "db.8k.64";
$G_db_fname = "NULL";
$G_iters = 2;
# $G_iters = 1; # TTT
$G_counter = 0;
@G_trav_prob = ("89.11.0"); # Gives us 100, 20

@G_travlist = (200, 6, 1, 3);

$G_fe_exec = "NULL";
$G_or_exec = "NULL";

$G_or_exec_1k = "or.1k";
$G_fe_exec_1k = "fe.1k";

$G_or_exec_4k = "or.4k";
$G_fe_exec_4k = "fe.4k";

$G_or_exec_8k = "or.8k";
$G_fe_exec_8k = "fe.8k";

$G_or_exec_8k_bigslots = "or.8k.64";
$G_fe_exec_8k_bigslots = "fe.8k.64";

$G_fe_exec_lru = "fe.lru";
$G_fe_exec_optimal = "fe.optimal";

$G_fe_machine = "fugue";
$G_or_machine = "harmony";

# $G_fe_machine = "clef"; # TTT
# $G_or_machine = "note"; # TTT


sub usage_assert { # Parameter Boolean, string
    local($val, $str) = @_;
    if ($val) {return;}
    usage ($str);
}

sub usage { # Paramaters: Error string
    local($err) = @_;
    print "USAGE: $err\n";
    $usage = join(' ',
	  "runexp [-e] [-st <small | medium>] [-compact <pagesize>]",
	  "[-trace <opt | lru>]\n",
	  "      [-dynamic <dummy>] [-f <femachine>] [-o <ormachine]\n",
	  "      [-top <dir>] [-fg]\n",
	  "      [-dbdir <dir>] [-bigslots] [-test]\n\n",
	  "-e                      : If eviction has to be used [def: off]\n",
	  "-test                   : Not to invoke the experiment. Just test\n",
	  "-fg                     : Compaction/eviction in foreground\n",
	  "-st <small | medium> <p>: Normal OO7 (small/medium) with\n",
	  "                        : <p> being the pagesize as in -compact\n",
	  "-compact <pagesize>     : Compaction experiments (1, 4 or 8 for\n",
          "                          1/4/8K expers)\n",
	  "-sensitivity <factor>   : Sensitivity experiments (discard,\n",
	  "                          scan-factor, epoch, scan-pointers)\n",
          "                          1/4/8K expers)\n",
	  "-bigslots               : If 64-bit slots are used [def: off]\n",
          "                        : To be used only with -st\n",
	  "-trace opt | lru        : Trace driven optimal and LRU expers\n",
	  "                          Use with -compact, -hotcold\n",
 	  "-hotcold <shift| static>: Hotcold workloads. Use with -compact\n\n",
          "Note: All options are mutually exclusive except as stated\n\n",
          "-top <dir>              : Dir where the executables are\n",
          "-dbdir <dir>            : DB dir where the databases are\n",
          "                        : (default: ~/thor/alpha or ~/thor\n",
          "-f <femachine>          : Machine on which to run the FE\n",
          "-o <ormachine>          : Machine on which to run the OR");
    print "$usage\n\n";
    exit;
}

sub set_page_params {
    # Params: (IN) use_evitcion, page_size, bigslots, (OUT) or_exec, fe_exec,
    #         dbfname, fe_args 
    # effects: Set the parameters passed according to the page_size

    local($use_eviction, $page_size, $bigslots) = @_;
    $or_exec = \$_[3];
    $fe_exec = \$_[4];
    $dbfname = \$_[5];
    $fe_args = \$_[6];

    $$or_exec = $G_or_exec_8k;
    $$fe_exec = $G_fe_exec_8k;
    $$dbfname = $G_db8k_fname;

    $$fe_args = "";

    if ($use_eviction) {
	$$fe_args = "-fargs \"-m 1 -e\"";
    } else {
	$$fe_args = "-fargs \"-m 1\"";
    }

    if ($bigslots) {
	if ($page_size != 8) {
	    print "Big slots are to be used with 8K pages only. Exiting ...\n";
	    exit;
	}
	$$or_exec = $G_or_exec_8k_bigslots;
	$$fe_exec = $G_fe_exec_8k_bigslots;
	$$dbfname = $G_db8k_bigslots_fname;
    }

    if ($page_size == 1) {    # Compaction with adaptive prefetching
	$$dbfname = $G_db1k_fname;
	$$fe_exec = $G_fe_exec_1k;
	$$or_exec = $G_or_exec_1k;
	if ($use_eviction) {
	    print "Eviction with 1K pages? Exiting ...\n";
	    exit;
	}
	$$fe_args = ""; # Switch adaptive prefetching on
    } elsif ($page_size == 4) {
	$$dbfname = $G_db4k_fname;
	$$fe_exec = $G_fe_exec_4k;
	$$or_exec = $G_or_exec_4k;
    }
}

sub basic_run {
    # Parameters is_hot, travlist, rest of arguments

    # Uses:    G_dbdir, G_iters, G_counter
    # effects: Run oo7.tcl with the arguments given in the parameters
    #          Rest of arguments can override the previous ones

    local($is_hot,$travlist,@rest) = @_;

    if ($is_hot) {
	$hot_str = "-temp hot";
    } else {
	$hot_str = "-temp cold";
    }

    if ($G_db_fname eq "NULL") {
	print "Internal error: DB filename missing. Check code\n";
	exit;
    }
    $command = "oo7.tcl";
    $outdir = "$G_outdir/$G_finaldir";
    if (!(-d $outdir)) {
	system("mkdir $outdir");
    }

    $fe_file = "$outdir/${G_outprefix}";
    $or_file = "/dev/null";
    $dbfile = "$G_dbdir/$G_db_fname";

    if (-e $fe_file) {
	system("echo FILE OVERWRITTEN: $fe_file >> $G_outdir/LOG");
	print "FILE OVERWRITTEN: $fe_file. Exiting\n";
	exit;
    }

    if ($foreground) {
	$fore_args = "-fargs \"-R\"";
    }

    $finalargs = join(' ', $command, $hot_str, "-trav \"$travlist\"",
		      "-i $G_iters", "-db $dbfile", "-dbname $G_dbname",
		      "-fdat $fe_file", "-odat $or_file",
		      "-f $G_fe_machine", "-o $G_or_machine",
		      "-fe $G_fe_exec", "-or $G_or_exec",
		      "-d $G_top", $fore_args,
		      @rest);
    #print "XXXXXXXXXXXX $finalargs\n";
    $G_counter++;
    system("echo $G_counter > $G_counter_file");
    if ($G_testmode) {
	print "FINAL COMMAND: $finalargs\n";
    } else {
	system "$finalargs";
    }
}

sub clust { # Param: a list of 3 ints of clustering probs:  t6, t1, t3
    # Returns 0 for poor clustering, 1 for avg and 2 for good
    $probs = $_[0];
    $probs =~ s/(\{|\})//g; # Remove the parens

    ($t6,$t1,$t3) = split(/ /,$probs);
    if ($t3 > 50) {
	return 2; # Good clustering
    }
    return 1; # Avg clustering
}

sub clustering { # Similar to clust except that it uses param of x.y.z
    local($trav) = @_;
   
    ($t6,$t1,$t3) = split(/\./, $trav);
    return clust("{$t6 $t1 $t3}");
}

sub normal_exp { # Parameter: small or medium, page_size( 1, 4, 8), bigslots
    # effects: Run the normal experiments for OO7

    # Cold: Medium and small: T6, T1, T2a, T2b
    # Hot:  Small: T6, T1

    local($size, $use_eviction, $page_size, $bigslots) = @_;

    if ($size eq "small") {
	$G_dbname = "small";
    } else {
	$G_dbname = "medium";
    }

    $G_finaldir = "normal";
    set_page_params($use_eviction, $page_size, $bigslots, $G_or_exec, $G_fe_exec,
		    $G_db_fname, $args);
    # print "$use_eviction, $page_size, $G_or_exec, $G_fe_exec, \
    # $G_db_fname, $args\n";
    $outpref = "p${page_size}_e${use_eviction}_b${bigslots}_${size}";
    $G_outprefix = "${outpref}_cold";

    $travlist = "6 1 2a 2b";
    if ($use_eviction) {
	$fcache = 9645;
    } else {
	$fcache = 7938;
    }

    $args = "$args -fcache $fcache";
    if ($bigslots) {
	$args = "$args -ocache 29696 -oargs \"-l 7168\"";
    }
    

    basic_run(0, $travlist, $args);
    if ($size eq "small") {
	$G_outprefix = "${outpref}_hot";
	basic_run(1, "6 1",  $args);
    }
}

sub get_dirname { # Params: $is_hotcold, $is_dynamic
    # Given whether the traversal is dynamic, returns the directory name to
    # be used inside the the results dir
    local($is_hotcold, $is_dynamic) = @_;
    if ($is_hotcold) {
	if ($is_dynamic) { return "hotcold-dynamic";}
	return "hotcold-static";
    }
    return "compact";
}

sub cache_filter { # Params: use_eviction, page_size, is_hotcold
    # is_dynamic, traversal, cache_sizes array

    # Logic for T1:
    #
    # (A) Below and including 15344 bytes (Does not fit in objects)
    #
    # ROT entry = 16 bytes. Avg objszie = 27.2 (+2 for offset) = 29.2 bytes
    # ROT overhead = 16/29.2 = 54.8% assuming perfect packing in cache
    # FPCS uses 50% of the ROT entries only since half the objects in each page
    # are not installed. Thus, FPCS cache = HACS-cache * 1.548/1.274
    #
    # (B) Above 15344 and below 29 K. There is a constant ROT overhead of 713
    #	KB for HACS. Thus, FPCS-cache = (HACS-cache + 7713)/1.274
    #	We divide by 1.274 since the extra cached pages will cause only 1/2
    #   of them to use ROT space
    #
    # (C) Fits in pages: Same ROT size since same no. of ROT entries used


    local($use_eviction, $page_size, $is_hotcold, $is_dynamic,
	  $traversal, @cache_sizes) = @_;
    if (!$use_eviction || $page_size == 4) {
	return @cache_sizes;
    }

    @result = ();
    if (!$is_hotcold) {
	# Normal cache experiments
	foreach $csize (@cache_sizes) {
	    $new_csize =  $csize;
	    if ($traversal eq "1") {
		if ($csize < 16 * $K1) {
		    $new_csize =  $csize * 1.215;
		} elsif ($csize < 28 * $K1) {
		    $new_csize =  ($csize + 7713)/1.274;
		}
	    } elsif ($traversal eq "150") {
                if ($csize < 5 * $K1) {
                    $new_csize =  $csize * 1.41;
                } elsif ($csize < 28 * $K1) {
                    $new_csize =  ($csize + 2074)/1.0625;
                }
            } elsif ($traversal eq "200") {
                if ($csize < 9 * $K1) {
                    $new_csize =  $csize * 1.34;
                } elsif ($csize < 28 * $K1) {
                    $new_csize =  ($csize + 4115)/1.13;
                }
            } elsif ($traversal eq "250") {
                if ($csize < 12 * $K1) {
                    $new_csize =  $csize * 1.28;
                } elsif ($csize < 28 * $K1) {
                    $new_csize =  ($csize + 6020)/1.2;
                }
	    } elsif ($traversal eq "6") {
		$new_csize = $csize + 50;
	    } elsif ($traversal eq "3") {
		$new_csize = $csize * 1.05;
	    }
	    push(@result, int $new_csize);
	}
    } else {
	# Hotcold experiments
	foreach $csize (@cache_sizes) {
	    $new_csize =  $csize;
	    $cl = clustering($traversal);
	    if ($cl == 0) {
		$new_csize = $csize; # XXX Needs to be fixed
	    } elsif ($cl == 1) {
		if ($csize <= 20 * $K1) {
		    $new_csize = $csize * 1.30;
		} else {
		    $mcsize = $csize/$K1;
		    $new_csize = 1024 * $mcsize * (1.565 - $mcsize * 0.013);
		}
	    } else {
		$new_csize = $csize * 1.05;
	    }
	    push(@result, int $new_csize);
	}
    }

    # print "RRRRRRRR @result\n";
    return @result;
}

sub get_cache_list { # Params: use_eviction, page_size, is_hotcold
    # is_dynamic, traversal
    # effects: Given the information whether the traversal is shifing or not,
    # dynamic or not and the name of the traversal, returns the list of
    # cachesizes to be used for the experiment

    local($use_eviction, $page_size, $is_hotcold, $is_dynamic,$traversal) = @_;

    @cachesizes = (5 * $K1, 9 * $K1, 10 * $K1, 12 * $K1, 15344, 20 * $K1,
		   25 * $K1, 29 * $K1, 35 * $K1);

    if ($page_size == 4) {
	# Get cachesizes as specified in Kossman's paper
	# We supply our sizes in KBytes.
	@cachesizes = (800, 1600, 2400, 3200, 4000, 4800, 5600, 6400, 7200,
		       8000);
    } elsif ($is_hotcold) {
	if (clust($traversal) == 0) {
	    # Bad clustering
	    @cachesizes = (2 * $K1, 4 * $K1, 6 * $K1, 8 * $K1, 10 * $K1,
			   12 * $K1, 14 * $K1, 16 * $K1);
	} else {
	# Medium/good clustering
         @cachesizes = (9 * $K1, 10 * $K1, 12 * $K1, 15344, 20 * $K1,
                           25 * $K1, 29 * $K1, 35 * $K1);
       }
    } elsif ($traversal eq "6") {
	# Normal workloads
	@cachesizes =
	    (160, 240, 1 * $K1, 1 * $K1 + 512, 2 * $K1, 2 * $K1 + 512,
	     3 * $K1,  3 * $K1 + 512, 4 * $K1,  4 * $K1 + 512);
    }
    return cache_filter($use_eviction, $page_size, $is_hotcold, $is_dynamic,
			$traversal, @cachesizes);
}

sub cache_exp {
    # Parameters: use_eviction, page_size Trav list, cold_also, args that are not
    #             interpreted by this but passed to oo7.tcl directly
    # effects: Runs an experiment for the given parameters with
    #          varying cachesizes. If cold_also is TRUE, run the cold
    #          experiments along with the hot ones. Else just the hot ones

    local($use_eviction, $page_size, $travlist, $cold_also, $args) = @_;

    $travname = $travlist;
    $prob_traversal = 0; # Whether the traversals involve dynamic
    # choosing of operations

    if ($travname =~ /\./) {
	# Contains a .; must be prob traversals. we will keep 3 numbers
	($t6,$t1,$t3) = split(/\./,$travname);
	$travlist = "{$t6 $t1 $t3}";
	$prob_traversal = 1;
    }

    foreach $cachesize (@G_cachesizes) {
	#print "Csize: $cachesize\n";
	$outpref = "p${page_size}_e${use_eviction}_c${cachesize}_t${travname}";
	$G_outprefix = "${outpref}_hot";

	# Special cases for traversals where the ROT is too small
	$obj_size = -1;
	if ($travname == 3 && $cachesize < 6 * $K1) {
	    $obj_size = 15;
	}
	if ($obj_size != -1) {
	    $args = "$args -fargs \"-b $obj_size\"";
	}


	basic_run(1, $travlist, "-fcache $cachesize", $args);
	if ($cold_also) {
	    $G_outprefix = "${outpref}_cold";
	    basic_run(0, $travlist, "-fcache $cachesize", $args);
	}
    }
}


sub general_exp {
    # Parameters: tracedir, is_hotcold, is_dynamic,
    #             use_eviction, page_size
    #             page_size interpretation: 1, 4 or 8

    local($tracedir, $is_hotcold,$is_dynamic, $use_eviction, $page_size) = @_;

    $G_finaldir = get_dirname($is_hotcold, $is_dynamic);
    $args = "";
    set_page_params($use_eviction, $page_size, 0, $G_or_exec, $G_fe_exec,
		    $G_db_fname, $args);

    $is_trace = $tracedir ne "NULL";

    if ($is_trace) { # Trace driven experiments
	$G_fe_exec = $G_fe_exec_lru;
	$G_finaldir = "trace-$G_finaldir";
	if ($trace_type eq "opt") {
	    $G_fe_exec = $G_fe_exec_optimal;
	}
    }

    @travlist = @G_travlist;
    $cold_also = 1;
    if ($page_size == 4) { # Experiments for comparing with Kossman
	$G_dbname = "small";
	$args = "$args -ocache 3584 -oargs \"-l 512\"";
	@travlist = (1, "2b");
    } else {
	$G_dbname = "medium";
    }

    if ($is_hotcold) {    # Hot cold workloads
	@travlist =  @G_trav_prob;
	$G_dbname = "shifting";
	$cold_also = 0;
	if ($is_dynamic) {
	    $args = "$args -hotcold dynamic";
	} else {
	    $args = "$args -hotcold static";
	}
    }

    # Run the experiments for each of the cases with different cachesizes
    foreach $trav (@travlist) {
	@G_cachesizes = get_cache_list($use_eviction, $page_size,
				       $is_hotcold, $is_dynamic, $trav);
	# @G_cachesizes = (8192); # TTT
	$final_args = $args;
	if ($is_trace) {
	    $tracefile = "$tracedir/t$trav";
	    $final_args = "$args -fargs \"-E $tracefile -R\"";
	}
	cache_exp($use_eviction, $page_size, $trav, $cold_also, $final_args);
    }
}

sub sensitivity_exp { # Param: Which experiment is it 

    local($factor) = @_;
    $G_finaldir = "sensitivity";
    $G_dbname = "medium";
    set_page_params(0, 8, 0, $G_or_exec, $G_fe_exec, $G_db_fname, $args);
    
    if ($factor eq "discard") {
	$factor_arg = "-d";
	@factor_list = (2, 3, 4, 7, 10);
    } elsif ($factor eq "scan-factor") {
	$factor_arg = "-f";
	@factor_list = (3, 4, 7, 10, 20);
    } elsif ($factor eq "epoch") {
	$factor_arg = "-r";
	@factor_list = (1, 10, 20, 50, 100, 500);
    } elsif ($factor eq "scan-pointers") {
	$factor_arg = "-n";
	@factor_list = (1, 2, 3, 4, 7, 10, 20);
    } else {
	print "Bad option for sensitivity\n";
	exit;
    }

    $travname = "200";
    if ($travname eq "1") {
	@sensitivity_caches = (5 * $K1, 10 * $K1, 15 * $K1);
	$G_dbname = "medium";
    } elsif ($travname eq "200") {
	@sensitivity_caches = (3 * $K1, 5 * $K1, 8 * $K1);
	$G_dbname = "medium";
    } else {
	$G_dbname = "shifting";
	$args = "$args -hotcold dynamic";
	@sensitivity_caches = (10 * $K1, 15 * $K1, 20 * $K1);
    }
    foreach $cachesize (@sensitivity_caches) {
	foreach $factor_val (@factor_list) {
	    $G_outprefix = "$factor${factor_val}_c${cachesize}";
	    $final_args = "$args -fargs \"$factor_arg $factor_val\"";
	    basic_run(1, $travname, "-fcache $cachesize", $final_args);
	}
    }
}

# ---------------------------------------------------------------------------
#                            The main program
# ---------------------------------------------------------------------------


if (!(-d $G_top)) {
    $G_top = "$home/thor";
    if (!(-d $G_top)) {
	print "Directory $home/thor/alpha or $home/thor must exist\n";
	exit;
    }
}

$nargs = $#ARGV + 1;
$kind = "NULL";
$hotcold = 0;
$dynamic = 0;
$use_eviction = 0;
$bigslots = 0;
$tracedir = "NULL";

# Process the options from the user

for ($i = 0; $i < $nargs; $i += 2) {
    $option = $ARGV[$i];
    $val = $ARGV[$i+1];
    if ($option eq "-st") {
	usage_assert($kind eq "NULL", "More than one option specified");
	$normal_size = $val;
	$kind = "normal";
	$page_size = $ARGV[$i+2];
	$i++;
	usage_assert($normal_size =~ "small|medium",
		     "Bad size to -st option");
	usage_assert($page_size =~ "1|4|8",
		     "Incorrect page size to -st option");

    } elsif ($option eq "-e") {
	$i--;
	$use_eviction = 1;
    } elsif ($option eq "-fg") {
	$i--;
	$foreground = 1;
    } elsif ($option eq "-test") {
	$i--;
	$G_testmode = 1;
    } elsif ($option eq "-bigslots") {
	$i--;
	$bigslots = 1;
    } elsif ($option eq "-compact") {
	$kind = "compact";
	$page_size = $val;
	usage_assert($page_size =~ "1|4|8",
		     "Incorrect page size to -compact option");
    } elsif ($option eq "-sensitivity") {
	$kind = "sensitivity";
	$factor = $val;
	usage_assert($factor =~ "discard|scan-factor|epoch|scan-pointers",
		     "Incorrect factor to -sensitivity option");
    } elsif ($option eq "-trace") {
	$trace_type = $val;
	usage_assert($trace_type =~ "opt|lru",
		     "Incorrect trace type to -trace option");
	$tracedir = "$G_top/traces";
    } elsif ($option eq "-hotcold") {
	$hotcold = 1;
	usage_assert($val =~ "static|shift", "Wrong type to -hotcold option");
	$dynamic = $val eq "shift";
    } elsif ($option eq "-dbdir") {
	$G_dbdir = $val;
    } elsif ($option eq "-top") {
	$G_top = $val;
    } elsif ($option eq "-f") {
	$G_fe_machine = $val;
    } elsif ($option eq "-o") {
	$G_or_machine = $val;
    } else {
	usage "Bad option given";
    }
}

$G_dbdir = $G_top;
$G_outdir = "$G_top/results";

if (!(-d $G_outdir)) {
    system("mkdir $G_outdir");
}

# Set up the output directory and file names

$G_counter_file = "$G_outdir/COUNTER";
if (!(-e $G_counter_file)) {
    system("echo 0 > $G_counter_file");
}

open(COUNTERFILE, "+<".$G_counter_file);
$G_counter = <COUNTERFILE>;
$G_counter =~ s/(\r|\n)//g;
close(COUNTERFILE);

if (!($G_counter =~ /\d+/)) {
    print "Bad counter value in file: XXX ${G_counter} XXX\n";
    exit;
}

# Check for sanity of params
# usage_assert((-e "$G_dbdir/$G_db1k_fname"), "1k db missing");
# usage_assert((-e "$G_dbdir/$G_db4k_fname"), "4k db missing");
# usage_assert((-e "$G_dbdir/$G_db8k_fname"), "8k db missing");
usage_assert(!$bigslots || $kind eq "normal",
	     "64-bit slots used without -st option");
usage_assert($kind ne "NULL", "No option specified");
usage_assert(!$dynamic || $kind eq "compact",
	     "Compact not specified for dynamic");

print "Running runexp ";
for ($i = 0; $i < $nargs; $i++) {
    print "$ARGV[$i] ";
}

print "\nOutput files counter is being started from $G_counter\n";

# Run the appropriate experiment

if ($kind eq "normal") {
    normal_exp($normal_size, $use_eviction, $page_size, $bigslots);
} elsif ($kind eq "compact") {
    general_exp($tracedir, $hotcold, $dynamic, $use_eviction, $page_size);
} elsif ($kind eq "sensitivity") {
    sensitivity_exp($factor);
} else {
    print "Not possible: Kind is $kind\n";
}
