implement new auto balloon algorithm
Moved code into PVE/AutoBalloon.pm, and added a regression tests in bin/test/balloontest.pl
This commit is contained in:
parent
284188c650
commit
8372a03e8d
5
Makefile
5
Makefile
@ -9,6 +9,9 @@ DEB=${PACKAGE}_${VERSION}-${PACKAGERELEASE}_all.deb
|
||||
|
||||
all: ${SUBDIRS}
|
||||
|
||||
check:
|
||||
${MAKE} -C bin/test check
|
||||
|
||||
%:
|
||||
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done
|
||||
|
||||
@ -43,7 +46,7 @@ ${DEB} deb:
|
||||
lintian ${DEB}
|
||||
|
||||
.PHONY: upload
|
||||
upload: ${DEB}
|
||||
upload: ${DEB} check
|
||||
./repoid.pl .git check
|
||||
umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw
|
||||
mkdir -p /pve/${RELEASE}/extra
|
||||
|
146
PVE/AutoBalloon.pm
Normal file
146
PVE/AutoBalloon.pm
Normal file
@ -0,0 +1,146 @@
|
||||
package PVE::AutoBalloon;
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
sub compute_alg1 {
|
||||
my ($vmstatus, $goal, $maxchange, $debug) = @_;
|
||||
|
||||
my $log = sub { print @_ if $debug; };
|
||||
|
||||
my $change_func = sub {
|
||||
my ($res, $idlist, $bytes) = @_;
|
||||
|
||||
my $rest = $bytes;
|
||||
my $repeat = 1;
|
||||
my $done_hash = {};
|
||||
my $progress = 1;
|
||||
|
||||
while ($rest && $repeat && $progress) {
|
||||
$repeat = 0;
|
||||
$progress = 0;
|
||||
|
||||
my $shares_total = 0;
|
||||
my $alloc_old = 0;
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $balloon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$alloc_old += $balloon - $d->{balloon_min};
|
||||
$shares_total += $d->{shares} || 1000;
|
||||
}
|
||||
|
||||
my $changes = 0;
|
||||
|
||||
my $alloc_new = $alloc_old + $rest;
|
||||
|
||||
&$log("shares_total: $shares_total $alloc_new\n");
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $shares = $d->{shares} || 1000;
|
||||
my $desired = $d->{balloon_min} + int(($alloc_new/$shares_total)*$shares);
|
||||
|
||||
if ($desired > $d->{maxmem}) {
|
||||
$desired = $d->{maxmem};
|
||||
$repeat = 1;
|
||||
} elsif ($desired < $d->{balloon_min}) {
|
||||
$desired = $d->{balloon_min};
|
||||
$repeat = 1;
|
||||
}
|
||||
|
||||
my ($new, $balloon);
|
||||
if (($bytes > 0) && ($desired - $d->{balloon}) > 0) { # grow
|
||||
$new = $d->{balloon} + $maxchange;
|
||||
$balloon = $new > $desired ? $desired : $new;
|
||||
} elsif (($desired - $d->{balloon}) < 0) { # shrink
|
||||
$new = $d->{balloon} - $maxchange;
|
||||
$balloon = $new > $desired ? $new : $desired;
|
||||
} else {
|
||||
$done_hash->{$vmid} = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
my $diff = $balloon - $d->{balloon};
|
||||
if ($diff != 0) {
|
||||
my $oldballoon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$res->{$vmid} = $balloon;
|
||||
my $change = $balloon - $oldballoon;
|
||||
if ($change != 0) {
|
||||
$changes += $change;
|
||||
my $absdiff = $diff > 0 ? $diff : -$diff;
|
||||
$progress += $absdiff;
|
||||
$repeat = 1;
|
||||
}
|
||||
&$log("change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n");
|
||||
}
|
||||
}
|
||||
|
||||
$rest -= $changes;
|
||||
}
|
||||
|
||||
return $rest;
|
||||
};
|
||||
|
||||
|
||||
my $idlist = []; # list of VMs with working balloon river
|
||||
my $idlist1 = []; # list of VMs with memory pressure
|
||||
my $idlist2 = []; # list of VMs with enough free memory
|
||||
|
||||
foreach my $vmid (keys %$vmstatus) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
next if !$d->{balloon}; # skip if balloon driver not running
|
||||
next if !$d->{balloon_min}; # skip if balloon value not set in config
|
||||
|
||||
push @$idlist, $vmid;
|
||||
|
||||
if ($d->{freemem} &&
|
||||
($d->{freemem} > $d->{balloon_min}*0.25) &&
|
||||
($d->{balloon} >= $d->{balloon_min})) {
|
||||
push @$idlist2, $vmid;
|
||||
&$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
} else {
|
||||
push @$idlist1, $vmid;
|
||||
&$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
}
|
||||
}
|
||||
|
||||
my $res = {};
|
||||
|
||||
if ($goal > 10*1024*1024) {
|
||||
&$log("grow request start $goal\n");
|
||||
# priorize VMs with memory pressure
|
||||
my $rest = &$change_func($res, $idlist1, $goal);
|
||||
if ($rest >= $goal) { # no progress ==> consider all VMs
|
||||
&$log("grow request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("grow request end $rest\n");
|
||||
|
||||
} elsif ($goal < -10*1024*1024) {
|
||||
&$log("shrink request $goal\n");
|
||||
# priorize VMs with enough free memory
|
||||
my $rest = &$change_func($res, $idlist2, $goal);
|
||||
if ($rest <= $goal) { # no progress ==> consider all VMs
|
||||
&$log("shrink request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("shrink request end $rest\n");
|
||||
} else {
|
||||
&$log("do nothing\n");
|
||||
# do nothing - requested change to small
|
||||
}
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if !$res->{$vmid};
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $diff = int($res->{$vmid} - $d->{balloon});
|
||||
my $absdiff = $diff < 0 ? -$diff : $diff;
|
||||
&$log("BALLOON $vmid to $res->{$vmid} ($diff)\n");
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
@ -11,6 +11,7 @@ PERLSOURCE = \
|
||||
OpenVZ.pm \
|
||||
OpenVZMigrate.pm \
|
||||
APLInfo.pm \
|
||||
AutoBalloon.pm \
|
||||
VZDump.pm
|
||||
|
||||
all: pvecfg.pm ${SUBDIRS}
|
||||
|
117
bin/pvestatd
117
bin/pvestatd
@ -16,6 +16,7 @@ use PVE::QemuServer;
|
||||
use PVE::OpenVZ;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::API2::Subscription;
|
||||
use PVE::AutoBalloon;
|
||||
|
||||
$SIG{'__WARN__'} = sub {
|
||||
my $err = $@;
|
||||
@ -178,13 +179,14 @@ sub auto_balloning {
|
||||
my ($vmstatus) = @_;
|
||||
|
||||
my $log = sub {
|
||||
return if !$opt_debug;
|
||||
print @_;
|
||||
return if !$opt_debug;
|
||||
print @_;
|
||||
};
|
||||
|
||||
my $hostmeminfo = PVE::ProcFSTools::read_meminfo();
|
||||
|
||||
# to debug, run 'pvestatd -d' and set memtotal here
|
||||
#$hostmeminfo->{memtotal} = int(3*1024*1024*1024/0.8); # you can set this to test
|
||||
#$hostmeminfo->{memtotal} = int(2*1024*1024*1024/0.8); # you can set this to test
|
||||
|
||||
my $hostfreemem = $hostmeminfo->{memtotal} - $hostmeminfo->{memused};
|
||||
|
||||
@ -192,115 +194,12 @@ sub auto_balloning {
|
||||
# goal: we want to change memory usage by this amount (positive or negative)
|
||||
my $goal = int($hostmeminfo->{memtotal}*0.8 - $hostmeminfo->{memused});
|
||||
|
||||
my $maxchange = 100*1024*1024;
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange);
|
||||
|
||||
&$log("host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
|
||||
|
||||
my $maxchange = 100*1024*1024;
|
||||
|
||||
my $get_summary = sub {
|
||||
my ($idlist) = @_;
|
||||
|
||||
my $shares = 0;
|
||||
my $freeshares = 0;
|
||||
my $alloc = 0;
|
||||
my $free = 0;
|
||||
foreach my $vmid (@$idlist) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
$shares += $d->{shares} || 1000;
|
||||
$freeshares += 1/($d->{shares} || 1000);
|
||||
if ($d->{balloon} > $d->{balloon_min}) { # just to be sure
|
||||
$alloc += $d->{balloon} - $d->{balloon_min}
|
||||
}
|
||||
if ($d->{maxmem} > $d->{balloon}) { # just to be sure
|
||||
$free += $d->{maxmem} - $d->{balloon};
|
||||
}
|
||||
}
|
||||
return ($shares, $freeshares, $alloc, $free);
|
||||
};
|
||||
|
||||
my $grow_func = sub {
|
||||
my ($res, $idlist, $bytes) = @_;
|
||||
|
||||
my $changes = 0;
|
||||
my (undef, $shares_total, undef, $free_total) = &$get_summary($idlist);
|
||||
return $changes if !$shares_total;
|
||||
|
||||
&$log("grow $goal\n");
|
||||
|
||||
my $target = $bytes < $free_total ? $free_total - $bytes : 0;
|
||||
&$log("shares_total: $shares_total\n");
|
||||
&$log("free_total: $free_total\n");
|
||||
&$log("target: $target\n");
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $shares = 1/($d->{shares} || 1000);
|
||||
&$log("shares $vmid: $shares\n");
|
||||
next if $shares < 0; # just to be sure
|
||||
my $max = $d->{maxmem} - int(($target/$shares_total)*$shares);
|
||||
$max = $d->{balloon_min} if $max < $d->{balloon_min};
|
||||
my $new = $d->{balloon} + $maxchange;
|
||||
my $balloon = $new > $max ? $max : $new;
|
||||
my $diff = $balloon - $d->{balloon};
|
||||
if ($diff > 0) {
|
||||
$res->{$vmid} = $balloon;
|
||||
$changes += $diff;
|
||||
&$log("grow request for $vmid ($res->{$vmid}, $diff, $max, $new)\n");
|
||||
}
|
||||
}
|
||||
return $changes;
|
||||
};
|
||||
|
||||
my $idlist = []; # list of VMs with working balloon river
|
||||
my $idlist1 = []; # list of VMs with memory pressure
|
||||
my $idlist2 = []; # list of VMs with enough free memory
|
||||
|
||||
foreach my $vmid (keys %$vmstatus) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
next if !$d->{balloon}; # skip if balloon driver not running
|
||||
next if !$d->{balloon_min}; # skip if balloon value not set in config
|
||||
|
||||
push @$idlist, $vmid;
|
||||
|
||||
if (($goal > 0) && $d->{freemem} &&
|
||||
($d->{freemem} > $d->{maxmem}*0.25) &&
|
||||
($d->{balloon} >= $d->{balloon_min})) {
|
||||
push @$idlist2, $vmid;
|
||||
&$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
} else {
|
||||
push @$idlist1, $vmid;
|
||||
&$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
}
|
||||
}
|
||||
|
||||
my $res = {};
|
||||
|
||||
if ($goal > 10*1024*1024) {
|
||||
&$log("grow request $goal\n");
|
||||
# we priorize VMs with memory pressure
|
||||
if (!&$grow_func($res, $idlist1, $goal)) {
|
||||
&$grow_func($res, $idlist2, $goal);
|
||||
}
|
||||
} elsif ($goal < -10*1024*1024) {
|
||||
&$log("shrink request $goal\n");
|
||||
my ($shares_total, undef, $alloc_old) = &$get_summary($idlist);
|
||||
my $alloc_new = $alloc_old + $goal;
|
||||
$alloc_new = 0 if $alloc_new < 0;
|
||||
&$log("shares_total: $shares_total $alloc_new\n");
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $shares = $d->{shares} || 1000;
|
||||
next if $shares < 0; # just to be sure
|
||||
my $min = $d->{balloon_min} + int(($alloc_new/$shares_total)*$shares);
|
||||
my $new = $d->{balloon} - $maxchange;
|
||||
$res->{$vmid} = $new > $min ? $new : $min;
|
||||
}
|
||||
} else {
|
||||
&$log("do nothing\n");
|
||||
# do nothing - requested change to small
|
||||
}
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if !$res->{$vmid};
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $diff = int($res->{$vmid} - $d->{balloon});
|
||||
|
@ -2,6 +2,9 @@ include ../../defines.mk
|
||||
|
||||
all:
|
||||
|
||||
check:
|
||||
./balloontest.pl
|
||||
|
||||
SCRIPTS = \
|
||||
example1.pl \
|
||||
example2.pl
|
||||
|
185
bin/test/balloontest.pl
Executable file
185
bin/test/balloontest.pl
Executable file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use lib qw(../../);
|
||||
use strict;
|
||||
use Storable qw(dclone);
|
||||
use Data::Dumper;
|
||||
use PVE::AutoBalloon;
|
||||
|
||||
my $debug = 0;
|
||||
|
||||
my $test_status1 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
};
|
||||
|
||||
abtest($test_status1, 0);
|
||||
abtest($test_status1, MB(90), 100 => MB(1060), 101 => MB(1030));
|
||||
abtest($test_status1, MB(150), 100 => MB(1100), 101 => MB(1050));
|
||||
abtest($test_status1, MB(270), 100 => MB(1100), 101 => MB(1090));
|
||||
absim($test_status1, MB(180), 100 => MB(1120), 101 => MB(1060));
|
||||
absim($test_status1, MB(270), 100 => MB(1180), 101 => MB(1090));
|
||||
absim($test_status1, MB(600), 100 => MB(1300), 101 => MB(1300));
|
||||
absim($test_status1, MB(900), 100 => MB(1600), 101 => MB(1300));
|
||||
|
||||
my $test_status2 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
};
|
||||
|
||||
abtest($test_status2, 0);
|
||||
abtest($test_status2, MB(18), 101 => MB(1018));
|
||||
abtest($test_status2, MB(500), 101 => MB(1100));
|
||||
|
||||
my $test_status3 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1)+MB(7),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
102 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(512),
|
||||
},
|
||||
};
|
||||
|
||||
abtest($test_status3, 0);
|
||||
abtest($test_status3, MB(11), 101 => MB(1018));
|
||||
abtest($test_status3, MB(80), 101 => MB(1087));
|
||||
abtest($test_status3, MB(200), 101 => MB(1107));
|
||||
|
||||
my $status = absim($test_status3, MB(593), 101 => MB(1300), 102 => MB(1300));
|
||||
absim($status, -MB(200), 101 => MB(1200), 102 => MB(1200));
|
||||
absim($status, -MB(400), 101 => MB(1200), 102 => GB(1));
|
||||
absim($status, -MB(593), 101 => MB(1007), 102 => GB(1));
|
||||
exit (0);
|
||||
|
||||
sub abapply {
|
||||
my ($vmstatus, $res, $sum) = @_;
|
||||
|
||||
my $changes = 0;
|
||||
my $abschanges = 0;
|
||||
foreach my $vmid (keys %$res) {
|
||||
my $diff = $res->{$vmid} - $vmstatus->{$vmid}->{balloon};
|
||||
if ($diff != 0) {
|
||||
# fixme: adjust freemem ?
|
||||
$vmstatus->{$vmid}->{freemem} += $diff;
|
||||
$vmstatus->{$vmid}->{freemem} = 0 if $vmstatus->{$vmid}->{freemem} < 0;
|
||||
$vmstatus->{$vmid}->{balloon} = $res->{$vmid};
|
||||
$sum->{$vmid} = $res->{$vmid};
|
||||
$changes += $diff;
|
||||
$abschanges += $diff > 0 ? $diff : -$diff;
|
||||
}
|
||||
}
|
||||
|
||||
return ($changes, $abschanges);
|
||||
}
|
||||
|
||||
my $tcount = 0;
|
||||
sub absim {
|
||||
my ($vmstatus, $goal, %expect) = @_;
|
||||
|
||||
$tcount++;
|
||||
|
||||
print "BALLOON SIM $tcount\n" if $debug;
|
||||
|
||||
$vmstatus = dclone($vmstatus); # do not change original
|
||||
|
||||
my $changes = 0;
|
||||
my $abschanges = 0;
|
||||
my $sum = {};
|
||||
do {
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug);
|
||||
print Dumper($res) if $debug;
|
||||
($changes, $abschanges) = abapply($vmstatus, $res, $sum);
|
||||
$goal -= $changes;
|
||||
} while ($abschanges);
|
||||
|
||||
abcheck($sum, %expect);
|
||||
|
||||
print "BALLOON SIM END\n" if $debug;
|
||||
print Dumper($vmstatus) if $debug;
|
||||
|
||||
return $vmstatus;
|
||||
}
|
||||
|
||||
sub abcheck {
|
||||
my ($res, %expect) = @_;
|
||||
|
||||
foreach my $vmid (keys %expect) {
|
||||
my $ev = $expect{$vmid};
|
||||
if (defined ($res->{$vmid})) {
|
||||
die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n"
|
||||
if $ev != $res->{$vmid};
|
||||
} else {
|
||||
die "T$tcount: missing value for VM $vmid (extected $ev)\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $vmid (keys %$res) {
|
||||
die "T$tcount: got unexpected result for $vmid\n"
|
||||
if (defined($res->{$vmid}) &&
|
||||
!defined($expect{$vmid}));
|
||||
}
|
||||
}
|
||||
|
||||
sub abtest {
|
||||
my ($vmstatus, $goal, %expect) = @_;
|
||||
|
||||
$tcount++;
|
||||
|
||||
print "BALLOON TEST $tcount\n" if $debug;
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug);
|
||||
print Dumper($res) if $debug;
|
||||
|
||||
abcheck($res, %expect);
|
||||
|
||||
print "\n\n" if $debug;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub MB {
|
||||
my $mb = shift;
|
||||
return $mb*1000*1000;
|
||||
};
|
||||
sub GB {
|
||||
my $gb = shift;
|
||||
return $gb*1000*1000*1000;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user