Commit ec9601a9 authored by Gary Wong's avatar Gary Wong

Add heuristic "free count" of nodes, considering future reservations.

And a bunch of related tweaks.
parent fdc86d7f
...@@ -519,30 +519,38 @@ sub LookupAll($$) ...@@ -519,30 +519,38 @@ sub LookupAll($$)
while( my($count, $pid, $eid, $uid, $end, $autoswap, $next_reserve, while( my($count, $pid, $eid, $uid, $end, $autoswap, $next_reserve,
$slice_expire, $slice_lockdown ) = $slice_expire, $slice_lockdown ) =
$query_result->fetchrow_array() ) { $query_result->fetchrow_array() ) {
# If a node has an outstanding next_reserve, assume it's my $endtime;
# unavailable until further notice -- treat it as if it doesn't
# exist.
next if( defined( $next_reserve ) );
if( defined( $slice_expire ) ) { if( defined( $slice_expire ) ) {
# A GENI slice: its end time is the slice expiration... # Node(s) allocated to a GENI slice. Treat as unavailable
$end = $slice_expire; # if locked down, otherwise assume released at slice expiry
# time.
# ...unless it's locked down, in which we consider the $endtime = $slice_lockdown ? undef : $slice_expire;
# node unavailable:
next if( $slice_lockdown );
} else { } else {
# Not a GENI slice... if it's not marked autoswap, assume # A non-GENI slice. Use the computed autoswap duration,
# the nodes aren't coming back (hwdown looks like this, for # if autoswap is enabled.
# example). $endtime = $autoswap ? $end : undef;
next if( defined( $pid ) && !$autoswap );
} }
if( defined( $pid ) ) { # If next_reserve is set, assume unavailable indefinitely.
# FIXME can we obtain more precise predictions by doing a
# CreateExisting() for the current experiment then something
# else at $endtime?
if( defined( $next_reserve ) ) {
$endtime = undef;
}
# Consider nodes in reloading to be available. One important
# reason for doing so is that if a project has a current reservation
# and swaps one experiment out and a replacement in, then during
# the transition period, sufficient nodes must be considered available
# for assignment to that project.
if( defined( $pid ) && ( $pid ne "emulab-ops" ||
$eid ne "reloading" ) ) {
# Handle the case where an experiment is swapped in. The # Handle the case where an experiment is swapped in. The
# nodes aren't free right now, but at some time in the # nodes aren't free right now, but at some time in the
# future they will become so. # future they could become so.
my $res = CreateExisting( $class, $pid, $eid, $uid, $end, $type, my $res = CreateExisting( $class, $pid, $eid, $uid, $end, $type,
$count ); $count );
push( @reservations, $res ); push( @reservations, $res );
...@@ -570,14 +578,19 @@ sub LookupAll($$) ...@@ -570,14 +578,19 @@ sub LookupAll($$)
return $cache{$type}; return $cache{$type};
} }
sub IsFeasible($$;$$$) sub IsFeasible($$;$$$$$)
{ {
my ($class, $reservations, $error, $conflicttime, $conflictcount) = @_; my ($class, $reservations, $error, $conflicttime, $conflictcount,
$projlist, $forecast) = @_;
my @timeline = (); my @timeline = ();
my $free = 0; my $free = 0;
my %used = ();
my %reserved = ();
my $answer = 1;
foreach my $reservation ( @$reservations ) { foreach my $reservation ( @$reservations ) {
my $pid = $reservation->pid();
my $start; my $start;
my $end; my $end;
...@@ -588,7 +601,15 @@ sub IsFeasible($$;$$$) ...@@ -588,7 +601,15 @@ sub IsFeasible($$;$$$)
't' => $reservation->start(), 't' => $reservation->start(),
'used' => $reservation->nodes(), 'used' => $reservation->nodes(),
'reserved' => 0 }; 'reserved' => 0 };
} else {
# A mapped experiment. Using physical nodes now.
if( defined( $used{ $pid } ) ) {
$used{ $pid } += $reservation->nodes();
} else {
$used{ $pid } = $reservation->nodes();
}
} }
# Will later release real nodes. # Will later release real nodes.
$end = { 'pid' => $reservation->pid(), $end = { 'pid' => $reservation->pid(),
't' => $reservation->end(), 't' => $reservation->end(),
...@@ -596,6 +617,10 @@ sub IsFeasible($$;$$$) ...@@ -596,6 +617,10 @@ sub IsFeasible($$;$$$)
'reserved' => 0 }; 'reserved' => 0 };
} elsif( defined( $reservation->pid() ) ) { } elsif( defined( $reservation->pid() ) ) {
# A reservation. Uses then releases reserved nodes. # A reservation. Uses then releases reserved nodes.
# Ignore reservations for listed projects.
next if( grep( $_ eq $reservation->pid(), @$projlist ) );
$start = { 'pid' => $reservation->pid(), $start = { 'pid' => $reservation->pid(),
't' => $reservation->start(), 't' => $reservation->start(),
'used' => 0, 'used' => 0,
...@@ -614,15 +639,12 @@ sub IsFeasible($$;$$$) ...@@ -614,15 +639,12 @@ sub IsFeasible($$;$$$)
} }
my @events = sort { $a->{'t'} <=> $b->{'t'} } @timeline; my @events = sort { $a->{'t'} <=> $b->{'t'} } @timeline;
my %used = ();
my %reserved = ();
foreach my $event ( @events ) { foreach my $event ( @events ) {
my $pid = $event->{'pid'}; my $pid = $event->{'pid'};
if( !exists( $used{ $pid } ) ) {
$used{ $pid } = 0; $used{ $pid } = 0 if( !exists( $used{ $pid } ) );
$reserved{ $pid } = 0; $reserved{ $pid } = 0 if( !exists( $reserved{ $pid } ) );
}
my $oldsum = $used{ $pid } > $reserved{ $pid } ? $used{ $pid } : my $oldsum = $used{ $pid } > $reserved{ $pid } ? $used{ $pid } :
$reserved{ $pid }; $reserved{ $pid };
...@@ -635,6 +657,18 @@ sub IsFeasible($$;$$$) ...@@ -635,6 +657,18 @@ sub IsFeasible($$;$$$)
$free += $oldsum - $newsum; $free += $oldsum - $newsum;
if( defined( $forecast ) ) {
my %used_ = %used;
my %reserved_ = %reserved;
my %stamp = (
t => $event->{'t'},
used => \%used_,
reserved => \%reserved_,
free => $free
);
push( @$forecast, \%stamp );
}
if( $free < 0 ) { if( $free < 0 ) {
# Insufficient resources. # Insufficient resources.
if( ref( $error ) ) { if( ref( $error ) ) {
...@@ -649,12 +683,63 @@ sub IsFeasible($$;$$$) ...@@ -649,12 +683,63 @@ sub IsFeasible($$;$$$)
if( ref( $conflictcount ) ) { if( ref( $conflictcount ) ) {
$$conflictcount = -$free; $$conflictcount = -$free;
} }
return 0; if( defined( $forecast ) ) {
$answer = 0;
} else {
return 0;
}
} }
} }
return 1; return $answer;
}
#
# Generate a heuristic "count" of free nodes of a given type. For each
# "free forever" physical node (i.e., a node never required for future
# reservations), the count is incremented by one. Each node currently
# assigned to an experiment is ignored (doesn't affect the count). Each
# node currently free but later required for a reservation increments the
# count by some fractional value depending how far into the future the
# reservation is.
#
# Reservation->FreeCount( $type, $projlist )
#
# $type must be a valid node type.
# $projlist is an optional reference to a list of PIDs, and reservations
# for any projects in the list will be ignored (i.e., the nodes are
# assumed free -- useful if a user wants to consider nodes reserved
# to their own projects as available).
sub FreeCount($$) {
my ($class, $type, $projlist) = @_;
my $reservations = LookupAll( $class, $type );
my @forecast = ();
my $t = time();
IsFeasible( $class, $reservations, undef, undef, undef, $projlist,
\@forecast );
my $free = $forecast[ 0 ]->{'free'};
my $answer = $free;
foreach my $f ( @forecast ) {
if( $f->{'free'} < $free ) {
my $deltat = $f->{'t'} - $t;
$deltat = 0 if( $deltat < 0 );
# Weight the nodes based on how far into the future the
# reservation is. The 0x10000 is chosen so that a node available
# for the next 24 hours only is worth about half a node available
$answer -= ( ( $free - $f->{'free'} ) * exp( -$deltat / 0x10000 ) );
$free = $f->{'free'};
}
}
return $answer;
} }
sub ExptTypes($) { sub ExptTypes($) {
......
...@@ -45,7 +45,9 @@ use Reservation; ...@@ -45,7 +45,9 @@ use Reservation;
sub usage() sub usage()
{ {
print STDERR "Usage: predict [-p] [-u] [-t time] type\n"; print STDERR "Usage: predict [-p] [-u] [-t time] type\n";
print STDERR " predict -c type [pid...]\n";
print STDERR " -h This message\n"; print STDERR " -h This message\n";
print STDERR " -c Give an oversimplified free node count\n";
print STDERR " -p Identify by pid only, not pid/eid\n"; print STDERR " -p Identify by pid only, not pid/eid\n";
print STDERR " -t Give time/date for prediction (defaults to now)\n"; print STDERR " -t Give time/date for prediction (defaults to now)\n";
print STDERR " -u Interpret/display all times in UTC\n"; print STDERR " -u Interpret/display all times in UTC\n";
...@@ -53,10 +55,11 @@ sub usage() ...@@ -53,10 +55,11 @@ sub usage()
exit( -1 ); exit( -1 );
} }
my $optlist = "dhpt:"; my $optlist = "cdhpt:";
my $debug = 0; my $debug = 0;
my $time = time; # default to now my $time = time; # default to now
my $pidonly = 0; my $pidonly = 0;
my $countonly = 0;
my $type; my $type;
sub fatal($) sub fatal($)
...@@ -110,12 +113,21 @@ if (defined($options{"t"})) { ...@@ -110,12 +113,21 @@ if (defined($options{"t"})) {
} }
} }
} }
usage() if( @ARGV != 1 ); if (defined($options{"c"})) {
$countonly = 1;
}
usage() if( $countonly ? @ARGV < 1 : @ARGV != 1 );
$type = shift( @ARGV ); $type = shift( @ARGV );
unless( $type =~ /^[-\w]+$/ ) { unless( $type =~ /^[-\w]+$/ ) {
fatal( "Invalid node type." ); fatal( "Invalid node type." );
} }
if( $countonly ) {
print( int( Reservation->FreeCount( $type, \@ARGV ) + 0.5 ) . "\n" );
exit( 0 );
}
my $reservations = Reservation->LookupAll( $type ); my $reservations = Reservation->LookupAll( $type );
my @timeline = (); my @timeline = ();
my $free = 0; my $free = 0;
...@@ -199,3 +211,5 @@ if( $free >= 0 ) { ...@@ -199,3 +211,5 @@ if( $free >= 0 ) {
print "Overbooked by " . -$free . " node"; print "Overbooked by " . -$free . " node";
print ( $free == -1 ? ".\n" : "s.\n" ); print ( $free == -1 ? ".\n" : "s.\n" );
} }
exit( 0 );
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