OpenSolaris_b135/cmd/filebench/fbscript/filebench.pl

#!/usr/bin/perl
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

use POSIX;
use Socket;

my $MULTI_CLIENT = 0;
my $USE_XANADU = 0;
my $TIMEOUT = 60;
my $EOL = "\n";
my $FILEBENCH = "/usr/benchmarks/filebench";
my $PROG = "bin/go_filebench";
my $SHAREDFILEALLOCATOR;
my $TARGETPATH;
my $TARGETDIR;
my $FB_MASTERPATH;
my $STATSBASE;
my $CONFNAME;
my $FSCRIPT;
my $SCRIPT_NO;
my @CLIENTLIST = ();
my %CLIENTHASH = ();
my @CONFLIST;
my %MULTIDATA = ();
my %CMDLINEDATA = ();
my %DEFDATA = ();
my %CONFDATA = ();
my %STATSHASH = ();
my $OPTIONFLAGS = "cleanupstorage dofscheck";
@ext_stats=();
@file_stats=();
@arg_stats=();
@pid_arr=();

# The following if test caters for running benchpoint from an alternative path
#if (-r $ENV{"FILEBENCH") {
#	$FILEBENCH = $ENV{"FILEBENCH"};
#}

##############################################################################
## Configuration hash data operations
##############################################################################

# This sub allows a function program to extract the base directory for filebench
sub get_FILEBENCH {
    return ($FILEBENCH);
}

sub get_STATSBASE {
    return ($STATSBASE);
}

sub get_CONFNAME {
    return ($CONFNAME);
}

sub multi_putval {
    my ($key) = shift;
    my ($val) = shift;
    @{MULTIDATA{$key}} = ();
    push(@{ $MULTIDATA{$key} }, $val);
}

sub multi_getval {
    my ($key) = shift;
    return ("@{$MULTIDATA{$key}}");
}

sub multi_exists {
    my ($key) = shift;
    if (exists($MULTIDATA{$key})) {
	return (1);
    }
    return (0);
}

sub conf_getval {
    my ($key) = shift;
    return ("@{$CONFDATA{$key}}");
}

sub conf_reqval {
    my ($key) = shift;
    
    if (exists($CONFDATA{$key})) {
	return ("@{$CONFDATA{$key}}");
    }
    print "ERROR: required key \"$key\" missing from configuration\n";
    exit(1);
}

sub conf_exists {
    my ($key) = shift;
    if (exists($CONFDATA{$key})) {
	return (1);
    }
    return (0);
}

sub conf_hash {
    return(%CONFDATA);
}

##############################################################################
## Filebench Operations
##############################################################################

sub op_init {
}

sub op_load {
    my ($workload) = shift;
    $scriptname = conf_reqval("statsdir") . "/thisrun.f";

    if($workload ne '') {
	print ("Creating Client Script " . $scriptname . "\n");
	open (FSCRIPT, ">$scriptname");
	chmod (0755, $scriptname);
	print FSCRIPT "#!$FILEBENCH/$PROG -f\n\n";
	# Load the df
	print FSCRIPT "load $workload\n";
	# Load the user defined defaults
	op_load_defaults();

	# enable multiclient, if needed
	if ($MULTI_CLIENT == 1) {
	    print FSCRIPT "enable multi master=".multi_getval("masterhost").", client=".conf_getval("myname")."\n";
	}

	# Check to see if the path is legal and pointing to the correct FS
	if (conf_exists("dofscheck") == 1) {
	    print FSCRIPT "fscheck path=" . conf_reqval("dir") . " fstype=" . conf_reqval("filesystem") . "\n";
	}

	# Create the associated files and filesets
	print FSCRIPT "create filesets\n";
    }
    $SCRIPT_NO = 1;
    return(0);
}

sub op_fsflush {
        print FSCRIPT "fsflush fstype=" . conf_reqval("filesystem") . "\n";
}

sub op_set {
    my ($var, $val) = @_;
    if($var eq 'debug') {
	    print FSCRIPT "debug $val\n";
    } elsif($var ne '') {
	    print FSCRIPT "set \$$var=$val\n";
    }
    return(0);
}

sub op_eventrate {
    my ($eventrate) = shift;
	if ($eventrate ne '') {
		print FSCRIPT "eventgen rate=$eventrate\n";
		return(0);
	}
}

sub op_run {
    my ($time) = shift;
    print FSCRIPT "run $time\n";
    return(0);
}

sub op_sleep {
    my ($time) = shift;
    print FSCRIPT "sleep $time\n";
    return(0);
}

sub op_msg {
    my ($msg) = shift;
    print FSCRIPT "echo \"$msg\"\n";
    return(0);
}

sub op_quit {
    # Shutdown the appropriate processes
    print FSCRIPT "shutdown processes\n";

    # remove filesets, if requested
    if (conf_exists("cleanupstorage") == 1) {
	printf FSCRIPT "shutdown filesets\n";
    }

    # Quit filebench
    print FSCRIPT "quit\n";
    close(FSCRIPT);
}

sub op_statsdir {
    print FSCRIPT "stats directory ".conf_reqval("statsdir")."\n";
    return(0);
}

sub op_indiv_vars {
    my ($ivar) = shift;
    print FSCRIPT "echo \"\$$ivar\"\n";
    my ($imatch, $ierr, $ibefore, $iafter) = &expect(FSCRIPT,
						 $TIMEOUT, "filebench>");
   
    $ibefore =~ /(.*): (.*): (-*\d+)/;
    $imatch = $3;
    $imatch =~ s/^\s+//;
    chomp($imatch);
    return($imatch);
}

sub op_indiv_stats {
    my ($var) = shift;
    print FSCRIPT "echo \"\${stats.$var}\"\n";
    my ($match, $err, $before, $after) = &expect(FSCRIPT,
						 $TIMEOUT, "filebench>");
   
    $before =~ /(.*): (.*): (-*\d+)/;
    $match = $3;
    $match =~ s/^\s+//;
    chomp($match);
    return($match);
}

sub op_stats {
    my ($time) = shift;
    my ($warmup) = shift;
    my ($statsfile) = shift;
    my $mstrstatsdir = $STATSBASE."/".$CONFNAME;

    if ($MULTI_CLIENT == 1) {
	print FSCRIPT "domultisync value=1\n";
    }

    # Create the associated processes and start them running
    print FSCRIPT "create processes\n";

    if ($warmup ne '') {
	print FSCRIPT "warmup $warmup\n";
    }

    if (($time ne '') && ($statsfile ne '')) {
	# Clear the current statistics buffers
	print FSCRIPT "stats clear\n";

	# Start external statistics collection (if any)
	# Note all statistics arrays MUST be the same length !
	if (@ext_stats != ()) {
	    if (($#ext_stats == $#file_stats) && ($#ext_stats == $#arg_stats)) {
		$script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
		open (RUNSCRIPT, ">$script");
		chmod (0755, $script);
		print FSCRIPT "system \"$script\"\n";
		$SCRIPT_NO++;
		$index=0;
		foreach my $ext (@ext_stats) {
		    print RUNSCRIPT "$FILEBENCH/scripts/collect_$ext $ext $file_stats[$index] ";
		    print RUNSCRIPT  $mstrstatsdir;
		    print RUNSCRIPT " $time $FILEBENCH $arg_stats[$index] &\n";
		    $index++;
		}
	    }
	}
	close(RUNSCRIPT);

	# Sleep for the run time
	print FSCRIPT "sleep $time\n";

	# Snap the statistics
	print FSCRIPT "stats snap\n";

	# Dump the statistics to a raw file - out required due to filename constraint
	if ($MULTI_CLIENT == 1) {
	    print FSCRIPT "domultisync value=2\n";
	    print FSCRIPT "stats multidump \"$statsfile.out\"\n";
	} else {
	    print FSCRIPT "stats dump \"$statsfile.out\"\n";
	}

	# Statistics reaping occurs here
	if (@ext_stats != ()) {
	    if (($#ext_stats == $#file_stats) && ($#ext_stats == $#arg_stats)) {
		$script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
		open (RUNSCRIPT, ">$script");
		chmod (0755, $script);
		print FSCRIPT "system \"$script\"\n";
		$SCRIPT_NO++;
		foreach my $ext (@ext_stats) {
		    print RUNSCRIPT "$FILEBENCH/scripts/kill_stats $ext &\n";
		}
		close(RUNSCRIPT);
	    }
	}

	# Dump the statistics to a Xanadu compatible XML file
	if ($USE_XANADU) {
	    op_xmlstats($statsfile);

	    $script = $mstrstatsdir . "/stats$SCRIPT_NO.pl";
	    open (RUNSCRIPT, ">$script");
	    chmod (0755, $script);
	    print FSCRIPT "system \"$script\"\n";
	    $SCRIPT_NO++;

	    # The following loop adds the benchpoint run parameters and statistics into the filebench XML file
	    # We capture the meta data from the start of the filebench xml file
	    print RUNSCRIPT "#!/usr/bin/perl\n";
	    print RUNSCRIPT "\$phase=1;\n";
	    print RUNSCRIPT "open(STATSFILE,\"<".$mstrstatsdir."/$statsfile.xml\");\n";
	    print RUNSCRIPT "open(OSTATSFILE,\">".$mstrstatsdir."/$statsfile.new.xml\");\n";
	    print RUNSCRIPT "while (<STATSFILE>) {\n";
	    print RUNSCRIPT "\t\$temp=\$_;\n";
	    print RUNSCRIPT "\tif ((!((/.*meta.*/) || (/.*stat_doc.*/))) && (\$phase == 1)) {\n";
	    print RUNSCRIPT "\t\topen(XMLFILE,\"<".$mstrstatsdir."/$statsfile.config.xml\");\n";
	    print RUNSCRIPT "\t\twhile (<XMLFILE>) {\n";
	    print RUNSCRIPT "\t\t\tprint OSTATSFILE \$_;\n";
	    print RUNSCRIPT "\t\t}\n";
	    print RUNSCRIPT "\t\tclose(XMLFILE);\n";
	    print RUNSCRIPT "\t\t\$phase++;\n";
	    print RUNSCRIPT "\t}\n";
	    print RUNSCRIPT "\tprint OSTATSFILE \$temp;\n";
	    print RUNSCRIPT "}\n";
	    print RUNSCRIPT "close(STATSFILE);\n";
	    print RUNSCRIPT "close(OSTATSFILE);\n";
	    print RUNSCRIPT "unlink(\"".$mstrstatsdir."/$statsfile.xml\");\n";
	    print RUNSCRIPT "unlink(\"".$mstrstatsdir."/$statsfile.config.xml\");\n";
	    print RUNSCRIPT "system(\"mv ".$mstrstatsdir."/$statsfile.new.xml ".$mstrstatsdir."/$statsfile.xml\");\n";

	    $script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
	    open (RUNSCRIPT, ">$script");
	    chmod (0755, $script);
	    print FSCRIPT "system \"$script\"\n";
	    $SCRIPT_NO++;

	    print RUNSCRIPT "mkdir ".$mstrstatsdir."/xml\n";
	    print RUNSCRIPT "mkdir ".$mstrstatsdir."/html\n";

	    print RUNSCRIPT "mv ".$mstrstatsdir."/$statsfile.xml ".$mstrstatsdir."/xml/$statsfile.xml\n";

	    # Process XML file using Xanadu 2
	    print RUNSCRIPT "$FILEBENCH/xanadu/scripts/xanadu import ".$mstrstatsdir." ".$mstrstatsdir."/xml ".conf_reqval("function")."-".$mstrstatsdir."\n";
	    print RUNSCRIPT "$FILEBENCH/xanadu/scripts/xanadu export ".$mstrstatsdir."/xml ".$mstrstatsdir."/html\n";
	    close(RUNSCRIPT);
	}
    }
    return(0);	
}

sub op_xmlstats {
    my ($statsfile) = shift;
    my $mstrstatsdir = $STATSBASE."/".$CONFNAME;
    if($statsfile ne '') {
	print FSCRIPT "stats xmldump \"$statsfile.xml\"\n";	

	# The following loop adds the benchpoint run parameters and statistics into a temporary XML file
	open(OSTATSFILE,">".$mstrstatsdir."/$statsfile.config.xml");
	%CONFHASH = conf_hash();
	# There is no test for whether CONFHASH contains no keys 
	# The following two lines is to obtain the stats run directory name for xanadu meta data
	print OSTATSFILE "<meta name=\"RunId\" value=\"".conf_reqval("function")."-".$mstrstatsdir."\"/>\n";
	print OSTATSFILE "<stat_group name=\"Benchpoint Configuration\">\n";
	print OSTATSFILE "<cell_list>\n";
	foreach $k (keys(%CONFHASH)) {
	    print OSTATSFILE "<cell>@{ $CONFHASH{$k} }</cell>\n";
	}
	print OSTATSFILE "</cell_list>\n";
	print OSTATSFILE "<dim_list>\n";
	print OSTATSFILE "<dim>\n";
	print OSTATSFILE "<dimval>Value</dimval>\n";
	print OSTATSFILE "</dim>\n";
	print OSTATSFILE "<dim>\n";
	foreach $k (keys(%CONFHASH)) {
	    print OSTATSFILE "<dimval>$k</dimval>\n";
	}
	print OSTATSFILE "</dim>\n";
	print OSTATSFILE "</dim_list>\n";
	print OSTATSFILE "</stat_group>\n";
	close(OSTATSFILE);

	return(0);	
    }
    return(1);	
}

sub op_command {
    my ($command) = shift;
	if($command ne '') {
	    print FSCRIPT "$command\n";	
	}
	return(0);	
}

sub op_statshash {
    op_indiv_stats("iocount");	
    $STATSHASH{"iocount"} = op_indiv_stats("iocount");	
    $STATSHASH{"iorate"} = op_indiv_stats("iorate");	
    $STATSHASH{"ioreadrate"} = op_indiv_stats("ioreadrate");	
    $STATSHASH{"iowriterate"} = op_indiv_stats("iowriterate");	
    $STATSHASH{"iobandwidth"} = op_indiv_stats("iobandwidth");	
    $STATSHASH{"iolatency"} = op_indiv_stats("iolatency");	
    $STATSHASH{"iocpu"} = op_indiv_stats("iocpu");	
    $STATSHASH{"oheadcpu"} = op_indiv_stats("oheadcpu");	
    $STATSHASH{"iowait"} = op_indiv_stats("iowait");	
    $STATSHASH{"syscpu"} = op_indiv_stats("syscpu");	
    $STATSHASH{"iocpusys"} = op_indiv_stats("iocpusys");	
    return(%STATSHASH);
}

sub op_load_defaults {
# The following code causes an intermittent bug - may be fixed at a later date
# Prevents the capture of filebench default parameters
#    print FSCRIPT "vars\n";
#    my ($match, $err, $before, $after) = &expect(FSCRIPT,
#						 $TIMEOUT, "filebench>");
#    chomp($before);
#    $before =~ /(.*): (.*): (.*)/;
#    $match = $3;
#    my @vars = split(/ /, $match);
#    my $value = "";
#    # Cater for the default filebench commands
#    foreach my $var (@vars) {
#        if (!conf_exists($var)) {
#            $var =~ s/ //g;
#	    if ($var ne '') {
#		$value = op_indiv_vars($var);
#       	        push(@{ $CONFDATA{$var} }, $value);		   
#	    }
#	}
#    }

    # Cater for the user defined defaults
    foreach $var (keys(%CONFDATA)) {
        if (conf_exists($var)) {
            $var =~ s/ //g;
            my $val = conf_getval($var);

	    if (($SHAREDFILEALLOCATOR) and ($var eq "sharedprealloc")) {
		if (conf_reqval("myname") ne $SHAREDFILEALLOCATOR) {
		    $val = "0";
		}
	    }

	    if ($val ne "") {
		op_set($var, $val);
	    }
	}
    }
}

##############################################################################
## Local functions
##############################################################################

sub parse_profile {
    my ($profile) = shift;
    my ($config_section, $default_section, $multi_section);
    
    open(CFILE, "$profile") or 
	die "ERROR: couldn't open profile $profile";
    
    while(<CFILE>) {
	my ($line) = $_;
	chomp($line);
	$line =~ s/^\s+//; # Get rid of spaces
	
	if($line =~ /^#/ or $line eq "") {
	} else {
	    if($line =~ /}/) {
		if($multi_section == 1) {
		    $multi_section = 0;
		}
		if($default_section == 1) {
		    $default_section = 0;
		}
		if($config_section == 1) {
		    $config_section = 0;
		}
	    } elsif($multi_section) {
		$line =~ /([^\s]+)\s*=\s*(.+);/;
		my $opt = $1;
		my $val = $2;
		chomp($opt);
		chomp($val);
		my @vals = ();
		# Check to see if this needs to be a list
		if($val =~ /,/) {
		    push(@vals, $+) while $val =~
			m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
		    push(@vals, undef) if substr($val, -1,1) eq ',';
		    @{ $MULTIDATA{$opt} } = @vals;
		} else {
		    @{MULTIDATA{$opt}} = ();
		    push(@{ $MULTIDATA{$opt} }, $val);		   
		}	       
	    } elsif($default_section) {
		if ($line =~ /([^\s]+)\s*=\s*(.+);/) {
		    my $opt = $1;
		    my $val = $2;
		    chomp($opt);
		    chomp($val);
		    my @vals = ();
		    # Check to see if this needs to be a list
		    if(($val =~ /,/) && ($val !~ /"/)) {
			push(@vals, $+) while $val =~
			    m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
			push(@vals, undef) if substr($val, -1,1) eq ',';
			@{ $DEFDATA{$opt} } = @vals;
		    } elsif(exists($CMDLINEDATA{$opt})) {
			@{DEFDATA{$opt}} = ();
			push(@{ $DEFDATA{$opt} }, @{$CMDLINEDATA{$opt}});
		    } else {
			@{DEFDATA{$opt}} = ();
			push(@{ $DEFDATA{$opt} }, $val);		   
		    }
		} else {
		    if ($line =~ /([^;]+);/) {
			my $opt = $1;
			if ($OPTIONFLAGS =~ /$opt/) {
			    @{DEFDATA{$opt}} = ();
			    push(@{ $DEFDATA{$opt} }, "");
			}
		    }
		}
	    } else {
		if($line =~ /^CONFIG /) {
                    my $config = $line;
	 	    $config =~ s/CONFIG\s+(.+) {/$1/;
		    push(@CONFLIST, $config);
		    $config_section = 1;
		} elsif($line =~ /MULTICLIENT\s{/) {
		    $multi_section = 1;
		    $MULTI_CLIENT = 1;
		} elsif($line =~ /DEFAULTS\s{/) {
		    $default_section = 1;
		}
	    }
	}
    }
}


#
# Parse the configuration file
#
sub parse_config {
    my ($config) = shift;

    my $config_section = 0;

    print "parsing profile for config: $config\n";
    
    # Howdy
    seek(CFILE, 0, 0);
    
    while(<CFILE>) {
	# Read in the line and chomp...munch...chomp
	my ($line) = $_;
	chomp($line);
	$line =~ s/^\s+//; # Get rid of spaces

	# look for our stuff
	if ($line =~ /CONFIG $config /) {
	    $config_section = 1;
        }

        if($line =~ /}/) {
	    $config_section = 0;
        }

	# Skip until our config is found
	next if (!$config_section);

	next if ($line =~ /^#/ or $line eq "");

	if ($line =~ /([^\s]+)\s*=\s*(.+);/) {
	    my $opt = $1;
	    my $val = $2;
	    chomp($opt);
	    chomp($val);
	    my @vals = ();
	    # Check to see if this needs to be a list
	    if(($val =~ /,/) && ($val !~ /"/)) {
		push(@vals, $+) while $val =~
	            m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
		push(@vals, undef) if substr($val, -1,1) eq ',';
		@{ $CONFDATA{$opt} }  = @vals;
	    } else {
		@{CONFDATA{$opt}} = ();
		push(@{ $CONFDATA{$opt} }, $val);
	    }
	} else {
	    $line =~ /($OPTIONFLAGS);/;
	    my $opt = $1;
	    chomp($opt);
	    @{CONFDATA{$opt}} = ();
	    push(@{ $CONFDATA{$opt} }, "");
	}
    }
    
    # Bye, bye
    #close(CFILE) or die "ERROR: config file closing difficulties";
    return \%confdata;
}

sub build_run
{
    # The following function is taken from the user's function file
    pre_run();

    # Set the global statistics directory for this run
    op_statsdir();

    # The following function is taken from the user's function file
    bm_run();

    # Finish and close the .f script
    op_quit();
}

# statistics aggregation section
my %FLOWOPVALS;
my @SUMMARYVALS;

sub init_combined_stats
{
    %FLOWOPVALS = ();
    @SUMMARYVALS = (0,0,0,0,0,0);
}

sub add_2combstats
{
    my ($confname) = shift;
    my ($thisclient) = shift;
    my $clstatdir;
    my $flowopmode = 0;
    my $summarymode = 0;

    print "adding in stats for client: $thisclient, configuration: $confname\n";

    $clstatdir = multi_getval("masterpath")."/".$thisclient;

    print "from: ".$clstatdir."/stats.".$confname.".out\n";
    open (CLSTATS, $clstatdir."/stats.".$confname.".out");
    while(<CLSTATS>) {
	my ($line) = $_;
	chomp($line);
	if (($flowopmode == 0) and ($summarymode == 0)) {
	    if ($line =~ /^Flowop totals:/) {
		$flowopmode = 1;
		next;
	    }
	    if ($line =~ /^IO Summary:/) {
		$summarymode = 1;
		next;
	    }
	}
	if ($line eq "") {
	    $flowopmode = 0;
	    $summarymode = 0;
	    next;
	}

	# get the good stuff
	if ($flowopmode == 1) {
	    my @elementlist;
	    my @valuelist;
	    my $flkey;
	    my $vallistref = [];

	    @elementlist = split('	', $line);
	    $flkey = $elementlist[0];
	    @valuelist = @elementlist[1..$#elementlist];

	    if (exists($FLOWOPVALS{$flkey})) {
		my $numvals;

		$vallistref = $FLOWOPVALS{$flkey};
		$numvals = @{$vallistref};
		for (my $idx = 0; $idx < $numvals; $idx++) {
		    $vallistref->[$idx] += $valuelist[$idx];
		}
	    } else {
		# newly found flowop name
		$vallistref = [@valuelist];
		$FLOWOPVALS{$flkey} = $vallistref;
	    }
	    next;
	}

	# get final totals
	if ($summarymode == 1) {
	    my @valuelist;

	    @valuelist = split('	', $line);

	    for (my $idx = 0; $idx <= $#valuelist; $idx++) {
		$SUMMARYVALS[$idx] += $valuelist[$idx];
	    }
	    next;
	}
    }
    close (CLSTATS);
}

sub print_usage
{
    print "Usage:\n\tfilebench -c <stat_dir>\n";
    print "\tfilebench [-b <base_path>] ";
    print "[-D[ ]<variable name>[ |=| = ]<value>]... <profile name>\n";
}

sub dump_combined_stats
{
    my ($confname) = shift;
    my $totvalsref = [];
    my $flkey;
    use FileHandle;

## set up output formating info
format flowoplinefrm =
@<<<<<<<<<<<<<<<<<<< @#######ops/s @###.#mb/s @#####.#ms/op @#######us/op-cpu
$flkey, $totvalsref->[0], $totvalsref->[1], $totvalsref->[2]/$#CLIENTLIST, $totvalsref->[3]/$#CLIENTLIST
.

format summarylinefrm =

IO Summary: @#######ops, @#####.#ops/s, (@####/@#### r/w) @#####.#mb/s, @######us cpu/op, @####.#ms latency
$SUMMARYVALS[0], $SUMMARYVALS[1], $SUMMARYVALS[2], $SUMMARYVALS[3], $SUMMARYVALS[4], $SUMMARYVALS[5], $SUMMARYVALS[6]
.

    open (SUMSTATS, ">$STATSBASE/$confname/stats.$confname.out");
    print "Per-Operation Breakdown:\n";
    print SUMSTATS "Per-Operation Breakdown:\n";

    format_name  STDOUT "flowoplinefrm";
    format_name  SUMSTATS "flowoplinefrm";

    foreach $flkey (keys %FLOWOPVALS) {

	$totvalsref = $FLOWOPVALS{$flkey};

	write STDOUT;
	write SUMSTATS;
    }

    format_name  STDOUT "summarylinefrm";
    format_name  SUMSTATS "summarylinefrm";

    write STDOUT;
    write SUMSTATS;
    close (SUMSTATS);
}

#
# polls the synchronization socket for each client in turn every 5 seconds,
# then sends synch responses once all clients have "checked in". The
# sample number in the received sync requests must match the sequence
# number supplied with the call.
#
sub sync_receive
{
    my $seqnum = shift;
#    my @cl_list;
    my %cl_hash = ();
    %cl_hash = %CLIENTHASH;

    my $count = @CLIENTLIST;
    print "waiting for sync message: $seqnum from $count clients\n";
    while ($count > 0) {
	my $rcv_str = "";

	sleep 5;

	foreach my $client_name (keys %cl_hash)
	{
	    my $clientdata = $CLIENTHASH{$client_name};
	    my $client_hndl = $$clientdata[0];
	    print "recv sync: $client_name undefined handle\n" unless defined($client_hndl);
	    my $client_iaddr = $$clientdata[1];
	    my $sn = $$clientdata[2];
	    my $rtn = 0;

	    do {
		my $tmp_str;
		$rtn = recv($client_hndl, $tmp_str, 80, MSG_DONTWAIT);
		if (defined($rtn)) {
		    $rcv_str = $rcv_str.$tmp_str;
		}   
	    } until (!defined($rtn) || ($rcv_str =~ /$EOL/s ));

	    if (defined($rtn)) {
		my %ophash = ();
		my $ok;

		my @oplist = split /,/,$rcv_str;
		foreach my $opent (@oplist)
		{
		    my ($op, $val) = split /=/,$opent;
		    $ophash{$op} = $val;
		}
		$ok = ($sn == $seqnum);
		$ok &&= defined((my $cmd_val = $ophash{"cmd"}));
		$ok &&= defined((my $samp_val = $ophash{"sample"}));
		if ($ok && ($cmd_val eq "SYNC") && ($samp_val == $seqnum))
		{
		    delete $cl_hash{$client_name};
		    $count--;
		    print "received a sync request from $client_name\n";
		    ${$CLIENTHASH{$client_name}}[2] = ($sn + 1);
		} else {
		    print "received invalid sync request string [".rcv_str."] from client $client_name\n";
		}
	    }
	}
    }
    print "received all sync requests for seq $seqnum, sending responses\n";
    foreach my $client_name (@CLIENTLIST)
    {
	my $clientdata = $CLIENTHASH{$client_name};
	my $client_hndl = $$clientdata[0];
	print "send resp: $client_name undefined handle\n" unless defined($client_hndl);

	send ($client_hndl, "rsp=PASS,sample=$seqnum\n", 0);
    }
}

#
# waits for all known clients to connect, then calls sync_recieve(1) to
# sync_receive(N) to wait for N sync requests for designated sync points
# 1..N.
#
sub sync_server
{
    my $port = shift || 8001;
    my $proto = getprotobyname('tcp');
    my $paddr;
    my $count;

    socket(Server, PF_INET, SOCK_STREAM, $proto)     || die "socket: $!";
    setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
	       pack("l", 1))			     || die "setsockopt: $1";
    bind(Server, sockaddr_in($port, INADDR_ANY))     || die "bind: $1";
    listen(Server, SOMAXCONN)			     || die "listen: $1";

# wait for connection requests from clients
    print "sync: Waiting for ".@CLIENTLIST." Clients\n";
    for ($count = @CLIENTLIST; $count > 0; $count--) {
	$paddr = accept(my $client_hndl, Server);
	die "bad socket address" unless $paddr;

	my ($port, $iaddr) = sockaddr_in($paddr);
	my $cl_name = gethostbyaddr($iaddr, AF_INET);

	if (!exists($CLIENTHASH{$cl_name})) {
	    die "sync from unknown client $cl_name";
	}

	print "received sync connection from client: $cl_name\n";
	${$CLIENTHASH{$cl_name}}[0] = $client_hndl;
	${$CLIENTHASH{$cl_name}}[1] = $iaddr;
    }

# indicate that all clients have checked in
    sync_receive(1);
    if (conf_exists("runtime") == 1) {
	my $runtime =  conf_getval("runtime");
	sleep $runtime;
    }
    sync_receive(2);
}

##############################################################################
## Main program
##############################################################################

## Make sure arguments are okay
$numargs = $#ARGV + 1;

if($numargs < 1) {
    print_usage();
    exit(2);
}

for (my $idx = 0; $idx < $numargs;) {
    my $cur_argv = $ARGV[$idx++];
    my $cmdvar = "";
    my $cmddata = "";

    # See if filebench_compare should be run
    if($cur_argv eq "-c") {
	if($numargs < ($idx + 1)) {
	    print_usage();
	    exit(2);
	}
	# next argument variable is path to results files
	exec("$FILEBENCH/scripts/filebench_compare", $ARGV[$idx++]);
    }

    # Capture specification of an alternate FileBench path
    if($cur_argv eq "-b") {
	if($numargs < ($idx + 1)) {
	    print_usage();
	    exit(2);
	}
	# next argument is the base path name
	$FILEBENCH = $ARGV[$idx++];

    # See if Default variable will be supplied.
    } elsif($cur_argv =~ /^-D(.*)/) {
	my $def_arg = $1;

	# rest of info in separate argvs
	if ($def_arg eq "") {
	    if($numargs < ($idx + 2)) {
		print_usage();
		exit(2);
	    }

	    # get next bit
	    $def_arg = $ARGV[$idx++];
	}

	# with variable name but without "=" or value
	if($def_arg =~ /^([^=]+)$/) {
	    $cmdvar = $1;
	    if($numargs < ($idx + 2)) {
		print_usage();
		exit(2);
	    }

	    # get data from next argument, or next after that if '=' encountered
	    $cmddata = $ARGV[$idx++];
	    if ($cmddata eq "=") {
		if($numargs < ($idx + 2)) {
		    print_usage();
		    exit(2);
		}
		$cmddata = $ARGV[$idx++];
	    } else {
		$cmddata =~ s/^=//;
	    }

	# see if variable name and data supplied
	} elsif($def_arg =~ /^([^=]+)=(.+)/) {
	    $cmdvar = $1;
	    $cmddata = $2;
	    if($numargs < ($idx + 1)) {
		print_usage();
		exit(2);
	    }

	# see if variable name and '=' but no data supplied
	} elsif($def_arg =~ /^([^=]+)=$/) {
	    $cmdvar = $1;
	    if($numargs < ($idx + 2)) {
		print_usage();
		exit(2);
	    }
	    $cmddata = $ARGV[$idx++];
	}

	# push the variable onto the command line hash
	$CMDLINEDATA{$cmdvar} = ();
	push(@{ $CMDLINEDATA{$cmdvar} }, $cmddata);

    # otherwise argument is the profile name
    } else {
	$PROFILENAME = $cur_argv;
    }
}

$PROFILE = $PROFILENAME;
$PROFILE =~ s/.*\/(.+)$/$1/;
parse_profile("$PROFILENAME.prof");

%CONFDATA = ();
%CONFDATA = %DEFDATA;

# get the name of the host this script is running on
my $hostname = `hostname`;
chomp($hostname);

# Check for Multi-Client operation
if ($MULTI_CLIENT == 1) {

    if (multi_exists("targetpath")) {
	$TARGETPATH = multi_getval("targetpath");
    } else {
	print "ERROR: Target pathname required for multi-client operation\n";
	exit(1);
    }

    if (multi_exists("clients")) {
	@CLIENTLIST = split(' ',multi_getval("clients"));
    } else {
	print "ERROR: client list required for multi-client operation\n";
	exit(1);
    }

    if (multi_exists("sharefiles")) {
	$SHAREDFILEALLOCATOR = multi_getval("sharefiles");
    } else {
	$SHAREDFILEALLOCATOR = "";
    }

    $TARGETDIR = $TARGETPATH.conf_getval("dir");

    # Setup the multi client statistics base directory
    $STATSBASE = $TARGETPATH.conf_reqval("stats");

    multi_putval("masterhost", $hostname) unless multi_exists("masterhost");
    multi_putval("masterpath", $STATSBASE) unless multi_exists("masterpath");

    # create a path for filebench.pl to use to access the master directory
    $FB_MASTERPATH = multi_getval("masterpath");

    print "Target PathName = $TARGETPATH, path = ".multi_getval("masterpath")."\n";

} else {
    # Setup the single client statistics base directory
    $STATSBASE = conf_reqval("stats");
}

my $filesystem = conf_reqval("filesystem");
$STATSBASE = $STATSBASE . "/$hostname-$filesystem-$PROFILENAME-";
my $timestamp = strftime "%b_%e_%Y-%Hh_%Mm_%Ss", localtime;
$timestamp =~ s/ //;
$STATSBASE = $STATSBASE . $timestamp;

foreach $config_name (@CONFLIST)
{
    %CONFDATA = ();
    %CONFDATA = %DEFDATA;
    $CONFNAME = $config_name;
    parse_config("$config_name");
    my $function = conf_reqval("function");
    my $statsdir;

    if (-f "$function.func") {
	require "$function.func";
    } else {
	require "$FILEBENCH/config/$function.func";
    }

    # Setup the final statistics directory
    system("mkdir -p $STATSBASE");

    # Leave a log of the run info	
    open (RUNLOG, ">$STATSBASE/thisrun.prof");
    print RUNLOG "# " . conf_reqval("description") . "\n";
    close (RUNLOG);

    system ("cat $PROFILENAME.prof >>".$STATSBASE."/thisrun.prof");

    $statsdir = $STATSBASE . "/" . $config_name;
    system("mkdir -p $statsdir");
    system("chmod a+w $statsdir");

    if ($MULTI_CLIENT == 1) {
	my @pidlist;
	my %multi_confdata;
	my $procpid;
	my $syncclients = "";

	%multi_confdata = %CONFDATA;

	foreach my $thisclient (@CLIENTLIST) {
	    my $tmpdir;
	    my $tmpstatdir;
	    my @clientdata;

	    %CONFDATA = ();
	    %CONFDATA = %multi_confdata;
	    printf "building client: " . $thisclient . "\n";

	    # Setup the statistics directory for each client
	    $tmpstatdir = multi_getval("masterpath")."/".$thisclient;

	    if ($SHAREDFILEALLOCATOR) {
		$tmpdir = $TARGETDIR;
	    } else {
		$tmpdir = $TARGETDIR."/".$thisclient;
	    }

# add info to client hash
	    @clientdata = ();
	    $clientdata[2] = 1;
	    $CLIENTHASH{$thisclient} = \@clientdata;
	    $syncclients = $syncclients." --client ".$thisclient;

	    push(@{ $CONFDATA{"myname"} }, $thisclient);
	    push(@{ $CONFDATA{"statsdir"} }, $tmpstatdir);
	    system("mkdir -p ".$FB_MASTERPATH."/".$thisclient);
	    system("chmod 0777 ".$FB_MASTERPATH."/".$thisclient);

	    # modify dir config variable for multiclient
	    if (conf_exists("dir")) {
		@{$CONFDATA{"dir"}} = ($tmpdir);
	    }
	    build_run();
	}

	# Begin the RUN!!!
	print "Running " . $STATSBASE . "\n";

	#spawn the synchronization server
	print "Starting sync server on host ".$hostname."\n";
	if ($procpid = fork) {
	    push(@pidlist, $procpid);
	} else {
	    sync_server();
	    exit(0);
	}

	sleep(3);

	# remotely execute the run on each client
	foreach $thisclient (@CLIENTLIST) {
	    if($procpid = fork) {
		push(@pidlist, $procpid);
	    } else {
		if ($thisclient eq $hostname) {
		    print "Starting local client: $thisclient\n";
		    system(multi_getval("masterpath")."/".$thisclient."/thisrun.f");
		} else {
		    print "Starting remote client: $thisclient\n";
		    system("ssh ".$thisclient." ".multi_getval("masterpath")."/".$thisclient."/thisrun.f >> ".multi_getval("masterpath")."/".$thisclient."/runs.out");
		}
		exit(0);
	    }
	}

	# wait for all of them to finish
	foreach $procpid (@pidlist) {
	    waitpid($procpid, 0);
	}

	init_combined_stats();

	foreach $thisclient (@CLIENTLIST) {
	    add_2combstats($config_name, $thisclient);
	}

	# dump the combined client stats
	dump_combined_stats($config_name);

    } else {
	push(@{ $CONFDATA{"statsdir"} }, $statsdir);

	build_run();

	# Execute the run
	print "Running " . conf_reqval("statsdir") . "/thisrun.f\n";
	system ($statsdir."/thisrun.f");


    }

}

# The following function is taken from the user's function file
post_run();

print "\n";