#!/usr/bin/perl -w
require 5.003;
use strict;

use lib '/usr/local/polygraph/BB';

require Command;
require Blob;
require Executer;
require Tools;
use Logger;

# Client -> Server Names 
# note: these are host names/ips, not necessarily poly-robot/server ips
my @Hosts = (
	'10.10.1.1' => '10.10.129.1', # hard coded pairs
	#&importHostNames(),  # or use shell environment variables
);

my $Proxy = '10.10.1.1:3128'; # one for all
my $Master = '10.10.0.1';     # monitoring machine
my $Take = time()-940000000;  # to avoid new logs overwriting old ones
my %HostPairs = @Hosts;


# executed before each run
my @HeadCmds = (
	Cmd('date'),
	Pairs(
		'ssh $clt killall polyclt',
		'ssh $srv killall polysrv',
	),
	All('ssh $host mkdir logs'),
	All('ssh $host /usr/sbin/ntpdate -b $Master'),
	Cmd('sleep 600'),
);

# executed after each run
my @TailCmds = (
	Pairs(
		'ssh $clt killall -INT polyclt',
		'ssh $srv killall -INT polysrv',
	),
	Pairs(
		'scp -p $clt:logs/clt.\* logs.archive/',
		'scp -p $srv:logs/srv.\* logs.archive/',
	),
	All('ssh $host mv logs logs.local/$name'),
	Cmd('exprep.pl logs.archive/clt.$exp_mask.*.log'),
	Cmd('date'),
);

my @BlobCfgs  = (
	{
		# this is a no-proxy run for Polygraph 1.x
		# for Polygraph 2.x, see pmix2 workload and
		# remove --proxy option

		name => 'no-proxy.rr$rr.take$Take',
		exp_mask => 'no-proxy.rr:*:.take$Take',

		body => [ Pairs(
			'ssh $clt sleep 10\;
			bin/polyclt
				--ports 1024:30000
				--verb_lvl 4
				--origin $srv:80
				--rng_seed $cmd_id
				--rep_cachable 80p
				--pconn_use_lmt zipf:64
				--nagle off
				--robots 1
				--req_rate $rr/sec
				--dhr 55p
				--pop_model unif
				--tmp_loc none
				--cool_phase 1min
				--goal -1:1hr:0.30
				--log_size 15MB
				--log logs/clt.$name.$clt.log
				--console logs/clt.$name.$clt.con
				--notify $Master:18256
				--label np$rr
				',
			'ssh $srv bin/polysrv
				--port 80
				--verb_lvl 4
				--rng_seed $cmd_id
				--idle_tout 135sec
				--pconn_use_lmt zipf:16
				--nagle off
				--xact_think norm:3s,1.5s
				--obj_bday const:-1year
				--obj_with_lmt 100p
				--obj_life_cycle const:2year 
				--obj_expire 100p=lmt+const:1
				--goal 4.1hr
				--log_size 15MB
				--log logs/srv.$name.$srv.log
				--console logs/srv.$name.$srv.con
				--notify $Master:18256
				--label np$rr
				',
			),
		],

		runs => [ Runs('rr', 500) ],
	},

	{
		name => 'fill.cl$cl.take$Take',
		exp_mask => 'fill.cl:*:.take$Take',

		body => [ Pairs(
			'ssh $clt bin/polyclt 
				--ports 1024:30000
				--verb_lvl 4
				--proxy $Proxy --origin $srv:80
				--rng_seed $cmd_id
				--unique_urls 1
				--rep_cachable 100p 
				--pconn_use_lmt const:1000
				--nagle off
				--robots $cl
				--launch_win 30sec
				--cool_phase 1min
				--goal -1:8hr:.30
				--log_size 35MB
				--log logs/clt.$name.$clt.log
				--console logs/clt.$name.$clt.con
				--notify $Master:18256
				--label fl$cl
				',
			'ssh $srv bin/polysrv 
				--port 80 
				--verb_lvl 4
				--rng_seed $cmd_id
				--pconn_use_lmt const:1000
				--nagle off
				--obj_bday const:-1year
				--obj_with_lmt 100p
				--obj_life_cycle const:2year 
				--obj_expire 100p=lmt+const:1
				--idle_tout 5min
				--log_size 35MB
				--log logs/srv.$name.$srv.log
				--console logs/srv.$name.$srv.con
				--notify $Master:18256
				--label fl$cl
				',
			),
		],

		runs => [ Runs('cl', 10) ],
	},

	{
		name => 'dcomm.rr$rr.take$Take',
		exp_mask => 'dcomm.rr:*:.take$Take',

		body => [ Pairs(
			'ssh $clt sleep 10\;
			bin/polyclt
				--ports 1024:30000
				--verb_lvl 4
				--proxy $Proxy --origin $srv:80
				--launch_win 1min
				--rng_seed $cmd_id
				--rep_cachable 80p
				--pconn_use_lmt zipf:64
				--nagle off
				--robots 1
				--req_rate $rr/sec
				--dhr 55p
				--pop_model unif
				--tmp_loc none
				--cool_phase 1min
				--goal -1:4hr:0.30
				--log_size 15MB
				--log logs/clt.$name.$clt.log
				--console logs/clt.$name.$clt.con
				--notify $Master:18256
				--label dc$rr
				',
			'ssh $srv bin/polysrv
				--port 80
				--verb_lvl 4
				--rng_seed $cmd_id
				--pconn_use_lmt zipf:16
				--nagle off
				--idle_tout 135sec
				--xact_think norm:3s,1.5s
				--obj_bday const:-1year
				--obj_with_lmt 100p
				--obj_life_cycle const:2year 
				--obj_expire 100p=lmt+const:1
				--goal 4.1hr
				--log_size 15MB
				--log logs/srv.$name.$srv.log
				--console logs/srv.$name.$srv.con
				--notify $Master:18256
				--label dc$rr
				',
			),
		],

		runs => [ Runs('rr', 100, 150) ],
	},
	{
		# these specs assume that all PGL files
		# are in pgs/ and pgs/include remote directories
		# you may want to adjust @HeadCmds to sync those
		# directories automatically before each run

		# remove --proxy option for transparent setups
		# and no-proxy tests

		name => 'pmix2.take$Take',
		exp_mask => 'pmix2.take:*:ip',

		body => [ Pairs(
			'ssh $clt sleep 10\;
			bin/polyclt
				--proxy $Proxy
				--config pgs/polymix-2.pg
				--cfg_dirs pgs/include
				--verb_lvl 10
				--rng_seed $cmd_id
				--log logs/clt.$name.ip$clt.log
				--console logs/clt.$name.ip$clt.con
				--notify $Master:18256
				--label pm2
				',
			'ssh $srv bin/polysrv
				--config pgs/polymix-2.pg
				--cfg_dirs pgs/include
				--verb_lvl 10
				--rng_seed $cmd_id
				--idle_tout 5min
				--log logs/srv.$name.ip$srv.log
				--console logs/srv.$name.ip$srv.con
				--notify $Master:18256
				--label pm2
				',
			),
		],

		# this is left for backwards compatability, not used
		runs => [ Runs('rr', map { sprintf('%.2f', $_/1.) }
			1
		) ],
	},
	{
		# warning: this workload specs comes from the first 
		# bake-off tests and may need adjustments with later
		# version of Polygraph
		name => 'pmix1.rr$rr.take$Take',
		exp_mask => 'pmix1.rr:*:.take$Take',

		body => [ Pairs(
			'ssh $clt polyclt
				--ports 1024:30000
				--verb_lvl 4
				--proxy $Proxy --origin $srv:80
				--launch_win 1min
				--rng_seed $cmd_id
				--rep_cachable 80p
				--robots 1
				--req_rate $rr/sec
				--dhr 55p
				--pop_model unif
				--tmp_loc none
				--cool_phase 1min
				--goal -1:1hr:0.30
				--log logs/clt.$name.$clt.log
				--console logs/clt.$name.$clt.con
				',
			'ssh $srv polysrv
				--port 80
				--verb_lvl 4
				--rng_seed $cmd_id
				--idle_tout 135sec
				--xact_think norm:3s,1.5s
				--goal 1.1hr
				--log logs/srv.$name.$srv.log
				--console logs/srv.$name.$srv.con
				',
			),
		],

		runs => [ Runs('rr', 10, 100) ],
	},
);


$| = 1;

my @BlobPlan = ();

my @ActiveBlobNames = @ARGV;
my $FilterNames = scalar @ActiveBlobNames;
my @PossibleBlobNames = ();

foreach my $blob_cfg (@BlobCfgs) {
	my $blob = new BB::Blob($blob_cfg->{name});
	die() unless $blob_cfg->{body};
	die() unless $blob_cfg->{runs};

	$blob->exp_mask($blob_cfg->{exp_mask});

	$blob->head($blob_cfg->{head} || [@HeadCmds]);
	$blob->body($blob_cfg->{body});
	$blob->tail($blob_cfg->{tail} || [@TailCmds]);

	foreach (@{$blob_cfg->{runs}}) {
		my $b = $blob->clone($_);

		push @PossibleBlobNames, $b->name();

		# filter out extras
		if ($FilterNames) {
			next if ! grep { $b->name() =~ m/$_/; } @ActiveBlobNames;
		}

		push @BlobPlan, $b;
	}
}

if (!@BlobPlan) {
	die("No blobs!\n") unless $FilterNames;
	die(sprintf("No matching blobs among: %s\n",
		join(' ', @PossibleBlobNames)));
}

&Log("pid: $$");
&printRoutes(@Hosts);

&Log("starting...");
&Log("plan: ", join(' ', map { $_->name() } @BlobPlan));

&startNext(); # start the sequence

while (defined &BB::Executer::Step(60)) { ; }

&Log("terminating...");

exit 0;


sub startNext {
	my $blob = shift @BlobPlan;
	return &Log("no more blobs") unless $blob;

	$blob->run(\&blobDone);
}

sub blobDone {
	&startNext();
}


sub Cmd {
	my ($tmpl, $label) = @_;
	return new BB::Command($tmpl, $label || 'local');
}

sub All {
	my $tmpl = shift;
	my $cmd = Cmd($tmpl, '$host');

	my @cfgs = Cfgs('host', @Hosts);
	return BB::Tools::Breed($cmd, @cfgs);
}

sub Pairs {
	my ($tmpl_clt, $tmpl_srv) = @_;

	my $cmd_clt = Cmd($tmpl_clt, '$clt');
	my $cmd_srv = Cmd($tmpl_srv, '$srv');

	# these are actually the same
	my @cfgs_clt = Cfgs2(\%HostPairs);
	my @cfgs_srv = Cfgs2(\%HostPairs);

	# note the order!
	return 
		BB::Tools::Breed($cmd_srv, @cfgs_srv),
		BB::Tools::Breed($cmd_clt, @cfgs_clt);
}

sub Runs {
	my $var_name = shift;

	return Cfgs($var_name, @_);
}

sub Cfgs {
	my $var_name = shift;

	my @cfgs = ();

	foreach (@_) {
		push @cfgs, {
			$var_name => $_,
			Proxy => $Proxy,
			Take => $Take,
			Master => $Master,
		};
	}

	return @cfgs;
}

sub Cfgs2 {
	my $hosts = shift;

	my @cfgs = ();

	my ($clt, $srv);
	while (($clt, $srv) = each %{$hosts}) {
		push @cfgs, { clt => $clt, srv => $srv, 
			Proxy => $Proxy,
			Take => $Take,
			Master => $Master,
			};
	}

	return @cfgs;
}

# read well-known environment variables for clt/srv addresses
sub importHostNames {
	my $err = sprintf("bad \$clients or \$servers env vars!\nclt: %s\nsrv: %s;\nstopped",
		$ENV{'clients'} || '<none>',
		$ENV{'servers'} || '<none>');

	die($err) if !$ENV{'clients'} || !$ENV{'servers'};

	my @clts = split(/\s+/, $ENV{'clients'});
	my @srvs = split(/\s+/, $ENV{'servers'});

	die($err) if !@clts || !@srvs;
	die($err) if (scalar @clts) != (scalar @srvs);
	die($err) if grep { !&hostAddr('[13579]$', $_) } @clts;
	die($err) if grep { !&hostAddr('[02468]$', $_) } @srvs;

	my @res = ();
	while (@clts && @srvs) {
		push @res, pop @clts, pop @srvs;
	}

	return @res;
}

sub printRoutes {
	my @routes = @_;

	my $table = '';
	for (my $i = 0; $i <= $#routes; $i += 2) {
		$table .= sprintf("%s => %s\n", $routes[$i], $routes[$i+1]);
	}
	&Log("routing table:\n", ButifyBuf($table));
}

sub hostAddr {
	my $pattern = shift;
	for (shift) {
		return undef unless $_;
		my @comps = split /\./;
		return undef if !@comps;
		return undef if $comps[$#comps] !~ /$pattern/;
	}
	return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1