Home | History | Annotate | Download | only in fbscript
      1 #!/usr/bin/perl
      2 #
      3 # CDDL HEADER START
      4 #
      5 # The contents of this file are subject to the terms of the
      6 # Common Development and Distribution License (the "License").
      7 # You may not use this file except in compliance with the License.
      8 #
      9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10 # or http://www.opensolaris.org/os/licensing.
     11 # See the License for the specific language governing permissions
     12 # and limitations under the License.
     13 #
     14 # When distributing Covered Code, include this CDDL HEADER in each
     15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16 # If applicable, add the following below this CDDL HEADER, with the
     17 # fields enclosed by brackets "[]" replaced with your own identifying
     18 # information: Portions Copyright [yyyy] [name of copyright owner]
     19 #
     20 # CDDL HEADER END
     21 #
     22 #
     23 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     24 # Use is subject to license terms.
     25 #
     26 
     27 use POSIX;
     28 use Socket;
     29 
     30 my $MULTI_CLIENT = 0;
     31 my $USE_XANADU = 0;
     32 my $TIMEOUT = 60;
     33 my $EOL = "\n";
     34 my $FILEBENCH = "/usr/benchmarks/filebench";
     35 my $PROG = "bin/go_filebench";
     36 my $SHAREDFILEALLOCATOR;
     37 my $TARGETPATH;
     38 my $TARGETDIR;
     39 my $FB_MASTERPATH;
     40 my $STATSBASE;
     41 my $CONFNAME;
     42 my $FSCRIPT;
     43 my $SCRIPT_NO;
     44 my @CLIENTLIST = ();
     45 my %CLIENTHASH = ();
     46 my @CONFLIST;
     47 my %MULTIDATA = ();
     48 my %CMDLINEDATA = ();
     49 my %DEFDATA = ();
     50 my %CONFDATA = ();
     51 my %STATSHASH = ();
     52 my $OPTIONFLAGS = "cleanupstorage dofscheck";
     53 @ext_stats=();
     54 @file_stats=();
     55 @arg_stats=();
     56 @pid_arr=();
     57 
     58 # The following if test caters for running benchpoint from an alternative path
     59 #if (-r $ENV{"FILEBENCH") {
     60 #	$FILEBENCH = $ENV{"FILEBENCH"};
     61 #}
     62 
     63 ##############################################################################
     64 ## Configuration hash data operations
     65 ##############################################################################
     66 
     67 # This sub allows a function program to extract the base directory for filebench
     68 sub get_FILEBENCH {
     69     return ($FILEBENCH);
     70 }
     71 
     72 sub get_STATSBASE {
     73     return ($STATSBASE);
     74 }
     75 
     76 sub get_CONFNAME {
     77     return ($CONFNAME);
     78 }
     79 
     80 sub multi_putval {
     81     my ($key) = shift;
     82     my ($val) = shift;
     83     @{MULTIDATA{$key}} = ();
     84     push(@{ $MULTIDATA{$key} }, $val);
     85 }
     86 
     87 sub multi_getval {
     88     my ($key) = shift;
     89     return ("@{$MULTIDATA{$key}}");
     90 }
     91 
     92 sub multi_exists {
     93     my ($key) = shift;
     94     if (exists($MULTIDATA{$key})) {
     95 	return (1);
     96     }
     97     return (0);
     98 }
     99 
    100 sub conf_getval {
    101     my ($key) = shift;
    102     return ("@{$CONFDATA{$key}}");
    103 }
    104 
    105 sub conf_reqval {
    106     my ($key) = shift;
    107     
    108     if (exists($CONFDATA{$key})) {
    109 	return ("@{$CONFDATA{$key}}");
    110     }
    111     print "ERROR: required key \"$key\" missing from configuration\n";
    112     exit(1);
    113 }
    114 
    115 sub conf_exists {
    116     my ($key) = shift;
    117     if (exists($CONFDATA{$key})) {
    118 	return (1);
    119     }
    120     return (0);
    121 }
    122 
    123 sub conf_hash {
    124     return(%CONFDATA);
    125 }
    126 
    127 ##############################################################################
    128 ## Filebench Operations
    129 ##############################################################################
    130 
    131 sub op_init {
    132 }
    133 
    134 sub op_load {
    135     my ($workload) = shift;
    136     $scriptname = conf_reqval("statsdir") . "/thisrun.f";
    137 
    138     if($workload ne '') {
    139 	print ("Creating Client Script " . $scriptname . "\n");
    140 	open (FSCRIPT, ">$scriptname");
    141 	chmod (0755, $scriptname);
    142 	print FSCRIPT "#!$FILEBENCH/$PROG -f\n\n";
    143 	# Load the df
    144 	print FSCRIPT "load $workload\n";
    145 	# Load the user defined defaults
    146 	op_load_defaults();
    147 
    148 	# enable multiclient, if needed
    149 	if ($MULTI_CLIENT == 1) {
    150 	    print FSCRIPT "enable multi master=".multi_getval("masterhost").", client=".conf_getval("myname")."\n";
    151 	}
    152 
    153 	# Check to see if the path is legal and pointing to the correct FS
    154 	if (conf_exists("dofscheck") == 1) {
    155 	    print FSCRIPT "fscheck path=" . conf_reqval("dir") . " fstype=" . conf_reqval("filesystem") . "\n";
    156 	}
    157 
    158 	# Create the associated files and filesets
    159 	print FSCRIPT "create filesets\n";
    160     }
    161     $SCRIPT_NO = 1;
    162     return(0);
    163 }
    164 
    165 sub op_fsflush {
    166         print FSCRIPT "fsflush fstype=" . conf_reqval("filesystem") . "\n";
    167 }
    168 
    169 sub op_set {
    170     my ($var, $val) = @_;
    171     if($var eq 'debug') {
    172 	    print FSCRIPT "debug $val\n";
    173     } elsif($var ne '') {
    174 	    print FSCRIPT "set \$$var=$val\n";
    175     }
    176     return(0);
    177 }
    178 
    179 sub op_eventrate {
    180     my ($eventrate) = shift;
    181 	if ($eventrate ne '') {
    182 		print FSCRIPT "eventgen rate=$eventrate\n";
    183 		return(0);
    184 	}
    185 }
    186 
    187 sub op_run {
    188     my ($time) = shift;
    189     print FSCRIPT "run $time\n";
    190     return(0);
    191 }
    192 
    193 sub op_sleep {
    194     my ($time) = shift;
    195     print FSCRIPT "sleep $time\n";
    196     return(0);
    197 }
    198 
    199 sub op_msg {
    200     my ($msg) = shift;
    201     print FSCRIPT "echo \"$msg\"\n";
    202     return(0);
    203 }
    204 
    205 sub op_quit {
    206     # Shutdown the appropriate processes
    207     print FSCRIPT "shutdown processes\n";
    208 
    209     # remove filesets, if requested
    210     if (conf_exists("cleanupstorage") == 1) {
    211 	printf FSCRIPT "shutdown filesets\n";
    212     }
    213 
    214     # Quit filebench
    215     print FSCRIPT "quit\n";
    216     close(FSCRIPT);
    217 }
    218 
    219 sub op_statsdir {
    220     print FSCRIPT "stats directory ".conf_reqval("statsdir")."\n";
    221     return(0);
    222 }
    223 
    224 sub op_indiv_vars {
    225     my ($ivar) = shift;
    226     print FSCRIPT "echo \"\$$ivar\"\n";
    227     my ($imatch, $ierr, $ibefore, $iafter) = &expect(FSCRIPT,
    228 						 $TIMEOUT, "filebench>");
    229    
    230     $ibefore =~ /(.*): (.*): (-*\d+)/;
    231     $imatch = $3;
    232     $imatch =~ s/^\s+//;
    233     chomp($imatch);
    234     return($imatch);
    235 }
    236 
    237 sub op_indiv_stats {
    238     my ($var) = shift;
    239     print FSCRIPT "echo \"\${stats.$var}\"\n";
    240     my ($match, $err, $before, $after) = &expect(FSCRIPT,
    241 						 $TIMEOUT, "filebench>");
    242    
    243     $before =~ /(.*): (.*): (-*\d+)/;
    244     $match = $3;
    245     $match =~ s/^\s+//;
    246     chomp($match);
    247     return($match);
    248 }
    249 
    250 sub op_stats {
    251     my ($time) = shift;
    252     my ($warmup) = shift;
    253     my ($statsfile) = shift;
    254     my $mstrstatsdir = $STATSBASE."/".$CONFNAME;
    255 
    256     if ($MULTI_CLIENT == 1) {
    257 	print FSCRIPT "domultisync value=1\n";
    258     }
    259 
    260     # Create the associated processes and start them running
    261     print FSCRIPT "create processes\n";
    262 
    263     if ($warmup ne '') {
    264 	print FSCRIPT "warmup $warmup\n";
    265     }
    266 
    267     if (($time ne '') && ($statsfile ne '')) {
    268 	# Clear the current statistics buffers
    269 	print FSCRIPT "stats clear\n";
    270 
    271 	# Start external statistics collection (if any)
    272 	# Note all statistics arrays MUST be the same length !
    273 	if (@ext_stats != ()) {
    274 	    if (($#ext_stats == $#file_stats) && ($#ext_stats == $#arg_stats)) {
    275 		$script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
    276 		open (RUNSCRIPT, ">$script");
    277 		chmod (0755, $script);
    278 		print FSCRIPT "system \"$script\"\n";
    279 		$SCRIPT_NO++;
    280 		$index=0;
    281 		foreach my $ext (@ext_stats) {
    282 		    print RUNSCRIPT "$FILEBENCH/scripts/collect_$ext $ext $file_stats[$index] ";
    283 		    print RUNSCRIPT  $mstrstatsdir;
    284 		    print RUNSCRIPT " $time $FILEBENCH $arg_stats[$index] &\n";
    285 		    $index++;
    286 		}
    287 	    }
    288 	}
    289 	close(RUNSCRIPT);
    290 
    291 	# Sleep for the run time
    292 	print FSCRIPT "sleep $time\n";
    293 
    294 	# Snap the statistics
    295 	print FSCRIPT "stats snap\n";
    296 
    297 	# Dump the statistics to a raw file - out required due to filename constraint
    298 	if ($MULTI_CLIENT == 1) {
    299 	    print FSCRIPT "domultisync value=2\n";
    300 	    print FSCRIPT "stats multidump \"$statsfile.out\"\n";
    301 	} else {
    302 	    print FSCRIPT "stats dump \"$statsfile.out\"\n";
    303 	}
    304 
    305 	# Statistics reaping occurs here
    306 	if (@ext_stats != ()) {
    307 	    if (($#ext_stats == $#file_stats) && ($#ext_stats == $#arg_stats)) {
    308 		$script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
    309 		open (RUNSCRIPT, ">$script");
    310 		chmod (0755, $script);
    311 		print FSCRIPT "system \"$script\"\n";
    312 		$SCRIPT_NO++;
    313 		foreach my $ext (@ext_stats) {
    314 		    print RUNSCRIPT "$FILEBENCH/scripts/kill_stats $ext &\n";
    315 		}
    316 		close(RUNSCRIPT);
    317 	    }
    318 	}
    319 
    320 	# Dump the statistics to a Xanadu compatible XML file
    321 	if ($USE_XANADU) {
    322 	    op_xmlstats($statsfile);
    323 
    324 	    $script = $mstrstatsdir . "/stats$SCRIPT_NO.pl";
    325 	    open (RUNSCRIPT, ">$script");
    326 	    chmod (0755, $script);
    327 	    print FSCRIPT "system \"$script\"\n";
    328 	    $SCRIPT_NO++;
    329 
    330 	    # The following loop adds the benchpoint run parameters and statistics into the filebench XML file
    331 	    # We capture the meta data from the start of the filebench xml file
    332 	    print RUNSCRIPT "#!/usr/bin/perl\n";
    333 	    print RUNSCRIPT "\$phase=1;\n";
    334 	    print RUNSCRIPT "open(STATSFILE,\"<".$mstrstatsdir."/$statsfile.xml\");\n";
    335 	    print RUNSCRIPT "open(OSTATSFILE,\">".$mstrstatsdir."/$statsfile.new.xml\");\n";
    336 	    print RUNSCRIPT "while (<STATSFILE>) {\n";
    337 	    print RUNSCRIPT "\t\$temp=\$_;\n";
    338 	    print RUNSCRIPT "\tif ((!((/.*meta.*/) || (/.*stat_doc.*/))) && (\$phase == 1)) {\n";
    339 	    print RUNSCRIPT "\t\topen(XMLFILE,\"<".$mstrstatsdir."/$statsfile.config.xml\");\n";
    340 	    print RUNSCRIPT "\t\twhile (<XMLFILE>) {\n";
    341 	    print RUNSCRIPT "\t\t\tprint OSTATSFILE \$_;\n";
    342 	    print RUNSCRIPT "\t\t}\n";
    343 	    print RUNSCRIPT "\t\tclose(XMLFILE);\n";
    344 	    print RUNSCRIPT "\t\t\$phase++;\n";
    345 	    print RUNSCRIPT "\t}\n";
    346 	    print RUNSCRIPT "\tprint OSTATSFILE \$temp;\n";
    347 	    print RUNSCRIPT "}\n";
    348 	    print RUNSCRIPT "close(STATSFILE);\n";
    349 	    print RUNSCRIPT "close(OSTATSFILE);\n";
    350 	    print RUNSCRIPT "unlink(\"".$mstrstatsdir."/$statsfile.xml\");\n";
    351 	    print RUNSCRIPT "unlink(\"".$mstrstatsdir."/$statsfile.config.xml\");\n";
    352 	    print RUNSCRIPT "system(\"mv ".$mstrstatsdir."/$statsfile.new.xml ".$mstrstatsdir."/$statsfile.xml\");\n";
    353 
    354 	    $script = $mstrstatsdir . "/stats$SCRIPT_NO.sh";
    355 	    open (RUNSCRIPT, ">$script");
    356 	    chmod (0755, $script);
    357 	    print FSCRIPT "system \"$script\"\n";
    358 	    $SCRIPT_NO++;
    359 
    360 	    print RUNSCRIPT "mkdir ".$mstrstatsdir."/xml\n";
    361 	    print RUNSCRIPT "mkdir ".$mstrstatsdir."/html\n";
    362 
    363 	    print RUNSCRIPT "mv ".$mstrstatsdir."/$statsfile.xml ".$mstrstatsdir."/xml/$statsfile.xml\n";
    364 
    365 	    # Process XML file using Xanadu 2
    366 	    print RUNSCRIPT "$FILEBENCH/xanadu/scripts/xanadu import ".$mstrstatsdir." ".$mstrstatsdir."/xml ".conf_reqval("function")."-".$mstrstatsdir."\n";
    367 	    print RUNSCRIPT "$FILEBENCH/xanadu/scripts/xanadu export ".$mstrstatsdir."/xml ".$mstrstatsdir."/html\n";
    368 	    close(RUNSCRIPT);
    369 	}
    370     }
    371     return(0);	
    372 }
    373 
    374 sub op_xmlstats {
    375     my ($statsfile) = shift;
    376     my $mstrstatsdir = $STATSBASE."/".$CONFNAME;
    377     if($statsfile ne '') {
    378 	print FSCRIPT "stats xmldump \"$statsfile.xml\"\n";	
    379 
    380 	# The following loop adds the benchpoint run parameters and statistics into a temporary XML file
    381 	open(OSTATSFILE,">".$mstrstatsdir."/$statsfile.config.xml");
    382 	%CONFHASH = conf_hash();
    383 	# There is no test for whether CONFHASH contains no keys 
    384 	# The following two lines is to obtain the stats run directory name for xanadu meta data
    385 	print OSTATSFILE "<meta name=\"RunId\" value=\"".conf_reqval("function")."-".$mstrstatsdir."\"/>\n";
    386 	print OSTATSFILE "<stat_group name=\"Benchpoint Configuration\">\n";
    387 	print OSTATSFILE "<cell_list>\n";
    388 	foreach $k (keys(%CONFHASH)) {
    389 	    print OSTATSFILE "<cell>@{ $CONFHASH{$k} }</cell>\n";
    390 	}
    391 	print OSTATSFILE "</cell_list>\n";
    392 	print OSTATSFILE "<dim_list>\n";
    393 	print OSTATSFILE "<dim>\n";
    394 	print OSTATSFILE "<dimval>Value</dimval>\n";
    395 	print OSTATSFILE "</dim>\n";
    396 	print OSTATSFILE "<dim>\n";
    397 	foreach $k (keys(%CONFHASH)) {
    398 	    print OSTATSFILE "<dimval>$k</dimval>\n";
    399 	}
    400 	print OSTATSFILE "</dim>\n";
    401 	print OSTATSFILE "</dim_list>\n";
    402 	print OSTATSFILE "</stat_group>\n";
    403 	close(OSTATSFILE);
    404 
    405 	return(0);	
    406     }
    407     return(1);	
    408 }
    409 
    410 sub op_command {
    411     my ($command) = shift;
    412 	if($command ne '') {
    413 	    print FSCRIPT "$command\n";	
    414 	}
    415 	return(0);	
    416 }
    417 
    418 sub op_statshash {
    419     op_indiv_stats("iocount");	
    420     $STATSHASH{"iocount"} = op_indiv_stats("iocount");	
    421     $STATSHASH{"iorate"} = op_indiv_stats("iorate");	
    422     $STATSHASH{"ioreadrate"} = op_indiv_stats("ioreadrate");	
    423     $STATSHASH{"iowriterate"} = op_indiv_stats("iowriterate");	
    424     $STATSHASH{"iobandwidth"} = op_indiv_stats("iobandwidth");	
    425     $STATSHASH{"iolatency"} = op_indiv_stats("iolatency");	
    426     $STATSHASH{"iocpu"} = op_indiv_stats("iocpu");	
    427     $STATSHASH{"oheadcpu"} = op_indiv_stats("oheadcpu");	
    428     $STATSHASH{"iowait"} = op_indiv_stats("iowait");	
    429     $STATSHASH{"syscpu"} = op_indiv_stats("syscpu");	
    430     $STATSHASH{"iocpusys"} = op_indiv_stats("iocpusys");	
    431     return(%STATSHASH);
    432 }
    433 
    434 sub op_load_defaults {
    435 # The following code causes an intermittent bug - may be fixed at a later date
    436 # Prevents the capture of filebench default parameters
    437 #    print FSCRIPT "vars\n";
    438 #    my ($match, $err, $before, $after) = &expect(FSCRIPT,
    439 #						 $TIMEOUT, "filebench>");
    440 #    chomp($before);
    441 #    $before =~ /(.*): (.*): (.*)/;
    442 #    $match = $3;
    443 #    my @vars = split(/ /, $match);
    444 #    my $value = "";
    445 #    # Cater for the default filebench commands
    446 #    foreach my $var (@vars) {
    447 #        if (!conf_exists($var)) {
    448 #            $var =~ s/ //g;
    449 #	    if ($var ne '') {
    450 #		$value = op_indiv_vars($var);
    451 #       	        push(@{ $CONFDATA{$var} }, $value);		   
    452 #	    }
    453 #	}
    454 #    }
    455 
    456     # Cater for the user defined defaults
    457     foreach $var (keys(%CONFDATA)) {
    458         if (conf_exists($var)) {
    459             $var =~ s/ //g;
    460             my $val = conf_getval($var);
    461 
    462 	    if (($SHAREDFILEALLOCATOR) and ($var eq "sharedprealloc")) {
    463 		if (conf_reqval("myname") ne $SHAREDFILEALLOCATOR) {
    464 		    $val = "0";
    465 		}
    466 	    }
    467 
    468 	    if ($val ne "") {
    469 		op_set($var, $val);
    470 	    }
    471 	}
    472     }
    473 }
    474 
    475 ##############################################################################
    476 ## Local functions
    477 ##############################################################################
    478 
    479 sub parse_profile {
    480     my ($profile) = shift;
    481     my ($config_section, $default_section, $multi_section);
    482     
    483     open(CFILE, "$profile") or 
    484 	die "ERROR: couldn't open profile $profile";
    485     
    486     while(<CFILE>) {
    487 	my ($line) = $_;
    488 	chomp($line);
    489 	$line =~ s/^\s+//; # Get rid of spaces
    490 	
    491 	if($line =~ /^#/ or $line eq "") {
    492 	} else {
    493 	    if($line =~ /}/) {
    494 		if($multi_section == 1) {
    495 		    $multi_section = 0;
    496 		}
    497 		if($default_section == 1) {
    498 		    $default_section = 0;
    499 		}
    500 		if($config_section == 1) {
    501 		    $config_section = 0;
    502 		}
    503 	    } elsif($multi_section) {
    504 		$line =~ /([^\s]+)\s*=\s*(.+);/;
    505 		my $opt = $1;
    506 		my $val = $2;
    507 		chomp($opt);
    508 		chomp($val);
    509 		my @vals = ();
    510 		# Check to see if this needs to be a list
    511 		if($val =~ /,/) {
    512 		    push(@vals, $+) while $val =~
    513 			m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
    514 		    push(@vals, undef) if substr($val, -1,1) eq ',';
    515 		    @{ $MULTIDATA{$opt} } = @vals;
    516 		} else {
    517 		    @{MULTIDATA{$opt}} = ();
    518 		    push(@{ $MULTIDATA{$opt} }, $val);		   
    519 		}	       
    520 	    } elsif($default_section) {
    521 		if ($line =~ /([^\s]+)\s*=\s*(.+);/) {
    522 		    my $opt = $1;
    523 		    my $val = $2;
    524 		    chomp($opt);
    525 		    chomp($val);
    526 		    my @vals = ();
    527 		    # Check to see if this needs to be a list
    528 		    if(($val =~ /,/) && ($val !~ /"/)) {
    529 			push(@vals, $+) while $val =~
    530 			    m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
    531 			push(@vals, undef) if substr($val, -1,1) eq ',';
    532 			@{ $DEFDATA{$opt} } = @vals;
    533 		    } elsif(exists($CMDLINEDATA{$opt})) {
    534 			@{DEFDATA{$opt}} = ();
    535 			push(@{ $DEFDATA{$opt} }, @{$CMDLINEDATA{$opt}});
    536 		    } else {
    537 			@{DEFDATA{$opt}} = ();
    538 			push(@{ $DEFDATA{$opt} }, $val);		   
    539 		    }
    540 		} else {
    541 		    if ($line =~ /([^;]+);/) {
    542 			my $opt = $1;
    543 			if ($OPTIONFLAGS =~ /$opt/) {
    544 			    @{DEFDATA{$opt}} = ();
    545 			    push(@{ $DEFDATA{$opt} }, "");
    546 			}
    547 		    }
    548 		}
    549 	    } else {
    550 		if($line =~ /^CONFIG /) {
    551                     my $config = $line;
    552 	 	    $config =~ s/CONFIG\s+(.+) {/$1/;
    553 		    push(@CONFLIST, $config);
    554 		    $config_section = 1;
    555 		} elsif($line =~ /MULTICLIENT\s{/) {
    556 		    $multi_section = 1;
    557 		    $MULTI_CLIENT = 1;
    558 		} elsif($line =~ /DEFAULTS\s{/) {
    559 		    $default_section = 1;
    560 		}
    561 	    }
    562 	}
    563     }
    564 }
    565 
    566 
    567 #
    568 # Parse the configuration file
    569 #
    570 sub parse_config {
    571     my ($config) = shift;
    572 
    573     my $config_section = 0;
    574 
    575     print "parsing profile for config: $config\n";
    576     
    577     # Howdy
    578     seek(CFILE, 0, 0);
    579     
    580     while(<CFILE>) {
    581 	# Read in the line and chomp...munch...chomp
    582 	my ($line) = $_;
    583 	chomp($line);
    584 	$line =~ s/^\s+//; # Get rid of spaces
    585 
    586 	# look for our stuff
    587 	if ($line =~ /CONFIG $config /) {
    588 	    $config_section = 1;
    589         }
    590 
    591         if($line =~ /}/) {
    592 	    $config_section = 0;
    593         }
    594 
    595 	# Skip until our config is found
    596 	next if (!$config_section);
    597 
    598 	next if ($line =~ /^#/ or $line eq "");
    599 
    600 	if ($line =~ /([^\s]+)\s*=\s*(.+);/) {
    601 	    my $opt = $1;
    602 	    my $val = $2;
    603 	    chomp($opt);
    604 	    chomp($val);
    605 	    my @vals = ();
    606 	    # Check to see if this needs to be a list
    607 	    if(($val =~ /,/) && ($val !~ /"/)) {
    608 		push(@vals, $+) while $val =~
    609 	            m{"([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx;
    610 		push(@vals, undef) if substr($val, -1,1) eq ',';
    611 		@{ $CONFDATA{$opt} }  = @vals;
    612 	    } else {
    613 		@{CONFDATA{$opt}} = ();
    614 		push(@{ $CONFDATA{$opt} }, $val);
    615 	    }
    616 	} else {
    617 	    $line =~ /($OPTIONFLAGS);/;
    618 	    my $opt = $1;
    619 	    chomp($opt);
    620 	    @{CONFDATA{$opt}} = ();
    621 	    push(@{ $CONFDATA{$opt} }, "");
    622 	}
    623     }
    624     
    625     # Bye, bye
    626     #close(CFILE) or die "ERROR: config file closing difficulties";
    627     return \%confdata;
    628 }
    629 
    630 sub build_run
    631 {
    632     # The following function is taken from the user's function file
    633     pre_run();
    634 
    635     # Set the global statistics directory for this run
    636     op_statsdir();
    637 
    638     # The following function is taken from the user's function file
    639     bm_run();
    640 
    641     # Finish and close the .f script
    642     op_quit();
    643 }
    644 
    645 # statistics aggregation section
    646 my %FLOWOPVALS;
    647 my @SUMMARYVALS;
    648 
    649 sub init_combined_stats
    650 {
    651     %FLOWOPVALS = ();
    652     @SUMMARYVALS = (0,0,0,0,0,0);
    653 }
    654 
    655 sub add_2combstats
    656 {
    657     my ($confname) = shift;
    658     my ($thisclient) = shift;
    659     my $clstatdir;
    660     my $flowopmode = 0;
    661     my $summarymode = 0;
    662 
    663     print "adding in stats for client: $thisclient, configuration: $confname\n";
    664 
    665     $clstatdir = multi_getval("masterpath")."/".$thisclient;
    666 
    667     print "from: ".$clstatdir."/stats.".$confname.".out\n";
    668     open (CLSTATS, $clstatdir."/stats.".$confname.".out");
    669     while(<CLSTATS>) {
    670 	my ($line) = $_;
    671 	chomp($line);
    672 	if (($flowopmode == 0) and ($summarymode == 0)) {
    673 	    if ($line =~ /^Flowop totals:/) {
    674 		$flowopmode = 1;
    675 		next;
    676 	    }
    677 	    if ($line =~ /^IO Summary:/) {
    678 		$summarymode = 1;
    679 		next;
    680 	    }
    681 	}
    682 	if ($line eq "") {
    683 	    $flowopmode = 0;
    684 	    $summarymode = 0;
    685 	    next;
    686 	}
    687 
    688 	# get the good stuff
    689 	if ($flowopmode == 1) {
    690 	    my @elementlist;
    691 	    my @valuelist;
    692 	    my $flkey;
    693 	    my $vallistref = [];
    694 
    695 	    @elementlist = split('	', $line);
    696 	    $flkey = $elementlist[0];
    697 	    @valuelist = @elementlist[1..$#elementlist];
    698 
    699 	    if (exists($FLOWOPVALS{$flkey})) {
    700 		my $numvals;
    701 
    702 		$vallistref = $FLOWOPVALS{$flkey};
    703 		$numvals = @{$vallistref};
    704 		for (my $idx = 0; $idx < $numvals; $idx++) {
    705 		    $vallistref->[$idx] += $valuelist[$idx];
    706 		}
    707 	    } else {
    708 		# newly found flowop name
    709 		$vallistref = [@valuelist];
    710 		$FLOWOPVALS{$flkey} = $vallistref;
    711 	    }
    712 	    next;
    713 	}
    714 
    715 	# get final totals
    716 	if ($summarymode == 1) {
    717 	    my @valuelist;
    718 
    719 	    @valuelist = split('	', $line);
    720 
    721 	    for (my $idx = 0; $idx <= $#valuelist; $idx++) {
    722 		$SUMMARYVALS[$idx] += $valuelist[$idx];
    723 	    }
    724 	    next;
    725 	}
    726     }
    727     close (CLSTATS);
    728 }
    729 
    730 sub print_usage
    731 {
    732     print "Usage:\n\tfilebench -c <stat_dir>\n";
    733     print "\tfilebench [-b <base_path>] ";
    734     print "[-D[ ]<variable name>[ |=| = ]<value>]... <profile name>\n";
    735 }
    736 
    737 sub dump_combined_stats
    738 {
    739     my ($confname) = shift;
    740     my $totvalsref = [];
    741     my $flkey;
    742     use FileHandle;
    743 
    744 ## set up output formating info
    745 format flowoplinefrm =
    746 @<<<<<<<<<<<<<<<<<<< @#######ops/s @###.#mb/s @#####.#ms/op @#######us/op-cpu
    747 $flkey, $totvalsref->[0], $totvalsref->[1], $totvalsref->[2]/$#CLIENTLIST, $totvalsref->[3]/$#CLIENTLIST
    748 .
    749 
    750 format summarylinefrm =
    751 
    752 IO Summary: @#######ops, @#####.#ops/s, (@####/@#### r/w) @#####.#mb/s, @######us cpu/op, @####.#ms latency
    753 $SUMMARYVALS[0], $SUMMARYVALS[1], $SUMMARYVALS[2], $SUMMARYVALS[3], $SUMMARYVALS[4], $SUMMARYVALS[5], $SUMMARYVALS[6]
    754 .
    755 
    756     open (SUMSTATS, ">$STATSBASE/$confname/stats.$confname.out");
    757     print "Per-Operation Breakdown:\n";
    758     print SUMSTATS "Per-Operation Breakdown:\n";
    759 
    760     format_name  STDOUT "flowoplinefrm";
    761     format_name  SUMSTATS "flowoplinefrm";
    762 
    763     foreach $flkey (keys %FLOWOPVALS) {
    764 
    765 	$totvalsref = $FLOWOPVALS{$flkey};
    766 
    767 	write STDOUT;
    768 	write SUMSTATS;
    769     }
    770 
    771     format_name  STDOUT "summarylinefrm";
    772     format_name  SUMSTATS "summarylinefrm";
    773 
    774     write STDOUT;
    775     write SUMSTATS;
    776     close (SUMSTATS);
    777 }
    778 
    779 #
    780 # polls the synchronization socket for each client in turn every 5 seconds,
    781 # then sends synch responses once all clients have "checked in". The
    782 # sample number in the received sync requests must match the sequence
    783 # number supplied with the call.
    784 #
    785 sub sync_receive
    786 {
    787     my $seqnum = shift;
    788 #    my @cl_list;
    789     my %cl_hash = ();
    790     %cl_hash = %CLIENTHASH;
    791 
    792     my $count = @CLIENTLIST;
    793     print "waiting for sync message: $seqnum from $count clients\n";
    794     while ($count > 0) {
    795 	my $rcv_str = "";
    796 
    797 	sleep 5;
    798 
    799 	foreach my $client_name (keys %cl_hash)
    800 	{
    801 	    my $clientdata = $CLIENTHASH{$client_name};
    802 	    my $client_hndl = $$clientdata[0];
    803 	    print "recv sync: $client_name undefined handle\n" unless defined($client_hndl);
    804 	    my $client_iaddr = $$clientdata[1];
    805 	    my $sn = $$clientdata[2];
    806 	    my $rtn = 0;
    807 
    808 	    do {
    809 		my $tmp_str;
    810 		$rtn = recv($client_hndl, $tmp_str, 80, MSG_DONTWAIT);
    811 		if (defined($rtn)) {
    812 		    $rcv_str = $rcv_str.$tmp_str;
    813 		}   
    814 	    } until (!defined($rtn) || ($rcv_str =~ /$EOL/s ));
    815 
    816 	    if (defined($rtn)) {
    817 		my %ophash = ();
    818 		my $ok;
    819 
    820 		my @oplist = split /,/,$rcv_str;
    821 		foreach my $opent (@oplist)
    822 		{
    823 		    my ($op, $val) = split /=/,$opent;
    824 		    $ophash{$op} = $val;
    825 		}
    826 		$ok = ($sn == $seqnum);
    827 		$ok &&= defined((my $cmd_val = $ophash{"cmd"}));
    828 		$ok &&= defined((my $samp_val = $ophash{"sample"}));
    829 		if ($ok && ($cmd_val eq "SYNC") && ($samp_val == $seqnum))
    830 		{
    831 		    delete $cl_hash{$client_name};
    832 		    $count--;
    833 		    print "received a sync request from $client_name\n";
    834 		    ${$CLIENTHASH{$client_name}}[2] = ($sn + 1);
    835 		} else {
    836 		    print "received invalid sync request string [".rcv_str."] from client $client_name\n";
    837 		}
    838 	    }
    839 	}
    840     }
    841     print "received all sync requests for seq $seqnum, sending responses\n";
    842     foreach my $client_name (@CLIENTLIST)
    843     {
    844 	my $clientdata = $CLIENTHASH{$client_name};
    845 	my $client_hndl = $$clientdata[0];
    846 	print "send resp: $client_name undefined handle\n" unless defined($client_hndl);
    847 
    848 	send ($client_hndl, "rsp=PASS,sample=$seqnum\n", 0);
    849     }
    850 }
    851 
    852 #
    853 # waits for all known clients to connect, then calls sync_recieve(1) to
    854 # sync_receive(N) to wait for N sync requests for designated sync points
    855 # 1..N.
    856 #
    857 sub sync_server
    858 {
    859     my $port = shift || 8001;
    860     my $proto = getprotobyname('tcp');
    861     my $paddr;
    862     my $count;
    863 
    864     socket(Server, PF_INET, SOCK_STREAM, $proto)     || die "socket: $!";
    865     setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
    866 	       pack("l", 1))			     || die "setsockopt: $1";
    867     bind(Server, sockaddr_in($port, INADDR_ANY))     || die "bind: $1";
    868     listen(Server, SOMAXCONN)			     || die "listen: $1";
    869 
    870 # wait for connection requests from clients
    871     print "sync: Waiting for ".@CLIENTLIST." Clients\n";
    872     for ($count = @CLIENTLIST; $count > 0; $count--) {
    873 	$paddr = accept(my $client_hndl, Server);
    874 	die "bad socket address" unless $paddr;
    875 
    876 	my ($port, $iaddr) = sockaddr_in($paddr);
    877 	my $cl_name = gethostbyaddr($iaddr, AF_INET);
    878 
    879 	if (!exists($CLIENTHASH{$cl_name})) {
    880 	    die "sync from unknown client $cl_name";
    881 	}
    882 
    883 	print "received sync connection from client: $cl_name\n";
    884 	${$CLIENTHASH{$cl_name}}[0] = $client_hndl;
    885 	${$CLIENTHASH{$cl_name}}[1] = $iaddr;
    886     }
    887 
    888 # indicate that all clients have checked in
    889     sync_receive(1);
    890     if (conf_exists("runtime") == 1) {
    891 	my $runtime =  conf_getval("runtime");
    892 	sleep $runtime;
    893     }
    894     sync_receive(2);
    895 }
    896 
    897 ##############################################################################
    898 ## Main program
    899 ##############################################################################
    900 
    901 ## Make sure arguments are okay
    902 $numargs = $#ARGV + 1;
    903 
    904 if($numargs < 1) {
    905     print_usage();
    906     exit(2);
    907 }
    908 
    909 for (my $idx = 0; $idx < $numargs;) {
    910     my $cur_argv = $ARGV[$idx++];
    911     my $cmdvar = "";
    912     my $cmddata = "";
    913 
    914     # See if filebench_compare should be run
    915     if($cur_argv eq "-c") {
    916 	if($numargs < ($idx + 1)) {
    917 	    print_usage();
    918 	    exit(2);
    919 	}
    920 	# next argument variable is path to results files
    921 	exec("$FILEBENCH/scripts/filebench_compare", $ARGV[$idx++]);
    922     }
    923 
    924     # Capture specification of an alternate FileBench path
    925     if($cur_argv eq "-b") {
    926 	if($numargs < ($idx + 1)) {
    927 	    print_usage();
    928 	    exit(2);
    929 	}
    930 	# next argument is the base path name
    931 	$FILEBENCH = $ARGV[$idx++];
    932 
    933     # See if Default variable will be supplied.
    934     } elsif($cur_argv =~ /^-D(.*)/) {
    935 	my $def_arg = $1;
    936 
    937 	# rest of info in separate argvs
    938 	if ($def_arg eq "") {
    939 	    if($numargs < ($idx + 2)) {
    940 		print_usage();
    941 		exit(2);
    942 	    }
    943 
    944 	    # get next bit
    945 	    $def_arg = $ARGV[$idx++];
    946 	}
    947 
    948 	# with variable name but without "=" or value
    949 	if($def_arg =~ /^([^=]+)$/) {
    950 	    $cmdvar = $1;
    951 	    if($numargs < ($idx + 2)) {
    952 		print_usage();
    953 		exit(2);
    954 	    }
    955 
    956 	    # get data from next argument, or next after that if '=' encountered
    957 	    $cmddata = $ARGV[$idx++];
    958 	    if ($cmddata eq "=") {
    959 		if($numargs < ($idx + 2)) {
    960 		    print_usage();
    961 		    exit(2);
    962 		}
    963 		$cmddata = $ARGV[$idx++];
    964 	    } else {
    965 		$cmddata =~ s/^=//;
    966 	    }
    967 
    968 	# see if variable name and data supplied
    969 	} elsif($def_arg =~ /^([^=]+)=(.+)/) {
    970 	    $cmdvar = $1;
    971 	    $cmddata = $2;
    972 	    if($numargs < ($idx + 1)) {
    973 		print_usage();
    974 		exit(2);
    975 	    }
    976 
    977 	# see if variable name and '=' but no data supplied
    978 	} elsif($def_arg =~ /^([^=]+)=$/) {
    979 	    $cmdvar = $1;
    980 	    if($numargs < ($idx + 2)) {
    981 		print_usage();
    982 		exit(2);
    983 	    }
    984 	    $cmddata = $ARGV[$idx++];
    985 	}
    986 
    987 	# push the variable onto the command line hash
    988 	$CMDLINEDATA{$cmdvar} = ();
    989 	push(@{ $CMDLINEDATA{$cmdvar} }, $cmddata);
    990 
    991     # otherwise argument is the profile name
    992     } else {
    993 	$PROFILENAME = $cur_argv;
    994     }
    995 }
    996 
    997 $PROFILE = $PROFILENAME;
    998 $PROFILE =~ s/.*\/(.+)$/$1/;
    999 parse_profile("$PROFILENAME.prof");
   1000 
   1001 %CONFDATA = ();
   1002 %CONFDATA = %DEFDATA;
   1003 
   1004 # get the name of the host this script is running on
   1005 my $hostname = `hostname`;
   1006 chomp($hostname);
   1007 
   1008 # Check for Multi-Client operation
   1009 if ($MULTI_CLIENT == 1) {
   1010 
   1011     if (multi_exists("targetpath")) {
   1012 	$TARGETPATH = multi_getval("targetpath");
   1013     } else {
   1014 	print "ERROR: Target pathname required for multi-client operation\n";
   1015 	exit(1);
   1016     }
   1017 
   1018     if (multi_exists("clients")) {
   1019 	@CLIENTLIST = split(' ',multi_getval("clients"));
   1020     } else {
   1021 	print "ERROR: client list required for multi-client operation\n";
   1022 	exit(1);
   1023     }
   1024 
   1025     if (multi_exists("sharefiles")) {
   1026 	$SHAREDFILEALLOCATOR = multi_getval("sharefiles");
   1027     } else {
   1028 	$SHAREDFILEALLOCATOR = "";
   1029     }
   1030 
   1031     $TARGETDIR = $TARGETPATH.conf_getval("dir");
   1032 
   1033     # Setup the multi client statistics base directory
   1034     $STATSBASE = $TARGETPATH.conf_reqval("stats");
   1035 
   1036     multi_putval("masterhost", $hostname) unless multi_exists("masterhost");
   1037     multi_putval("masterpath", $STATSBASE) unless multi_exists("masterpath");
   1038 
   1039     # create a path for filebench.pl to use to access the master directory
   1040     $FB_MASTERPATH = multi_getval("masterpath");
   1041 
   1042     print "Target PathName = $TARGETPATH, path = ".multi_getval("masterpath")."\n";
   1043 
   1044 } else {
   1045     # Setup the single client statistics base directory
   1046     $STATSBASE = conf_reqval("stats");
   1047 }
   1048 
   1049 my $filesystem = conf_reqval("filesystem");
   1050 $STATSBASE = $STATSBASE . "/$hostname-$filesystem-$PROFILENAME-";
   1051 my $timestamp = strftime "%b_%e_%Y-%Hh_%Mm_%Ss", localtime;
   1052 $timestamp =~ s/ //;
   1053 $STATSBASE = $STATSBASE . $timestamp;
   1054 
   1055 foreach $config_name (@CONFLIST)
   1056 {
   1057     %CONFDATA = ();
   1058     %CONFDATA = %DEFDATA;
   1059     $CONFNAME = $config_name;
   1060     parse_config("$config_name");
   1061     my $function = conf_reqval("function");
   1062     my $statsdir;
   1063 
   1064     if (-f "$function.func") {
   1065 	require "$function.func";
   1066     } else {
   1067 	require "$FILEBENCH/config/$function.func";
   1068     }
   1069 
   1070     # Setup the final statistics directory
   1071     system("mkdir -p $STATSBASE");
   1072 
   1073     # Leave a log of the run info	
   1074     open (RUNLOG, ">$STATSBASE/thisrun.prof");
   1075     print RUNLOG "# " . conf_reqval("description") . "\n";
   1076     close (RUNLOG);
   1077 
   1078     system ("cat $PROFILENAME.prof >>".$STATSBASE."/thisrun.prof");
   1079 
   1080     $statsdir = $STATSBASE . "/" . $config_name;
   1081     system("mkdir -p $statsdir");
   1082     system("chmod a+w $statsdir");
   1083 
   1084     if ($MULTI_CLIENT == 1) {
   1085 	my @pidlist;
   1086 	my %multi_confdata;
   1087 	my $procpid;
   1088 	my $syncclients = "";
   1089 
   1090 	%multi_confdata = %CONFDATA;
   1091 
   1092 	foreach my $thisclient (@CLIENTLIST) {
   1093 	    my $tmpdir;
   1094 	    my $tmpstatdir;
   1095 	    my @clientdata;
   1096 
   1097 	    %CONFDATA = ();
   1098 	    %CONFDATA = %multi_confdata;
   1099 	    printf "building client: " . $thisclient . "\n";
   1100 
   1101 	    # Setup the statistics directory for each client
   1102 	    $tmpstatdir = multi_getval("masterpath")."/".$thisclient;
   1103 
   1104 	    if ($SHAREDFILEALLOCATOR) {
   1105 		$tmpdir = $TARGETDIR;
   1106 	    } else {
   1107 		$tmpdir = $TARGETDIR."/".$thisclient;
   1108 	    }
   1109 
   1110 # add info to client hash
   1111 	    @clientdata = ();
   1112 	    $clientdata[2] = 1;
   1113 	    $CLIENTHASH{$thisclient} = \@clientdata;
   1114 	    $syncclients = $syncclients." --client ".$thisclient;
   1115 
   1116 	    push(@{ $CONFDATA{"myname"} }, $thisclient);
   1117 	    push(@{ $CONFDATA{"statsdir"} }, $tmpstatdir);
   1118 	    system("mkdir -p ".$FB_MASTERPATH."/".$thisclient);
   1119 	    system("chmod 0777 ".$FB_MASTERPATH."/".$thisclient);
   1120 
   1121 	    # modify dir config variable for multiclient
   1122 	    if (conf_exists("dir")) {
   1123 		@{$CONFDATA{"dir"}} = ($tmpdir);
   1124 	    }
   1125 	    build_run();
   1126 	}
   1127 
   1128 	# Begin the RUN!!!
   1129 	print "Running " . $STATSBASE . "\n";
   1130 
   1131 	#spawn the synchronization server
   1132 	print "Starting sync server on host ".$hostname."\n";
   1133 	if ($procpid = fork) {
   1134 	    push(@pidlist, $procpid);
   1135 	} else {
   1136 	    sync_server();
   1137 	    exit(0);
   1138 	}
   1139 
   1140 	sleep(3);
   1141 
   1142 	# remotely execute the run on each client
   1143 	foreach $thisclient (@CLIENTLIST) {
   1144 	    if($procpid = fork) {
   1145 		push(@pidlist, $procpid);
   1146 	    } else {
   1147 		if ($thisclient eq $hostname) {
   1148 		    print "Starting local client: $thisclient\n";
   1149 		    system(multi_getval("masterpath")."/".$thisclient."/thisrun.f");
   1150 		} else {
   1151 		    print "Starting remote client: $thisclient\n";
   1152 		    system("ssh ".$thisclient." ".multi_getval("masterpath")."/".$thisclient."/thisrun.f >> ".multi_getval("masterpath")."/".$thisclient."/runs.out");
   1153 		}
   1154 		exit(0);
   1155 	    }
   1156 	}
   1157 
   1158 	# wait for all of them to finish
   1159 	foreach $procpid (@pidlist) {
   1160 	    waitpid($procpid, 0);
   1161 	}
   1162 
   1163 	init_combined_stats();
   1164 
   1165 	foreach $thisclient (@CLIENTLIST) {
   1166 	    add_2combstats($config_name, $thisclient);
   1167 	}
   1168 
   1169 	# dump the combined client stats
   1170 	dump_combined_stats($config_name);
   1171 
   1172     } else {
   1173 	push(@{ $CONFDATA{"statsdir"} }, $statsdir);
   1174 
   1175 	build_run();
   1176 
   1177 	# Execute the run
   1178 	print "Running " . conf_reqval("statsdir") . "/thisrun.f\n";
   1179 	system ($statsdir."/thisrun.f");
   1180 
   1181 
   1182     }
   1183 
   1184 }
   1185 
   1186 # The following function is taken from the user's function file
   1187 post_run();
   1188 
   1189 print "\n";
   1190