Commit 42629331 authored by Robert Ricci's avatar Robert Ricci

Initial checkin of a simple test harness for running assign and

gathering statistics from its output. Code is well commented, but I
still need to write some overall documentation.
parent 79a26bc2
This directory contains a number of scripts that can be useful in
running assign by itself, to test scaling, new features, etc.
#!/usr/bin/perl
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Common functions used by the scripts in the assign test harness
#
#
# List all files in the ptop/ directory that match a certain pattern (passed
# to a shell). Return a list.
#
sub lsptop($) {
my ($pattern) = @_;
my @filenames = `ls -1 ptop/$pattern`;
@filenames = map { `basename $_` } @filenames;
chomp @filenames;
return @filenames;
}
#
# Parse a file describing some tests to run - really, it's just some perl code
# to execute that's expected to set certain variables.
#
sub readtestfile($) {
my ($testfile) = @_;
# Put variable set in this file in a different package (package declaration
# ends when the function goes out of scope)
package CFG;
# This is sort of like 'source'
do($testfile);
die "Error parsing $testfile: $@\n" if $@;
}
#
# Set some standard global variables
#
sub setglobals() {
$::testdir = "tests/$CFG::name";
$::outdir = "$testdir/out";
$::statdir = "$testdir/stat";
}
#
# Extract some info (node counts, link couts, etc.) about each top and ptop
# file used by the currently loaded test. Expects to find this data in some
# auxiliary files, which it generates if they don't already exist. Returns two
# hashes, one for top files, one for ptop files, indexed by filename
#
sub readfileinfo() {
my (%topinfo, %ptopinfo);
foreach my $typeinfo ((['top',\@CFG::tops, \%topinfo],
['ptop',\@CFG::ptops, \%ptopinfo])) {
my ($type, $list, $hashref) = @$typeinfo;
foreach my $file (@$list) {
$$hashref{$file} = {};
my $nodecountfile = "$type/counts/$file.nodes";
if (!-e $nodecountfile) {
print "(re)Generating $nodecountfile for $type/$file\n";
system "egrep -c '^node \\w+ pc' $type/$file > $nodecountfile";
}
chomp(my $nodecount = `cat $nodecountfile`);
$$hashref{$file}{'nodes'} = $nodecount;
my $linkcountfile = "$type/counts/$file.links";
if (!-e $linkcountfile) {
print "(re)Generating $linkcountfile for $type/$file\n";
system "egrep -c '^link' $type/$file > $linkcountfile";
}
chomp(my $linkcount = `cat $linkcountfile`);
$$hashref{$file}{'links'} = $linkcount;
}
}
return (\%topinfo, \%ptopinfo);
}
#
# Parse a few critical details out of an assing log file. Assumes that we
# already know that the run succeeded.
#
sub parseassignlog($) {
my ($logfile) = @_;
my %assigninfo;
open LFH, "<$logfile" or die "Unable to open $logfile: $!\n";
while (my $line = <LFH>) {
chomp $line;
if ($line =~ /BEST SCORE:\s+(\d*\.*\d+) in (\d+) iters and (\d*\.*\d+) seconds/) {
($assigninfo{'bestscore'},$assigninfo{'iters'},
$assigninfo{'runtime'} ) = ($1,$2,$3);
}
}
if (!exists($assigninfo{'runtime'})) {
warn "Unable to find runtime in $logfile";
}
return \%assigninfo;
}
#
# Given a top and a ptop file (name), return the name of the specific test
#
sub testname($$) {
my ($top,$ptop) = @_;
return "$top+$ptop";
}
#
# List all tests for the currently loaded config. We sort them using an
# alphanumeric sort so taht they will typically range from easiest to
# hardest
#
sub enumeratetests($) {
my @tests;
foreach my $top (@CFG::tops) {
foreach my $ptop (@CFG::ptops) {
push @tests, testname($top,$ptop);
}
}
return (sort { alphanum($a,$b) } @tests);
}
#
# Returns true if the given test (already run) passed
#
sub passed($) {
my ($test) = @_;
if (-e passedfile($test)) {
return 1;
} else {
return 0;
}
}
#
# Extract the top or ptop name from a test name
#
sub top($) {
my ($test) = @_;
my ($top,$ptop) = split /\+/, $test;
return $top;
}
sub ptop($) {
my ($test) = @_;
my ($top,$ptop) = split /\+/, $test;
return $ptop;
}
#
# Conveneince functions to return names of files for various tests
#
sub statfile($) {
my ($statname) = @_;
return "$::statdir/$statname";
}
sub logfile($) {
my ($testname) = @_;
return "$::outdir/$testname.log";
}
sub passedfile($) {
my ($testname) = @_;
return "$::outdir/$testname.passed";
}
sub failedfile($) {
my ($testname) = @_;
return "$::outdir/$testname.failed";
}
sub topfile($) { return "top/" . top($_[0]); }
sub ptopfile($) { return "ptop/" . ptop($_[0]); }
#
# Takes a filename and a reference to a list (which it assumes is a 2-d array),
# sorts the list by the first column, and writes to the file. Perfect for
# producing output for gnuplot
#
sub write_sortedfile($$) {
my ($fname, $arrayref) = @_;
my @sorted = sort { $a[0] <=> $b[0] } @$arrayref;
open FH, ">$fname" or die "Unable to open $fname: $!\n";
print FH join("\n",(map { join "\t", @$_ } @sorted));
print FH "\n";
close FH;
}
#
# Stuff for alphanumeric sorting - from DaveKoelle.com
#
sub alphanum {
# split strings into chunks
my @a = chunkify($_[0]);
my @b = chunkify($_[1]);
# while we have chunks to compare.
while (@a && @b) {
my $a_chunk = shift @a;
my $b_chunk = shift @b;
my $test =
(($a_chunk =~ /\d/) && ($b_chunk =~ /\d/)) ? # if both are numeric
$a_chunk <=> $b_chunk : # compare as numbers
$a_chunk cmp $b_chunk ; # else compare as strings
# return comparison if not equal.
return $test if $test != 0;
}
# return longer string.
return @a <=> @b;
}
# split on numeric/non-numeric transitions
sub chunkify {
my @chunks = split m{ # split on
(?= # zero width
(?<=\D)\d | # digit preceded by a non-digit OR
(?<=\d)\D # non-digit preceded by a digit
)
}x, $_[0];
return @chunks;
}
1;
#!/usr/bin/perl
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Gather statictics, in the form of files that can be passed as datafiles to
# gnuplot, for a set of tests that have already been run
#
require common;
use strict;
#
# Read config from file passed on command line
#
die "Usage: $0 <testfile>" unless @ARGV==1;
my ($testfile) = @ARGV;
readtestfile($testfile);
setglobals();
#
# Read stats on the top and ptop files for this test
#
my ($topinfo,$ptopinfo) = readfileinfo();
my (@pnode_time, @vnode_time, @plink_time, @vlink_time);
#
# Loop through every test, gathering stats
#
foreach my $test (enumeratetests()) {
if (passed($test)) {
my $assigninfo = parseassignlog(logfile($test));
my $top = top($test);
my $ptop = ptop($test);
my $time = $$assigninfo{'runtime'};
my $topnodes = $$topinfo{$top}{'nodes'};
my $toplinks = $$topinfo{$top}{'links'};
my $ptopnodes = $$ptopinfo{$ptop}{'nodes'};
my $ptoplinks = $$ptopinfo{$ptop}{'links'};
push @pnode_time, [$ptopnodes, $time];
push @vnode_time, [$topnodes, $time];
push @plink_time, [$ptoplinks, $time];
push @vlink_time, [$toplinks, $time];
} else {
print "NOTICE: Test $test failed, skipping\n";
}
}
print "Writing statistics files\n";
write_sortedfile(statfile("pnode_time"), \@pnode_time);
write_sortedfile(statfile("vnode_time"), \@vnode_time);
write_sortedfile(statfile("plink_time"), \@plink_time);
write_sortedfile(statfile("vlink_time"), \@vlink_time);
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Make a simple top file - a bunch of PCs all in a big gigabit LAN
#
use strict;
die "Usage: $0 <nodes>\n" unless @ARGV == 1;
my ($nodes) = @ARGV;
print "node lannode lan\n";
for (my $i = 0; $i < $nodes; $i++) {
print "node node$i pc\n";
print "link vlink$i node$i lannode 1000 0 0 ethernet\n";
}
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Makes a simple physical topology, based on a number of parameters (which
# are mostly hardcoded for now.) The topology simply fills up switches
# according to some simple rules about the number of interfaces per node and
# the number of ports per switch. Switches are connected in a "star" with
# a "hub" switch in middle to which all node switches have trunks
#
$| = 1;
use strict;
use POSIX qw(ceil floor);
my $nodes;
#
# Parameters
#
# Interfaces per node
my $IFACES = 4;
# Ports per switch (1000 looks like a
my $PPS = 1000;
# Types that are assigned to every switch
my @SWITCHTYPES = ("switch:*","*lan:*");
# Features to be assigned to every switch
my @SWITCHFEATURES = ();
# Types that are assigned to every node
my @NODETYPES = ("pc:1", "pcvm:10");
# Features that are assigned to every node
my @NODEFEATURES = ();
# Type of links in use
my @LINKTYPES = ("ethernet");
# Speed of node interfaces (gigabit)
my $NODESPEED = "1000";
# Speed of trunk links - relative to speed of node interfaces (here, a single
# 100 gbit interface or 10 trunked 10gbit interfaces)
my $TRUNKSPEED = $NODESPEED * 100;
if (@ARGV == 1) {
($nodes) = @ARGV;
} elsif (@ARGV == 3) {
my $trunkmult;
($nodes, $PPS, $trunkmult) = @ARGV;
$TRUNKSPEED = $NODESPEED * $trunkmult;
} else {
die "Usage: $0 <nodes> [pps trunkmult]\n";
}
#
# How many nodes will fit on each swich
#
my $nodesperswitch = ($PPS*1.0/$IFACES);
#
# How many 'edge' switches are needed
#
my $switchcount = ceil($nodes*1.0 / $nodesperswitch);
#
# Print out a node with the given types and features
#
sub node($$$) {
my ($name,$typeref,$fref) = @_;
my @types = @$typeref;
my @features = @$fref;
print "node $name ". join(" ",@types) . " - " . join(" ",@features) . "\n";
}
#
# Print out n links with the given source, destination, and speed
#
sub links($$$$) {
my ($count, $src, $dst, $speed) = @_;
for (my $i = 0; $i < $count; $i++) {
print "link $src-$dst-$i $src:(null)/$i $dst:(null)/$i $speed 0 0 " .
join(" ",@LINKTYPES) . "\n";
}
}
if ($switchcount > 1) {
# Print out node line for the interconnect switch
node("hub",\@SWITCHTYPES,\@SWITCHFEATURES);
}
for (my $i = 0; $i < $nodes; $i++) {
my $switchname = "switch" . floor(($i / $nodesperswitch));
#
# Start a new switch
#
if ($i % $nodesperswitch == 0) {
# Print out the switch for this set of nodes
node($switchname, \@SWITCHTYPES, \@SWITCHFEATURES);
# Link to the hub switch
if ($switchcount > 1) {
links(1,$switchname,"hub",$TRUNKSPEED);
}
}
my $pcname = "pc" . $i;
node($pcname,\@NODETYPES,\@NODEFEATURES);
links($IFACES,$pcname,$switchname,$NODESPEED);
}
#!/usr/bin/perl
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009 University of Utah and the Flux Group.
# All rights reserved.
#
require common;
use strict;
#
# Run all tests specified in a testfile
#
#
# Read config from file passed on command line
#
die "Usage: $0 <testfile>" unless @ARGV==1;
my ($testfile) = @ARGV;
readtestfile($testfile);
#
# Set up global vars that store directory locations, etc.
#
setglobals();
#
# Make intermediate directories
#
if (-d $::testdir) {
print "Clearing out old testdir\n";
system "rm -rf $::testdir";
}
foreach my $dir ($::testdir, $::outdir, $::statdir) {
mkdir $dir or die "Unable to make $dir: $!";
}
#
# Actually run assign
#
foreach my $test (enumeratetests()) {
my $cmdline = "bin/assign $CFG::assignopts " . ptopfile($test) .
" " . topfile($test);
my $logfile = logfile($test);
print "Running $test ... ";
my $oldtime = time();
my $rv = system "$cmdline > $logfile 2>&1";
my $newtime = time();
my $delta = $newtime - $oldtime;
print "($delta seconds)\n";
if ($rv) {
print "Failed!\n";
system "touch " . failedfile($test);
} else {
system "touch " . passedfile($test);
}
}
@tops = ( "plan-20.top" );
@ptops = &::lsptop("big-*.ptop");
$name = "plan20-scale";
$assignopts = "-P";
@tops = ( "plan-20.top" );
@ptops = &::lsptop("small-*.ptop");
$name = "plan20-small-scale";
$assignopts = "-P";
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment