#!/usr/bin/perl -w

# Usage: brutal <url>
#
# brutal creates the emcast group <url> and then joins it multiple
# times.  The joiners are randomly killed and restarted.  If a 
# core file is created, 
#
# Internally, it uses the emcast program to launch the programs.

use IPC::Open2;

my $emcast = "../src/emcast -l";  # Location of emcast program
#my $emcast = "../btp/btpcat -l";  # Location of emcast program

my $max_joiners = 0;           # Number of clients who join
my $max_joins   = 1;           # Number of times each joiner will join
my $max_sends   = 1;           # Number of sends per join
my $send_rate   = 1.0;         # Time between sends

my $create_sleep_time = 1;     # Seconds to sleep after creating group
my $interjoin_sleep_time = 0;  # Seconds to sleep between joins
my $join_sleep_time   = 5;     # Seconds to sleep after joining group
my $linger_time       = 5;     # Seconds to wait after sending last message

my $creator_sends = 0;         # Creator sends
my $joiners_killed = 0;        # Kill every other joiner?

my $debug = 0;
my $log = 0;    # Stderr process log 
my $log2 = 1;   # Brutal log

my $id = 0;

my $options = "";
#$options = "-Obtp_debug_flags 0xffffffff";
#$options = "-d 0xc5cc";


####################
# Parse command line

while ($#ARGV > 0)
{
    $_ = shift(@ARGV);

    if    (/--joiners/)  { $max_joiners = shift(@ARGV); }
    elsif (/--joins/)    { $max_joins   = shift(@ARGV); }
    elsif (/--sends/)    { $max_sends   = shift(@ARGV); }
    elsif (/--sendrate/) { $send_rate   = shift(@ARGV); }
    elsif (/--linger/)   { $linger_time = shift(@ARGV); }
    elsif (/--kills/)    { $joiners_killed = 1;         }
    elsif (/--debug/)    { $debug       = 1;            }
    elsif (/--log/)      { $log         = 1;            }
    elsif (/--log-brutal/){ $log2       = 1;            }
    else                 
    { 
	print STDERR "$0: Bad option: $_\n";
	usage();                     
    }
}

usage () if ($#ARGV < 0);
my $url = shift(@ARGV);


####################

my @pids = ();

sub main
{
    $| = 1;
    $SIG{'INT'} = 'on_sigint';

    # Launch creator
    emcreate ($url);
    sleep ($create_sleep_time);

    # Launch joiners
    my $i;
    for ($i = 0; $i < $max_joiners; $i++)
    {
	emjoin ($url, $i);
	sleep ($interjoin_sleep_time) if $interjoin_sleep_time;
    }

    # Wait for all but one (the creator) to finish
    while ($#pids > 0 && (my $kid = wait) != -1)
    {
	print STDERR "KID $kid DONE\n" if $debug;

	if (-f 'core')
	{
	    rename ('core', "core.$kid");
	    print STDERR "$0 WARNING: core file (moved to core.$kid)\n";
	}

	for ($i = 0; $i <= $#pids; $i++)
	{
	    if ($pids[$i] == $kid)
	    {
		splice (@pids, $i, 1);
		last;
	    }
	}
    }

    kill_processes();

    exit 0;

}

main();


####################


sub usage
{
    print STDERR ("Usage: $0 [OPTIONS] <URL>\n");
    exit 1;
}


sub emcreate
{
    my $url = $_[0];

    print STDERR "CREATE $url\n" if $debug;
    run ($id++, "$emcast $options $url", \&run_creator);
}


sub emjoin
{
    my ($url, $num) = @_;

    print STDERR "JOIN $url $num\n" if $debug;
    run ($id++, "$emcast $options $url", \&run_joiner);
}


sub run
{
    my ($id, $cmd, $fun) = @_;
    my $pid;

    if ($pid = fork)         # Parent
    {
	push (@pids, $pid);
    }
    elsif (defined $pid)     # Child
    {
	setpgrp (0, $$);
	$fun->($id, $cmd);
	exit (0);
    }
    else                     # Error
    { 
	die ("Fork error: $!\n"); 
    }
}


sub run_creator
{
    my ($id, $cmd) = @_;
    my $pid;
    my ($tot_sends, $tot_recvs, $tot_errs) = (0, 0, 0);

    open (STDERR, ">creator-$$.log") if $log;
    if ($pid = open2(\*RDR, \*WTR, $cmd))
    {
	$max_sends = 0 if !$creator_sends;

	while (1) 
	{
	    # Send and receive data
	    my ($sends, $recvs, $errs);
	    ($sends, $recvs, $errs) = loop ($pid);
	    $tot_sends += $sends;
	    $tot_recvs += $recvs;
	    $tot_errs  += $errs;
	}
    }
    else
    {
	die ("open2 error: $!\n");
    }

    print ("CREATOR: SEND $tot_sends RECV $tot_recvs ERROR $tot_errs\n");
}


sub run_joiner
{
    my ($id, $cmd) = @_;
    my $i;
    my $pid;
    my ($tot_sends, $tot_recvs, $tot_errs) = (0, 0, 0);

    # Do $max_runs runs
    open (STDERR, ">joiner-$$.log") if $log;
    for ($i = 0; $i < $max_joins; $i++)
    {
	# Launch emcast
	if ($pid = open2(\*RDR, \*WTR, $cmd))
	{
	    sleep ($join_sleep_time) if $join_sleep_time;

	    # Send and receive data
	    my ($sends, $recvs, $errs);
	    ($sends, $recvs, $errs) = loop ($pid);
	    $tot_sends += $sends;
	    $tot_recvs += $recvs;
	    $tot_errs  += $errs;

	    # Close or kill emcast
	    if ($joiners_killed && ($i % 2))
	    {
		print STDERR "$pid KILL\n" if $debug;
		kill (9, $pid);
	    }
	    else
	    {
		print STDERR "$pid CLOSE\n" if $debug;
		close (WTR);
	    }

	    waitpid $pid, 0;
	}
	else
	{
	    die ("open2 error: $!\n"); 
	}
    }

    print ("JOINER $id: SEND $tot_sends RECV $tot_recvs ERROR $tot_errs\n");

    exit 0;
}


sub loop
{
    my ($pid) = @_;

    my $num_sends = 0;

    if ($num_sends < $max_sends)
    {
	$num_sends++;
	print WTR "MESSAGE $pid $num_sends\n";
	print "$pid SEND: MESSAGE $pid $num_sends\n" if $log2;
    }

    my ($rin, $rout);
    my ($timeout, $timeout_time);
    my $linger = 0;

    $timeout = $send_rate;
    $timeout = undef if !$max_sends;
    $timestart = time();
    $rin = '';
    vec($rin, fileno(RDR), 1) = 1;

    my ($recvs, $errs) = (0, 0);

    while (my ($nfound) = select($rout = $rin, undef, undef, $timeout))
    {
	if (vec($rout, fileno(RDR), 1))
	{
	    $! = 0;
	    my $line = readline(*RDR);
	    if ($! != 0)
	    {
		$errs++;
		print STDERR "ERROR: readline failed in loop\n";
		last;
	    }
	    print STDERR "EOF\n" if !defined $line;
	    last if (!defined($line));
	    $recvs++;

	    print STDERR $line if $debug;
	    print STDOUT "$pid RECV: $line" if $log2;
	}

	if (defined($timeout))
	{
	    my $timepassed = time() - $timestart;

	    # Check linger timer
	    if ($linger && $timepassed >= $linger_time)
	    {
		last;
	    }
	    # Check send timer
	    elsif (!$linger && $timepassed >= $send_rate)
	    {
		$num_sends++;
		print WTR "MESSAGE $pid $num_sends\n";
		print "$pid SEND: MESSAGE $pid $num_sends\n" if $log2;
		# Check if we can quit
		if ($num_sends >= $max_sends)
		{
		    if ($linger_time)  # Linger
		    {
			$linger = 1;
			$timeout = $linger_time;
			$timestart = time();
		    }
		    else
		    {
			last;
		    }
		}
		else
		{
		    # Set next timer
		    $timeout = $send_rate;
		    $timestart = time();
		}
	    }
	    # Update timer
	    else
	    {
		if ($linger)
		{
		    $timeout = $linger_time - $timepassed;
		}
		else
		{
		    $timeout = $send_rate - $timepassed;
		}
	    }
	}

	$rin = '';
	vec($rin, fileno(RDR), 1) = 1;
    }

    return ($num_sends, $recvs, $errs);
}



sub on_sigint
{
    kill_processes ();
    exit 0;
}


sub kill_processes
{
    # Kill all children
    foreach $pid (@pids)
    {
	kill (-9, $pid);
	waitpid ($pid, 0);
    }
}
