From 23b0cbf1b886a248c99d6bdfeb709ea4a070be3b Mon Sep 17 00:00:00 2001 From: Timothy Stack <stack@flux.utah.edu> Date: Mon, 12 Jul 2004 14:26:47 +0000 Subject: [PATCH] Merge feedback stuff from the virt tree. --- sensors/slothd/GNUmakefile.in | 4 +- sensors/slothd/digest-slothd | 350 ++++++++++++++++++++++++++++++ sensors/slothd/webfeedback.in | 328 ++++++++++++++++++++++++++++ tbsetup/assign_prepass.in | 26 ++- tbsetup/ns2ir/GNUmakefile.in | 2 +- tbsetup/ns2ir/sim.tcl.in | 20 ++ tbsetup/ns2ir/tb_compat.tcl.in | 375 ++++++++++++++++++++++++++++++++- tbsetup/ptopgen.in | 2 + www/feedback.php3 | 220 +++++++++++++++++++ www/remapexp.php3 | 181 ++++++++++++++++ www/showexp.php3 | 13 ++ 11 files changed, 1515 insertions(+), 6 deletions(-) create mode 100644 sensors/slothd/digest-slothd create mode 100644 sensors/slothd/webfeedback.in create mode 100644 www/feedback.php3 create mode 100644 www/remapexp.php3 diff --git a/sensors/slothd/GNUmakefile.in b/sensors/slothd/GNUmakefile.in index d0c203bb35..f24a8a0f06 100644 --- a/sensors/slothd/GNUmakefile.in +++ b/sensors/slothd/GNUmakefile.in @@ -43,7 +43,9 @@ version.c: slothd.c slothd.h sdcollectd.c sdcollectd.h client: slothd -install: $(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS) sdcollectd) +install: $(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS) sdcollectd) webfeedback + $(INSTALL_PROGRAM) $(SRCDIR)/digest-slothd $(INSTALL_LIBEXECDIR) + $(INSTALL_PROGRAM) webfeedback $(INSTALL_LIBEXECDIR) client-install: client $(INSTALL_PROGRAM) -s slothd $(DESTDIR)$(CLIENT_BINDIR) diff --git a/sensors/slothd/digest-slothd b/sensors/slothd/digest-slothd new file mode 100644 index 0000000000..50bacadc26 --- /dev/null +++ b/sensors/slothd/digest-slothd @@ -0,0 +1,350 @@ +#! /usr/bin/awk -f + +# +# Digest slothd logs and generate a TCL file that contains resource usage data +# that can be fed back into the mapper. +# +# Usage: digest-slothd <slothd-log1> [<slothd-log2> ...] <mapping> +# +# <slothd-logN> - The output of slothd when it is run in high resolution +# tracing mode. +# <mapping> - A mapping of MAC addresses to node members and LAN/link names. +# XXX disgusting. +# + +BEGIN { + OVERLOAD_MAX = 3; # XXX Make this a command line argument. + error = 0; + if( ARGC < 2 ) + { + printf("Convert slothd output to a TCL feedback file.\n"); + printf("Usage: digest-slothd <file1> [<file2> ...] <mapping>\n"); + printf("\n"); + printf("Required arguments:\n"); + printf(" file1 - A file containing the output of slothd.\n"); + printf(" mapping - A mapping of MAC addresses to node members\n"); + printf(" and LAN/link names."); + printf("\n"); + printf("Example:\n"); + printf(" $ digest-slothd vhost-0.slothd - < mapping\n"); + printf("\n"); + printf("Example mapping:\n"); + printf(" 00:00:0a:01:01:02 node0 link"); + printf("\n"); + error = -1; + exit error; + } + + printf("# -*- TCL -*-\n"); + printf("# Automatically generated feedback file.\n"); + printf("#\n"); + for( lpc = 1; lpc < ARGC; lpc++ ) + { + printf("# ARGV[%d]: %s\n", lpc, ARGV[lpc]); + } + printf("#\n"); + printf("# Generated at: %s\n", strftime()); + printf("#\n\n"); + printf("# BEGIN Node/LAN\n"); +} + +# +# Process slothd output. This will digest the "vnode" and "iface" attributes +# and store the values in the following variables: +# +# vnodes_cpu[<vnode_name>,<instance>] - The vnode percent of CPU used per +# second. +# vnodes_ram[<vnode_name>,<instance>] - The vnode percent of RAM used per +# second. +# links_mac_bw[<MAC addr>] - The peak kilobits per second used on the NIC. +# links_mac_pkts[<MAC addr>] - The peak number of packets per second used on +# the NIC. +# +# Example input: +# +# vers=3 mis=1084907941 lave=0.0000000000,0.0000000000,0.0000000000 abits=0x5 page=0,0 ints=1134,693,82 cpu=0,1,98 iface=00:02:b3:3f:7a:20,18804,3352,14047137,457522 iface=00:03:47:73:a2:42,9079,125118,3428409,60045802 iface=00:00:00:00:00:00,0,0,0,0 iface=00:00:0a:01:08:03,2846,125780,385828,58014417 iface=00:00:0a:01:12:03,125782,2843,58015462,385822 iface=00:00:0a:01:0d:02,6285,152,2896485,19985 iface=00:00:0a:01:12:02,2845,125781,385780,58015462 vnode=dslclient-11.testdssvm.tbres.emulab.net,0.0,1.8 vnode=server.testdssvm.tbres.emulab.net,2.1,2.5 vnode=corerouter.testdssvm.tbres.emulab.net,0.0,1.2 +# +/vers=3/ { + line_count[ARGIND] += 1; + if( (vnode_count > 1) && + (line_count[ARGIND - 1] > 0) && + (line_count[ARGIND - 1] < 3) ) + { +# There are not enough lines to deduce anything other than overload. + for( vnode_name in line_vnode_names ) + { + alerts[vnode_name] = 1; + } + for( mac in line_macs ) + { + alerts[mac] = 1; + } + } + vnode_count = 0; + total_vnode_cpu = 0; + time_diff_s = 0; + ovld = 0; + delete line_vnode_names; + delete line_macs; + for( lpc = 2; lpc <= NF; lpc++ ) + { +# Determine the field type and + split($lpc, field, /=/); +# ... handle it. + if( field[1] == "stamp" ) + { + split(field[2], data, /,/); + if( last_stamp_s[ARGIND] ) + { + time_diff_us = (data[1] - last_stamp_s[ARGIND]) * 1000000; + time_diff_us += (data[2] - last_stamp_us[ARGIND]); + time_diff_s = time_diff_us / 1000000.0 + } + else + { + time_diff_s = 1.0; + } + last_stamp_s[ARGIND] = data[1]; + last_stamp_us[ARGIND] = data[2]; + +# Assume overload if the time diff is relatively large. + if( time_diff_s > 1.25 ) + { + ovld = 1; +# Signal multiple overload events for each missed time interval. + overload[ARGIND] += (time_diff_s - 1.0); + } + } + else if( field[1] == "ovld" ) + { + if( !ovld ) + ovld = field[2]; + } + else if( field[1] == "cpu" ) + { + total_cpu = field[2]; + } + else if( field[1] == "iface" ) + { +# Split the data values and + split(field[2], data, /,/); +# ... the ethernet MAC address. + link_mac = data[1]; + links_mac[link_mac] = 1; +# ... the input bandwidth/packets for this second, and + if( link_mac in links_last_in ) + { + last_pkt_value = links_last_pkt_in[link_mac]; + links_last_pkt_in[link_mac] = data[2]; + in_pkts = data[2] - last_pkt_value; + + last_value = links_last_in[link_mac]; + links_last_in[link_mac] = data[4]; + in_bw = data[4] - last_value; + } + else + { + links_last_pkt_in[link_mac] = data[2]; + links_last_in[link_mac] = data[4]; + + in_pkts = 0; + in_bw = 0; + } +# ... the output bandwidth/packets for this second. + if( link_mac in links_last_out ) + { + last_pkt_value = links_last_pkt_out[link_mac]; + links_last_pkt_out[link_mac] = data[3]; + out_pkts = data[3] - last_pkt_value; + + last_value = links_last_out[link_mac]; + links_last_out[link_mac] = data[5]; + out_bw = data[5] - last_value; + } + else + { + links_last_pkt_out[link_mac] = data[3]; + links_last_out[link_mac] = data[5]; + + out_pkts = 0; + out_bw = 0; + } +# Make sure the measures are 'per second' since slothd will sometimes +# miss an interval. + in_bw = in_bw / time_diff_s; + in_pkts = in_pkts / time_diff_s; + out_bw = out_bw / time_diff_s; + out_pkts = out_pkts / time_diff_s; +# Find the maximum of bandwidth/packets of the data seen so far. + if( in_pkts > links_mac_pkts[link_mac] ) + { + links_mac_pkts[link_mac] = in_pkts; + } + if( in_bw > links_mac_bw[link_mac] ) + { + links_mac_bw[link_mac] = in_bw; + } + if( out_pkts > links_mac_pkts[link_mac] ) + { + links_mac_pkts[link_mac] = out_pkts; + } + if( out_bw > links_mac_bw[link_mac] ) + { + links_mac_bw[link_mac] = out_bw; + } + line_macs[link_mac] = 1; + } + else if( field[1] == "vnode" ) + { + split(field[2], data, /,/); + split(data[1], name, /\./); + vnode_name = name[1]; + vnodes_name[vnode_name] = FNR; + vnodes_cpu[vnode_name,FNR] = data[2]; + vnodes_ram[vnode_name,FNR] = data[3]; + line_vnode_names[vnode_name] = 1; + total_vnode_cpu += data[2]; + vnode_count += 1; + } + } + +# Need to deal with CPU time lost in interrupts. + if( vnode_count == 0 ) + { + printf("error: no vnodes in line '%s'\n", $0) > /dev/stderr; + error = 1; + exit error; + } + else if( vnode_count == 1 ) + { + vnodes_cpu[vnode_name,FNR] = total_cpu; + } + else + { +# Check if slothd signalled overload for this period. + if( ovld || (total_cpu >= 99) ) + { +# We're in overload for this period, but do not signal an alert until +# we see OVERLOAD_MAX consecutive indicators. + if( overload[ARGIND] >= OVERLOAD_MAX ) + { + for( vnode_name in line_vnode_names ) + { + alerts[vnode_name] = 1; + } + for( mac in line_macs ) + { + alerts[mac] = 1; + } + } + else + { + overload[ARGIND] += 1; + } + } + else + { +# We're not in overload, so we clear it, and + overload[ARGIND] = 0; +# ... add any unaccounted for CPU to _all_ of the vnodes. Kind of a +# hack, but, nothing else to do here except be conservative. + diff = total_cpu - total_vnode_cpu; + printf("warning: unaccounted for CPU %f\n", diff) > /dev/stderr; + for( vnode_name in line_vnode_names ) + { + vnodes_cpu[vnode_name,FNR] += diff; + } + } + } +} + +# +# Process the mapping lines so we can find the mapping of IP addresses to +# symbolic link names. Also, we generate the reservations for the node <-> LAN +# connections, reservations for links will be done at the END when we know who +# the maximums are for the NICs on the links. +# +# Example input: +# +# 00:00:0a:01:01:02 node0 link +# +# Example output: +# +# set Reservations(link,node0,kbps) 123235.00 +# +/^[[:xdigit:]][[:xdigit:]]\:[[:xdigit:]][[:xdigit:]]\:[[:xdigit:]][[:xdigit:]]\:[[:xdigit:]][[:xdigit:]]\:[[:xdigit:]][[:xdigit:]]\:[[:xdigit:]][[:xdigit:]] [[:alnum:]\-]* [[:alnum:]\-]*$/ { + if( $1 in links_mac ) + { + printf("set Reservations(%s,%s,kbps) %f\n", + $3, + $2, + (links_mac_bw[$1] * 8) / 1000.0); + printf("set Reservations(%s,%s,pps) %f\n", + $3, + $2, + links_mac_pkts[$1]); + links_name[$3] = 1; + if( links_mac_bw[$1] > links_bw[$3] ) + links_bw[$3] = links_mac_bw[$1]; + if( links_mac_pkts[$1] > links_pkts[$3] ) + links_pkts[$3] = links_mac_pkts[$1]; + if( $1 in alerts ) + { + printf("set Alerts(%s,%s) 1\n", $3, $2); + printf("set Alerts(%s) 1\n", $3); + } + } +} + +# +# All done, time to print out the peak values we've discovered while +# processing. +# +END { + if( error ) + { + exit error; + } + + printf("# END Node/LAN\n\n"); + + printf("# BEGIN Nodes\n"); + for( vnode_name in vnodes_name ) + { + vnode_cpu = 0.001; + vnode_ram = 0.001; +# Note that we ignore the first and last values in the log, since the +# first value is always 100% and I just felt like making it symmetrical +# so the last was dropped as well. + for( lpc = 2; lpc < vnodes_name[vnode_name] - 1; lpc++ ) + { + if( vnodes_cpu[vnode_name,lpc] > vnode_cpu ) + { + vnode_cpu = vnodes_cpu[vnode_name,lpc]; + } + if( vnodes_ram[vnode_name,lpc] > vnode_ram ) + { + vnode_ram = vnodes_ram[vnode_name,lpc]; + } + } + printf("set Reservations(%s,cpupercent) %f\n", vnode_name, vnode_cpu); + printf("set Reservations(%s,rampercent) %f\n", vnode_name, vnode_ram); +# Check for an alert. + if( vnode_name in alerts ) + { + printf("set Alerts(%s) 1\n", vnode_name); + } + } + printf("# END Nodes\n\n"); + printf("# BEGIN Links\n"); + for( link_name in links_name ) + { + printf("set Reservations(%s,kbps) %f\n", + link_name, + (links_bw[link_name] * 8) / 1000.0); + printf("set Reservations(%s,pps) %f\n", + link_name, + links_pkts[link_name]); + } + printf("# END Links\n\n"); +} diff --git a/sensors/slothd/webfeedback.in b/sensors/slothd/webfeedback.in new file mode 100644 index 0000000000..641471bfcf --- /dev/null +++ b/sensors/slothd/webfeedback.in @@ -0,0 +1,328 @@ +#! /usr/bin/perl -wT + +use English; +use Getopt::Std; +use Socket; + +# +# Print out the usage statement for this script and exit with a -1 return code. +# +sub usage() +{ + print STDOUT + "Usage: webfeedback [-hc] [-d duration] pid gid eid\n". + "Web wrapper for dealing with feedback information.\n". + "\n". + "Required arguments:\n". + " pid - The project ID.\n". + " gid - The group ID.\n". + " eid - The experiment ID.\n". + "\n". + "Optional arguments:\n". + " -h Print this message.\n". + " -c Clear the feedback data.\n". + " -b Clear the bootstrap data.\n". + " -d secs Record feedback for the given duration.\n"; + + exit(-1); +} + +# +# Option list: +# +# -h Print the usage message. +# +my $optlist = "hcbd:f"; + +# +# Configure variables +# +my $TB = "@prefix@"; + +# Locals +my $digest_slothd = "$TB/libexec/digest-slothd"; +my $SAVEUID = $UID; +my $dbuid; +my $pid; +my $gid; +my $eid; +my $mode = ""; +my $duration; +my $fake = 0; + +# +# Untaint the path +# +$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/sbin:/usr/sbin"; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +# +# Testbed Support libraries +# +use lib "@prefix@/lib"; +use libdb; +use libtestbed; + +# +# Parse command arguments. Once we return from getopts, all that should +# left are the required arguments. +# +%options = (); +if (! getopts($optlist, \%options)) { + usage(); +} +if (defined($options{"h"})) { + usage(); +} +if (defined($options{"c"}) || defined($options{"b"})) { + $mode = "clear"; +} +if (defined($options{"d"})) { + if ($mode ne "") { + print STDERR "error: The clear and feedback options are mutually ". + "exclusive.\n"; + usage(); + } + $mode = "record"; + $duration = $options{"d"}; +} +if (defined($options{"f"})) { + $fake = 1; +} +if ($mode eq "") { + print STDERR "error: No mode specified, use '-c' to clear feedback or ". + "'-d N' to record for N seconds.\n"; + usage(); +} +if (@ARGV != 3) { + usage(); +} + +$pid = $ARGV[0]; +$gid = $ARGV[1]; +$eid = $ARGV[2]; + +# +# Must taint check! +# +if (defined($duration)) { + if ($duration =~ /^([0-9]+)$/) { + $duration = $1; + } + else { + die("Bad duration argument: $duration."); + } +} + +if ($pid =~ /^([-\w]+)$/) { + $pid = $1; +} +else { + die("Bad pid argument: $pid."); +} +if ($eid =~ /^([-\w]+)$/) { + $eid = $1; +} +else { + die("Bad eid argument: $eid."); +} +if ($gid =~ /^([-\w]+)$/) { + $gid = $1; +} +else { + die("Bad gid argument: $gid."); +} + +# +# Experiment must exist. +# +if (!($state = ExpState($pid,$eid))) { + die("There is no experiment $eid in project $pid\n"); +} + +# +# User must have permission to view the experiment. +# +if ($UID) { + if (!TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_MODIFY)) { + die("*** You not have permission to view this experiment!\n"); + } +} + +# XXX +$expdir = "/proj/$pid/exp/$eid"; + +# Figure out which mode we are in and act accordingly. +if ($mode eq "clear") { + if (defined($options{"c"})) { + unlink("$expdir/tbdata/feedback_data.tcl"); + } + if (defined($options{"b"})) { + unlink("$expdir/tbdata/bootstrap_data.tcl"); + } +} +if ($mode eq "record") { + + if ($state ne EXPTSTATE_ACTIVE) { + # nothing to do + print "Cannot record feedback for an inactive experiment.\n"; + exit(0); + } + + print STDOUT "Starting tracers...\n"; + + # Get the list of virtual hosts for the virtual nodes. + my $query_result = + DBQueryFatal("select node_id,vname from reserved ". + "where pid='$pid' and eid='$eid' and erole='virthost'"); + + # Iterate through the virthosts starting up slothd in high-resolution + # tracing mode. + while (my ($node_id,$vname) = $query_result->fetchrow_array()) { + my $cmd; + + $cmd = "rm -f /var/run/slothd.pid"; + if (!$fake) { + system("/usr/local/bin/sudo $TB/bin/sshtb -host $node_id \"$cmd\""); + } + $cmd = "/proj/tbres/kwebb/evslothd -e -i 1 -t ${duration}"; + if (!$fake) { + system("/usr/local/bin/sudo $TB/bin/sshtb -host $node_id \"$cmd\""); + } + } + + # Sleep for the duration of the run, then + sleep($duration + 3); + + $query_result->dataseek(0); + + print STDOUT "Pulling logs...\n"; + + $vhost_logs = ""; + + # ... iterate through the virthosts again picking up the logs. + while (my ($node_id,$vname) = $query_result->fetchrow_array()) { + my $cmd; + + $cmd = "/usr/local/bin/rsync -az ". + "--rsh=\"/usr/local/bin/sudo sshtb -host \" ". + "${node_id}:/var/emulab/logs/ ${expdir}/logs/${vname}/"; + $vhost_logs .= " ${expdir}/logs/${vname}/slothd.log"; + #if (!$fake) { + system($cmd); + #} + } + + + # Now that we have the logs, we have to find out what the peak resource + # needs are and then dump them into a TCL file. This TCL file is then + # included in the main NS file when it is reevaluated during a modify. + # Generating the file is actually done by a separate program, + # digest-slothd, but it requires a file that maps MAC addresses in the + # slothd log to the virtual node/link names. The rest of this script + # generates that file and pipes it into digest-slothd. + + # XXX Most of this code was just lifted from tbreport, it can probably be + # optimized a bit. + + # Mappings for IP/MAC addresses and testbed internal node/port member + # descriptors. + my %ipmap; + my %macmap; + my %memmap; + + # Get the virtual node names and IP addresses and + my $virtnodes_result = + DBQueryFatal("SELECT vname,ips from virt_nodes ". + "where pid='$pid' and eid='$eid' order by vname"); + + # ... convert them into "member" form (e.g. node0:0) so we can match them + # up against the virt_lans table. + while (($vname,$ips) = $virtnodes_result->fetchrow_array()) { + foreach $ipinfo (split(" ",$ips)) { + ($port,$ip) = split(":",$ipinfo); + $ipmap{"$vname:$port"} = $ip; + $macmap{$ip}->{"MEMBER"} = "$vname:$port"; + } + } + + # Get the addresses for regular interfaces and + my $result = + DBQueryFatal("select i.ip,i.mac,i.iface from reserved as r ". + "left join interfaces as i on r.node_id=i.node_id ". + "where r.pid='$pid' and r.eid='$eid' and ". + " i.ip is not NULL and i.ip!=''"); + + # ... add the MAC and node vname to the memmap. + while (($ip,$mac,$iface) = $result->fetchrow_array()) { + if ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) { + $mac = "$1:$2:$3:$4:$5:$6"; + } + $macmap{$ip}->{"MAC"} = $mac; + $macmap{$ip}->{"IFACE"} = $iface; + if (defined($macmap{$ip}->{"MEMBER"})) { + my $member = $macmap{$ip}->{"MEMBER"}; + my ($node,$port) = split(":", $member); + $memmap{$member}->{"MAC"} = $mac; + $memmap{$member}->{"NODE"} = $node; + } + } + + # Get the addresses for veth interfaces and + $result = + DBQueryFatal("select i.IP,i.mac,i.iface from reserved as r ". + "left join veth_interfaces as i on r.node_id=i.node_id ". + "where r.pid='$pid' and r.eid='$eid' and ". + " i.IP is not NULL and i.IP!=''"); + + # ... add the MAC and node vname to the memmap. + while (($ip,$mac,$iface) = $result->fetchrow_array()) { + if ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) { + $mac = "$1:$2:$3:$4:$5:$6"; + } + $macmap{$ip}->{"MAC"} = $mac; + $macmap{$ip}->{"IFACE"} = $iface; + if (defined($macmap{$ip}->{"MEMBER"})) { + my $member = $macmap{$ip}->{"MEMBER"}; + ($node,$port) = split(":", $member); + $memmap{$member}->{"MAC"} = $mac; + $memmap{$member}->{"NODE"} = $node; + } + } + + # Get all of the virtual LANs in this experiment and + $result = + DBQueryFatal("select vname,member ". + " from virt_lans as v ". + "where pid='$pid' and eid='$eid' ". + "order by vname,member"); + + # ... add their vnames to the memmap. + while (($vname,$member) = $result->fetchrow_array()) { + $memmap{$member}->{"LAN"} = $vname; + } + + # Start digest-slothd with all of the slothd logs, tee its output to the + # feedback file, and + open(DIGESTER, + "| $digest_slothd ${vhost_logs} - ". + " | tee ${expdir}/tbdata/feedback_data.tcl") or + fatal("Could not run digest-slothd!"); + + # ... pipe in our mapping data over stdin. + foreach my $m (sort keys(%memmap)) { + if (defined($memmap{$m})) { + printf DIGESTER + "%s %s %s\n", + $memmap{$m}->{"MAC"}, + $memmap{$m}->{"NODE"}, + $memmap{$m}->{"LAN"}; + } + } + + # All done, cleanup. + close(DIGESTER) or + fatal("$digest_slothd: " . ($? ? "exited with status $?." + : "error closing pipe: $!")); +} + diff --git a/tbsetup/assign_prepass.in b/tbsetup/assign_prepass.in index 978d58f9be..67318237c5 100755 --- a/tbsetup/assign_prepass.in +++ b/tbsetup/assign_prepass.in @@ -1281,16 +1281,36 @@ sub binpack_sort(@) { } } + if ($b->{'desires'} && $b->{'desires'}{'?+cpupercent'} && + $a->{'desires'} && $a->{'desires'}{'?+cpupercent'}) { + + $rv = ( $b->{'desires'}{'?+cpupercent'} <=> + $a->{'desires'}{'?+cpupercent'} ); + if ($rv != 0) { + return $rv; + } + } + # Then by memory - if ($b->{'desires'} && $b->{'desires'}{'?+mem'} && - $a->{'desires'} && $a->{'desires'}{'?+mem'}) { + if ($b->{'desires'} && $b->{'desires'}{'?+ram'} && + $a->{'desires'} && $a->{'desires'}{'?+ram'}) { - $rv = ( $b->{'desires'}{'?+mem'} <=> $a->{'desires'}{'?+mem'} ); + $rv = ( $b->{'desires'}{'?+ram'} <=> $a->{'desires'}{'?+ram'} ); if ($rv != 0) { return $rv; } } + if ($b->{'desires'} && $b->{'desires'}{'?+rampercent'} && + $a->{'desires'} && $a->{'desires'}{'?+rampercent'}) { + + $rv = ( $b->{'desires'}{'?+rampercent'} <=> + $a->{'desires'}{'?+rampercent'} ); + if ($rv != 0) { + return $rv; + } + } + # Fall back to count if neither of the others were given return $b->{'count'} <=> $a->{'count'}; } @_; diff --git a/tbsetup/ns2ir/GNUmakefile.in b/tbsetup/ns2ir/GNUmakefile.in index 598603772f..4868152795 100644 --- a/tbsetup/ns2ir/GNUmakefile.in +++ b/tbsetup/ns2ir/GNUmakefile.in @@ -1,6 +1,6 @@ # # EMULAB-COPYRIGHT -# Copyright (c) 2000-2003 University of Utah and the Flux Group. +# Copyright (c) 2000-2004 University of Utah and the Flux Group. # All rights reserved. # diff --git a/tbsetup/ns2ir/sim.tcl.in b/tbsetup/ns2ir/sim.tcl.in index 06c08c6d92..75d1695c4e 100644 --- a/tbsetup/ns2ir/sim.tcl.in +++ b/tbsetup/ns2ir/sim.tcl.in @@ -303,6 +303,26 @@ Simulator instproc run {} { # If any errors occur stop here. if {$errors == 1} {return} + # Write out the feedback "bootstrap" file. + var_import ::TBCOMPAT::expdir; + var_import ::TBCOMPAT::BootstrapReservations; + + if {! [file isdirectory $expdir]} { + # Experiment directory does not exist, so we cannot write the file... + } elseif {[array size BootstrapReservations] > 0} { + set file [open "$expdir/tbdata/bootstrap_data.tcl" w] + puts $file "# -*- TCL -*-" + puts $file "# Automatically generated feedback bootstrap file." + puts $file "#" + puts $file "# Generated at: [clock format [clock seconds]]" + puts $file "#" + puts $file "" + foreach res [array names BootstrapReservations] { + puts $file "set Reservations($res) $BootstrapReservations($res)" + } + close $file + } + # If we are running in impotent mode we stop here if {$impotent == 1 && $passmode == 0} {return} diff --git a/tbsetup/ns2ir/tb_compat.tcl.in b/tbsetup/ns2ir/tb_compat.tcl.in index 8e0939f041..cff3ce2b4e 100644 --- a/tbsetup/ns2ir/tb_compat.tcl.in +++ b/tbsetup/ns2ir/tb_compat.tcl.in @@ -48,6 +48,37 @@ namespace eval TBCOMPAT { $node ip $port $ip } + # + # Named argument helper function. + # + # Example: + # + # proc replace {s args} { + # named $args {-from 0 -to end -with ""} + # string replace $s $(-from) $(-to) $(-with) + # } + # + # % replace suchenwirth -from 4 -to 6 -with xx + # suchxxirth + # % replace suchenwirth -from 4 -to 6 -witha xx + # bad option '-witha', should be one of: -from -to -with + # + # @param args The optional arguments the caller received. + # @param defaults The option list with default values. + # + # @see http://wiki.tcl.tk/10702 + # + proc named-args {args defaults} { + upvar 1 "" "" + array set "" $defaults + foreach {key value} $args { + if {![info exists ($key)]} { + error "bad option '$key', should be one of: [lsort [array names {}]]" + } + set ($key) $value + } + } + # Let's set up a hwtypes table that contains all valid hardware types. variable hwtypes variable isremote @@ -90,6 +121,102 @@ namespace eval TBCOMPAT { # Physical node names variable physnodes + + ## Feedback related stuff below: + + # Experiment directory name. + variable expdir + + # Mapping of "resource classes" and "reservation types" to bootstrap + # values, where a resource class is a symbolic string provided by the user + # (e.g. Client, Server), and a reservation type is a resource name provided + # by the system (e.g. cpupercent, kbps). This array will be filled by the + # tb-feedback methods and then written out to a "bootstrap_data.tcl" file + # to be read in during future evaluations of the NS file. + variable BootstrapReservations + + # The experiment directory, this is where the feedback related files will + # be read from and dumped to. XXX Hacky + set expdir "/proj/${::GLOBALS::pid}/exp/${::GLOBALS::eid}/" + + # XXX Just for now... + global tbxlogfile + if {[file exists "$expdir"]} { + set tbxlogfile [open "$expdir/logs/feedback.log" w]; + } + + # Get any Emulab generated feedback data from the experiment directory. + if {[file exists "${expdir}/tbdata/feedback_data.tcl"]} { + source "${expdir}/tbdata/feedback_data.tcl" + } + # Get any bootstrap feedback data from a previous run. + if {[file exists "${expdir}/tbdata/bootstrap_data.tcl"]} { + source "${expdir}/tbdata/bootstrap_data.tcl" + } + + # + # Configure the default reservations for an object based on an optional + # "resource class". First, the function will check for a reservation + # specifically made for the object, then it will try to initialize the + # reservation from the resource class, otherwise it does nothing and + # returns zero. + # + # @param object The object name for which to configure the feedback + # defaults. + # @param rclass The "resource class" of the object or the empty string if + # it is not part of any class. This is just a symbolic string, such as + # "Client" or "Server". + # @return One, if there is an initialized slot in the "Reservations" array + # for the given object, or zero if it could not be initialized. + # + proc feedback-defaults {object rclass} { + var_import ::TBCOMPAT::Reservations; # The reservations to make + + if {[array get Reservations $object,*] == ""} { + # No node-specific values exist, try to initialize from the rclass. + if {[array get Reservations $rclass,*] != ""} { + # Use bootstrap feedback from a previous topology, + set rcdefaults [array get Reservations $rclass,*] + # ... substitute the node name for the rclass, and + regsub -all -- $rclass $rcdefaults $object rcdefaults + # ... add all the reservations to the table. + array set Reservations $rcdefaults + set retval 1 + } else { + # No feedback exists yet, let the caller fill it in. + set retval 0 + } + } else { + # Node-specific values exist, use those. + set retval 1 + } + return $retval + } + + # + # Record bootstrap feedback data for a resource class. This function + # should be called for every member of a resource class so that the one + # with the highest reservation will be used to bootstrap. + # + # @param rclass The "resource class" for which to update the bootstrap + # feedback data. This is just a symbolic string, such as "Client" or + # "Server". + # @param rtype The type of reservation (e.g. cpupercent,kbps). + # @param res The amount to reserve. + # + proc feedback-bootstrap {rclass rtype res} { + # The bootstrap reservations + var_import ::TBCOMPAT::BootstrapReservations + + if {$rclass == ""} { + # No class to operate on... + } elseif {([array get BootstrapReservations($rclass,$rtype)] == "") || + ($res > $BootstrapReservations($rclass,$rtype))} { + # This is either the first time this function was called for + # this rclass/rtype or the new value is greater than the old. + set BootstrapReservations($rclass,$rtype) $res + } + } } # IP addresses routines. These all do some checks and convert into set-ip @@ -892,6 +1019,253 @@ proc tb-use-physnaming {onoff} { set use_physnaming $onoff } +# +# Write to the tb-experimental log file, as defined by the tbxlogfile global +# variable. If the tbxlogfile variable is not set, the message is sent to +# /dev/null. +# +# @param msg The message to write to the log file. +# +# @global tbxlogfile The path to the log file, if defined. +# +proc tbx-log {msg} { + global tbxlogfile; + + if {[info exists tbxlogfile]} { + puts $tbxlogfile $msg + } +} + +## +## BEGIN Feedback +## + +proc tb-feedback-vnode {vnode hardware args} { + var_import ::TBCOMPAT::isvirt; # Make sure $hardware is a vnode. + var_import ::TBCOMPAT::Reservations; # The reservations to make for nodes. + var_import ::TBCOMPAT::BootstrapReservations; # Bootstrap file. + var_import ::TBCOMPAT::Alerts; # Alert indicators + + ::TBCOMPAT::named-args $args {-scale 1.2 -rclass "" -alertscale 3.0} + + # Check our inputs, + if {[$vnode info class] != "Node"} { + perror "\[tb-feedback-vnode] $vnode is not a node." + return + } + if {(! [info exists isvirt($hardware)]) || (! $isvirt($hardware))} { + perror "\[tb-feedback-vnode] Unknown hardware type: $hardware" + return + } + if {$(-scale) <= 0.0} { + perror "\[tb-feedback-vnode] Feedback scale is not greater than zero: $(-scale)" + return + } + + tbx-log "BEGIN feedback for $vnode" + + # ... set computed default values, and + if {[::TBCOMPAT::feedback-defaults $vnode $(-rclass)] == 0} { + # No feedback exists yet, so we assume 100%. + set Reservations($vnode,cpupercent) 100.0 + set Reservations($vnode,rampercent) 100.0 + tbx-log " Initializing node, $vnode, to one-to-one." + } + + # ... make the reservations. + foreach name [array names Reservations $vnode,*] { + # Get the type of reservation and + set reservation_type [lindex [split $name {,}] 1] + # ... the amount consumed. + set raw_reservation [set Reservations($name)] + + ::TBCOMPAT::feedback-bootstrap \ + $(-rclass) $reservation_type $raw_reservation + + # Then scale the reservation + set desired_reservation [expr $raw_reservation * $(-scale)] + # ... making sure it is still within the range of the hardware. + if {$desired_reservation < 0.0} { + # XXX Not allowing negative values might be too restrictive... + perror "\[tb-feedback-vnode] Bad reservation value: $name = $raw_reservation" + return + } + if {([array get Alerts $vnode] != "") && [set Alerts($vnode)] > 0} { + tbx-log "Alert for $vnode" + set desired_reservation \ + [expr $desired_reservation * $(-alertscale)]; # XXX + } + if {$desired_reservation > 100.0} { + set desired_reservation 100.0 + } + tbx-log " $reservation_type: ${desired_reservation}" + # Finally, tell assign about our desire. + $vnode add-desire ?+${reservation_type} ${desired_reservation} + } + + tb-set-hardware $vnode $hardware + + tbx-log "END feedback for $vnode" +} + +proc tb-feedback-vlan {vnode lan args} { + var_import ::TBCOMPAT::Reservations; # The reservations to make for lans + var_import ::TBCOMPAT::Alerts; # Alert indicators + + ::TBCOMPAT::named-args $args {-scale 1.0 -rclass "" -alertscale 3.0} + + if {[$vnode info class] != "Node"} { + perror "\[tb-feedback-vlan] $vnode is not a node." + return + } + if {[$lan info class] != "Lan"} { + perror "\[tb-feedback-vlan] $lan is not a LAN." + return + } + if {$(-scale) <= 0.0} { + perror "\[tb-feedback-vlan] Feedback scale is not greater than zero: $(-scale)" + return + } + + tbx-log "BEGIN feedback for node $vnode on lan $lan" + + if {[::TBCOMPAT::feedback-defaults "$vnode,$lan" $(-rclass)] == 0} { + # No feedback exists yet, so we assume 100%. Fortunately, everything + # already assumes 100%, so we do not have to do anything extra. + tbx-log " Initializing vlan, $vnode $lan, to one-to-one." + } + + foreach name [array names Reservations ${vnode},${lan},kbps] { + # Get the type of reservation and + set reservation_type [lindex [split $name {,}] 1] + # ... its value. + set raw_reservation [set Reservations($name)] + tbx-log " raw: $raw_reservation" + # Get the maximum allowed value and + set max_reservation 0 + foreach pair [$lan array names bandwidth "${vnode} *"] { + tbx-log " pair: $pair - [$lan set bandwidth($pair)]" + if {[$lan set bandwidth($pair)] > $max_reservation} { + set max_reservation [$lan set bandwidth($pair)] + } + } + tbx-log " max: $max_reservation" + # ... fix any measuring/shaping error. + if {$raw_reservation > $max_reservation} { + tbx-log " request > max: $raw_reservation $max_reservation" + set raw_reservation $max_reservation + } + + ::TBCOMPAT::feedback-bootstrap \ + $(-rclass) $reservation_type $raw_reservation + + # Then scale the reservation + set desired_reservation \ + [expr int(sqrt($raw_reservation * $max_reservation) * $(-scale))] + # ... making sure it is still within the range of the hardware. + if {$desired_reservation < 0.0} { + # XXX Not allowing negative values might be too restrictive... + perror "\[tb-feedback-vlan] Bad reservation value: $name = $raw_reservation" + } elseif {$desired_reservation < 10.0} { + set desired_reservation 10; # XXX see parse.tcl.in + } + if {([array get Alerts $lan,$vnode] != "") && + [set Alerts($lan,$vnode)] > 0} { + tbx-log "Alert for $lan, $vnode" + set desired_reservation \ + [expr $desired_reservation * $(-alertscale)]; # XXX + } + if {$desired_reservation > $max_reservation} { + set desired_reservation $max_reservation + } + + tbx-log " $reservation_type: ${desired_reservation}" + + # Finally, adjust the cap. + tb-set-node-lan-est-bandwidth $vnode $lan ${desired_reservation}kb + } + + tbx-log "END feedback for node $vnode on lan $lan" +} + +proc tb-feedback-vlink {link args} { + var_import ::TBCOMPAT::Reservations; # The reservations to make for links + var_import ::TBCOMPAT::Alerts; # Alert indicators + + ::TBCOMPAT::named-args $args {-scale 1.2 -rclass "" -alertscale 3.0} + + if {[$link info class] != "Link"} { + perror "\[tb-feedback-vlink] $link is not a link." + return + } + if {$(-scale) <= 0.0} { + perror "\[tb-feedback-vlink] Feedback scale is not greater than zero: $(-scale)" + return + } + + tbx-log "BEGIN feedback for link $link" + + if {[::TBCOMPAT::feedback-defaults $link $(-rclass)] == 0} { + # No feedback exists yet, so we assume 100%. Fortunately, not + # specifying anything implies 100%, so we do not have to do anything + # extra. + tbx-log " Initializing vlink, $link, to one-to-one." + } + + foreach name [array names Reservations $link,kbps] { + # Get the type of reservation and + set reservation_type [lindex [split $name {,}] 1] + # ... its value. + set raw_reservation [set Reservations($name)] + # Get the maximum allowed value and + set max_reservation 0 + foreach pair [$link array names bandwidth] { + if {[$link set bandwidth($pair)] > $max_reservation} { + set max_reservation [$link set bandwidth($pair)] + } + } + # ... fix any measuring/shaping error. + if {$raw_reservation > $max_reservation} { + tbx-log " request > max: $raw_reservation $max_reservation" + set raw_reservation $max_reservation + } + + ::TBCOMPAT::feedback-bootstrap \ + $(-rclass) $reservation_type $raw_reservation + + # Then scale the reservation + set desired_reservation \ + [expr int(sqrt($raw_reservation * $max_reservation))] + # ... making sure it is still within the range of the hardware. + if {$desired_reservation < 0.0} { + # XXX Not allowing negative values might be too restrictive... + perror "\[tb-feedback-vlink] Bad reservation value: $name = $raw_reservation" + return + } elseif {$desired_reservation < 10.0} { + set desired_reservation 10; # XXX see parse.tcl.in + } + if {([array get Alerts $link] != "") && [set Alerts($link)] > 0} { + tbx-log "Alert for $link" + set desired_reservation \ + [expr $desired_reservation * $(-alertscale)]; # XXX + } + if {$desired_reservation > $max_reservation} { + set desired_reservation $max_reservation + } + + tbx-log " $reservation_type: ${desired_reservation}" + + # Finally, adjust the cap. + tb-set-link-est-bandwidth $link ${desired_reservation}kb + } + + tbx-log "END feedback for link $link" +} + +## +## END Feedback +## + # # User indicates that this is a modelnet experiment. Be default, the number # of core and edge nodes is set to one each. The user must increase those @@ -921,4 +1295,3 @@ proc tb-set-modelnet-physnodes {cores edges} { set modelnet_cores $cores set modelnet_edges $edges } - diff --git a/tbsetup/ptopgen.in b/tbsetup/ptopgen.in index fb6a21cff0..da6cf9a7bc 100644 --- a/tbsetup/ptopgen.in +++ b/tbsetup/ptopgen.in @@ -388,6 +388,8 @@ foreach $node (keys(%nodes)) { # Add CPU and RAM information push @features, "?+cpu:$cpu_speed"; push @features, "?+ram:$ram"; + push @features, "?+cpupercent:100"; + push @features, "?+rampercent:100"; } # Add features diff --git a/www/feedback.php3 b/www/feedback.php3 new file mode 100644 index 0000000000..f5d057eb68 --- /dev/null +++ b/www/feedback.php3 @@ -0,0 +1,220 @@ +<?php +# +# EMULAB-COPYRIGHT +# Copyright (c) 2000-2004 University of Utah and the Flux Group. +# All rights reserved. +# +include("defs.php3"); +include("showstuff.php3"); + +# +# No PAGEHEADER since we spit out a Location header later. See below. +# + +# +# Only known and logged in users can do this. +# +$uid = GETLOGIN(); +LOGGEDINORDIE($uid); + +# +# Check to make sure a valid experiment. +# +if (isset($pid) && strcmp($pid, "") && + isset($eid) && strcmp($eid, "")) { + if (! TBvalid_eid($eid)) { + PAGEARGERROR("$eid contains invalid characters!"); + } + if (! TBvalid_pid($pid)) { + PAGEARGERROR("$pid contains invalid characters!"); + } + if (! TBValidExperiment($pid, $eid)) { + USERERROR("$pid/$eid is not a valid experiment!", 1); + } + if (! TBExptAccessCheck($uid, $pid, $eid, $TB_EXPT_MODIFY)) { + USERERROR("You do not have permission to run feedback on $pid/$eid!", + 1); + } +} +else { + PAGEARGERROR("Must specify pid and eid!"); +} + +if (isset($mode) && strcmp($mode, "")) { + if (strcmp($mode, "record") && strcmp($mode, "clear")) { + PAGEARGERROR("Mode value, $mode, is not 'record' or 'clear'!"); + } +} +else { + PAGEARGERROR("Must specify mode!"); +} + +$query_result = DBQueryFatal("select gid from experiments ". + "where pid='$pid' and eid='$eid'"); +$row = mysql_fetch_array($query_result); +$gid = $row[0]; + +# +# Get the duration of the feedback run, default to 30 seconds if nothing was +# given. +# +if (!isset($duration) || $duration == "") { + $duration = 30; # seconds +} +elseif (! TBvalid_tinyint($duration) || $duration < 3) { + PAGEARGERROR("Duration must be an integer >= 3"); +} + +# +# We run this twice. The first time we are checking for a confirmation +# by putting up a form. The next time through the confirmation will be +# set. Or, the user can hit the cancel button, in which case we should +# probably redirect the browser back up a level. +# +if ($canceled) { + PAGEHEADER("$mode Feedback"); + + echo "<center><h3><br> + Operation canceled! + </h3></center>\n"; + + PAGEFOOTER(); + return; +} + +if (!$confirmed) { + PAGEHEADER("$mode feedback"); + + echo "<font size=+2>Experiment <b>". + "<a href='showproject.php3?pid=$pid'>$pid</a>/". + "<a href='showexp.php3?pid=$pid&eid=$eid'>$eid</a></b></font>\n"; + + if(strcmp($mode, "record") == 0) { + echo "<center><font size=+2><br> + How much feedback data should be recorded?<br> + Must be atleast 3 seconds. + </font>\n"; + } + else if(strcmp($mode, "clear") == 0) { + echo "<center><font size=+2><br> + Really clear feedback data? + </font>\n"; + } + + SHOWEXP($pid, $eid, 1); + + echo "<form action=feedback.php3 method=get>"; + echo "<input type=hidden name=pid value=$pid>\n"; + echo "<input type=hidden name=eid value=$eid>\n"; + echo "<input type=hidden name=mode value=$mode>\n"; + + if(strcmp($mode, "record") == 0) { + echo "<table align=center border=1>\n"; + echo "<tr> + <td> + <input type='text' name='duration' value='$duration'> seconds + </td> + </tr> + </table><br>\n"; + } + else if(strcmp($mode, "clear") == 0) { + echo "<br>Data to clear:</br>"; + echo "<table align=center border=1>\n"; + echo "<tr> + <td> + <input type='checkbox' name='clear_last' value='1' checked='1'> Last recorded trace. + </td> + </tr> + <tr> + <td> + <input type='checkbox' name='clear_bootstrap' value='1'> Bootstrap data. + </td> + </tr> + </table><br>\n"; + } + + echo "<b><input type=submit name=confirmed value=Confirm></b>\n"; + echo "<b><input type=submit name=canceled value=Cancel></b>\n"; + echo "</form>\n"; + echo "</center>\n"; + + PAGEFOOTER(); + return; +} + +function SPEWCLEANUP() +{ + global $fp; + + if (connection_aborted() && $fp) { + pclose($fp); + } + exit(); +} + +# For backend. +TBGroupUnixInfo($pid, $gid, $unix_gid, $unix_name); + +if (strcmp($mode, "record") == 0) { + # + # A cleanup function to keep the child from becoming a zombie, since + # the script is terminated, but the children are left to roam. + # + $fp = 0; + + register_shutdown_function("SPEWCLEANUP"); + ignore_user_abort(1); + + # Start the script and pipe its output to the user. + $fp = popen("$TBSUEXEC_PATH $uid $unix_gid webfeedback -d $duration $pid $gid $eid", + "r"); + if (! $fp) { + USERERROR("Feedback failed!", 1); + } + + header("Content-Type: text/plain"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + flush(); + + echo date("D M d G:i:s T"); + echo "\n"; + echo "Recording feedback for $duration seconds\n"; + flush(); + while (!feof($fp)) { + $string = fgets($fp, 1024); + echo "$string"; + flush(); + } + $retval = pclose($fp); + $fp = 0; + if ($retval == 0) + echo "Feedback run was successful!\n"; + echo date("D M d G:i:s T"); + echo "\n"; + return; +} +else if (strcmp($mode, "clear") == 0) { + PAGEHEADER("Clearing feedback"); + + echo "foo $clear_last $clear_bootstrap"; + + $options = ""; + if (isset($clear_last) && !strcmp($clear_last, "1")) + $options .= " -c"; + if (isset($clear_bootstrap) && !strcmp($clear_bootstrap, "1")) + $options .= " -b"; + if ($options != "") { + $retval = SUEXEC($uid, $unix_gid, + "webfeedback $options $pid $gid $eid", + SUEXEC_ACTION_USERERROR); + } + + echo "<center><h3><br>Done! $options</h3></center>\n"; + + PAGEFOOTER(); + return; +} + +?> diff --git a/www/remapexp.php3 b/www/remapexp.php3 new file mode 100644 index 0000000000..0366a792f5 --- /dev/null +++ b/www/remapexp.php3 @@ -0,0 +1,181 @@ +<?php +# +# EMULAB-COPYRIGHT +# Copyright (c) 2000-2004 University of Utah and the Flux Group. +# All rights reserved. +# +include("defs.php3"); +include("showstuff.php3"); + +PAGEHEADER("Remap Virtual Nodes"); + +# +# Only known and logged in users can do this. +# +$uid = GETLOGIN(); +LOGGEDINORDIE($uid); + +# +# Check to make sure a valid experiment. +# +if (isset($pid) && strcmp($pid, "") && + isset($eid) && strcmp($eid, "")) { + if (! TBvalid_eid($eid)) { + PAGEARGERROR("$eid is contains invalid characters!"); + } + if (! TBvalid_pid($pid)) { + PAGEARGERROR("$pid is contains invalid characters!"); + } + if (! TBValidExperiment($pid, $eid)) { + USERERROR("$pid/$eid is not a valid experiment!", 1); + } + if (! TBExptAccessCheck($uid, $pid, $eid, $TB_EXPT_MODIFY)) { + USERERROR("You do not have permission to run remap on $pid/$eid!", + 1); + } +} +else { + PAGEARGERROR("Must specify pid and eid!"); +} + +$expstate = TBExptState($pid, $eid); + +if (strcmp($expstate, $TB_EXPTSTATE_ACTIVE) && + strcmp($expstate, $TB_EXPTSTATE_SWAPPED)) { + USERERROR("You cannot remap an experiment in transition.", 1); +} + +if (!strcmp($expstate, $TB_EXPTSTATE_ACTIVE)) { + $reboot = 1; + $eventrestart = 1; +} +else { + $reboot = 0; + $eventrestart = 0; +} + +if ($canceled) { + + echo "<center><h3><br> + Operation canceled! + </h3></center>\n"; + + PAGEFOOTER(); + return; +} + +if (!$confirmed) { + + echo "<font size=+2>Experiment <b>". + "<a href='showproject.php3?pid=$pid'>$pid</a>/". + "<a href='showexp.php3?pid=$pid&eid=$eid'>$eid</a></b></font>\n"; + + echo "<center><font size=+2><br> + Are you sure you want to remap your experiment? + </font>\n"; + + SHOWEXP($pid, $eid, 1); + + echo "<form action=remapexp.php3 method=post>"; + echo "<input type=hidden name=pid value=$pid>\n"; + echo "<input type=hidden name=eid value=$eid>\n"; + + echo "<b><input type=submit name=confirmed value=Confirm></b>\n"; + echo "<b><input type=submit name=canceled value=Cancel></b>\n"; + echo "</form>\n"; + echo "</center>\n"; + + PAGEFOOTER(); + return; +} + +# For backend. +TBExptGroup($pid, $eid, $gid); +TBGroupUnixInfo($pid, $gid, $unix_gid, $unix_name); + +echo "<center>"; +echo "<h2>Starting experiment remap. Please wait a moment ... + </h2></center>"; + +flush(); + +# +# Avoid SIGPROF in child. +# +set_time_limit(0); + +$query_result = DBQueryFatal("SELECT nsfile from nsfiles ". + "where pid='$pid' and eid='$eid'"); +if (mysql_num_rows($query_result)) { + $row = mysql_fetch_array($query_result); + $nsdata = stripslashes($row[nsfile]); +} +else { + $nsdata = ""; # XXX what to do... +} + +list($usec, $sec) = explode(' ', microtime()); +srand((float) $sec + ((float) $usec * 100000)); +$foo = rand(); +$nsfile = "/tmp/$uid-$foo.nsfile"; + +if (! ($fp = fopen($nsfile, "w"))) { + TBERROR("Could not create temporary file $nsfile", 1); +} +fwrite($fp, $nsdata); +fclose($fp); +chmod($nsfile, 0666); + +$retval = SUEXEC($uid, $unix_gid, + "webswapexp " . ($reboot ? "-r " : "") . + ($eventrestart ? "-e " : "") . + "-s modify $pid $eid $nsfile", + SUEXEC_ACTION_IGNORE); + +unlink($nsfile); + +# +# Fatal Error. Report to the user, even though there is not much he can +# do with the error. Also reports to tbops. +# +if ($retval < 0) { + SUEXECERROR(SUEXEC_ACTION_DIE); + # + # Never returns ... + # + die(""); +} + +# +# Exit status 0 means the experiment is swapping, or will be. +# +echo "<br>\n"; +if ($retval) { + echo "<h3>Experiment remap could not proceed: $retval</h3>"; + echo "<blockquote><pre>$suexec_output<pre></blockquote>"; +} +else { + # + # Exit status 0 means the experiment is modifying. + # + echo "<br>"; + echo "Your experiment is being remapped!<br><br>"; + + echo "You will be notified via email when the experiment has ". + "finished remapping and you are able to proceed. This ". + "typically takes less than 10 minutes, depending on the ". + "number of nodes in the experiment. ". + "If you do not receive email notification within a ". + "reasonable amount time, please contact $TBMAILADDR. ". + "<br><br>". + "While you are waiting, you can watch the log of experiment ". + "modification in ". + "<a target=_blank href=spewlogfile.php3?pid=$pid&eid=$eid> ". + "realtime</a>.\n"; +} + +# +# Standard Testbed Footer +# +PAGEFOOTER(); +?> diff --git a/www/showexp.php3 b/www/showexp.php3 index d98f8e9200..b3ddad5636 100644 --- a/www/showexp.php3 +++ b/www/showexp.php3 @@ -166,8 +166,21 @@ if ($expstate == $TB_EXPTSTATE_ACTIVE) { WRITESUBMENUBUTTON(($linktest_running ? "Stop LinkTest" : "Run LinkTest"), "linktest.php3?pid=$exp_pid&eid=$exp_eid"); + + WRITESUBMENUBUTTON("Record Feedback Data", + "feedback.php3?pid=$exp_pid&eid=$exp_eid&mode=record"); } } + +if (($expstate == $TB_EXPTSTATE_ACTIVE || + $expstate == $TB_EXPTSTATE_SWAPPED) && + STUDLY()) { + WRITESUBMENUBUTTON("Clear Feedback Data", + "feedback.php3?pid=$exp_pid&eid=$exp_eid&mode=clear"); + + WRITESUBMENUBUTTON("Remap Virtual Nodes", + "remapexp.php3?pid=$exp_pid&eid=$exp_eid"); +} # Wireless maps if experiment includes wireless lans. if ($wireless) { -- GitLab