From 53f13052edbdbbb167799d87eeaf0f744a3c2e78 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 28 Dec 2012 12:50:13 +0100 Subject: [PATCH] implement new auto balloon algorithm Moved code into PVE/AutoBalloon.pm, and added a regression tests in bin/test/balloontest.pl --- Makefile | 5 +- PVE/AutoBalloon.pm | 146 +++++++++++++++++++++++++++++++ PVE/Makefile | 1 + bin/pvestatd | 117 ++----------------------- bin/test/Makefile | 3 + bin/test/balloontest.pl | 185 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 347 insertions(+), 110 deletions(-) create mode 100644 PVE/AutoBalloon.pm create mode 100755 bin/test/balloontest.pl diff --git a/Makefile b/Makefile index 96b18856..6754736f 100644 --- a/Makefile +++ b/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 diff --git a/PVE/AutoBalloon.pm b/PVE/AutoBalloon.pm new file mode 100644 index 00000000..edf7332d --- /dev/null +++ b/PVE/AutoBalloon.pm @@ -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; diff --git a/PVE/Makefile b/PVE/Makefile index bb3b3c72..6e9e8a27 100644 --- a/PVE/Makefile +++ b/PVE/Makefile @@ -11,6 +11,7 @@ PERLSOURCE = \ OpenVZ.pm \ OpenVZMigrate.pm \ APLInfo.pm \ + AutoBalloon.pm \ VZDump.pm all: pvecfg.pm ${SUBDIRS} diff --git a/bin/pvestatd b/bin/pvestatd index 9976a55c..d9866060 100755 --- a/bin/pvestatd +++ b/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}); - &$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}); diff --git a/bin/test/Makefile b/bin/test/Makefile index a8933352..91fe61a4 100644 --- a/bin/test/Makefile +++ b/bin/test/Makefile @@ -2,6 +2,9 @@ include ../../defines.mk all: +check: + ./balloontest.pl + SCRIPTS = \ example1.pl \ example2.pl diff --git a/bin/test/balloontest.pl b/bin/test/balloontest.pl new file mode 100755 index 00000000..5ebf7e20 --- /dev/null +++ b/bin/test/balloontest.pl @@ -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; +}; -- 2.39.2