Commit 8372a03e authored by Dietmar Maurer's avatar Dietmar Maurer

implement new auto balloon algorithm

Moved code into PVE/AutoBalloon.pm, and added a regression tests in bin/test/balloontest.pl
parent 284188c6
...@@ -9,6 +9,9 @@ DEB=${PACKAGE}_${VERSION}-${PACKAGERELEASE}_all.deb ...@@ -9,6 +9,9 @@ DEB=${PACKAGE}_${VERSION}-${PACKAGERELEASE}_all.deb
all: ${SUBDIRS} all: ${SUBDIRS}
check:
${MAKE} -C bin/test check
%: %:
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done
...@@ -43,7 +46,7 @@ ${DEB} deb: ...@@ -43,7 +46,7 @@ ${DEB} deb:
lintian ${DEB} lintian ${DEB}
.PHONY: upload .PHONY: upload
upload: ${DEB} upload: ${DEB} check
./repoid.pl .git check ./repoid.pl .git check
umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw
mkdir -p /pve/${RELEASE}/extra mkdir -p /pve/${RELEASE}/extra
......
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 = \ ...@@ -11,6 +11,7 @@ PERLSOURCE = \
OpenVZ.pm \ OpenVZ.pm \
OpenVZMigrate.pm \ OpenVZMigrate.pm \
APLInfo.pm \ APLInfo.pm \
AutoBalloon.pm \
VZDump.pm VZDump.pm
all: pvecfg.pm ${SUBDIRS} all: pvecfg.pm ${SUBDIRS}
......
...@@ -16,6 +16,7 @@ use PVE::QemuServer; ...@@ -16,6 +16,7 @@ use PVE::QemuServer;
use PVE::OpenVZ; use PVE::OpenVZ;
use PVE::RPCEnvironment; use PVE::RPCEnvironment;
use PVE::API2::Subscription; use PVE::API2::Subscription;
use PVE::AutoBalloon;
$SIG{'__WARN__'} = sub { $SIG{'__WARN__'} = sub {
my $err = $@; my $err = $@;
...@@ -178,13 +179,14 @@ sub auto_balloning { ...@@ -178,13 +179,14 @@ sub auto_balloning {
my ($vmstatus) = @_; my ($vmstatus) = @_;
my $log = sub { my $log = sub {
return if !$opt_debug; return if !$opt_debug;
print @_; print @_;
}; };
my $hostmeminfo = PVE::ProcFSTools::read_meminfo(); my $hostmeminfo = PVE::ProcFSTools::read_meminfo();
# to debug, run 'pvestatd -d' and set memtotal here # 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}; my $hostfreemem = $hostmeminfo->{memtotal} - $hostmeminfo->{memused};
...@@ -192,115 +194,12 @@ sub auto_balloning { ...@@ -192,115 +194,12 @@ sub auto_balloning {
# goal: we want to change memory usage by this amount (positive or negative) # goal: we want to change memory usage by this amount (positive or negative)
my $goal = int($hostmeminfo->{memtotal}*0.8 - $hostmeminfo->{memused}); my $goal = int($hostmeminfo->{memtotal}*0.8 - $hostmeminfo->{memused});
&$log("host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
my $maxchange = 100*1024*1024; my $maxchange = 100*1024*1024;
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange);
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) { &$log("host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
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) { foreach my $vmid (keys %$vmstatus) {
next if !$res->{$vmid}; next if !$res->{$vmid};
my $d = $vmstatus->{$vmid}; my $d = $vmstatus->{$vmid};
my $diff = int($res->{$vmid} - $d->{balloon}); my $diff = int($res->{$vmid} - $d->{balloon});
......
...@@ -2,6 +2,9 @@ include ../../defines.mk ...@@ -2,6 +2,9 @@ include ../../defines.mk
all: all:
check:
./balloontest.pl
SCRIPTS = \ SCRIPTS = \
example1.pl \ example1.pl \
example2.pl example2.pl
......
#!/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;
};
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