Moved code into PVE/AutoBalloon.pm, and added a regression tests in bin/test/balloontest.pl
all: ${SUBDIRS}
+check:
+ ${MAKE} -C bin/test check
+
%:
set -e && for i in ${SUBDIRS}; do ${MAKE} -C $$i $@; done
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
--- /dev/null
+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;
OpenVZ.pm \
OpenVZMigrate.pm \
APLInfo.pm \
+ AutoBalloon.pm \
VZDump.pm
all: pvecfg.pm ${SUBDIRS}
use PVE::OpenVZ;
use PVE::RPCEnvironment;
use PVE::API2::Subscription;
+use PVE::AutoBalloon;
$SIG{'__WARN__'} = sub {
my $err = $@;
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};
# goal: we want to change memory usage by this amount (positive or negative)
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 $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
+ my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange);
- 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
- }
+ &$log("host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
- foreach my $vmid (@$idlist) {
+ foreach my $vmid (keys %$vmstatus) {
next if !$res->{$vmid};
my $d = $vmstatus->{$vmid};
my $diff = int($res->{$vmid} - $d->{balloon});
all:
+check:
+ ./balloontest.pl
+
SCRIPTS = \
example1.pl \
example2.pl
--- /dev/null
+#!/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;
+};