]> git.proxmox.com Git - pve-manager.git/commitdiff
implement new auto balloon algorithm
authorDietmar Maurer <dietmar@proxmox.com>
Fri, 28 Dec 2012 11:50:13 +0000 (12:50 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 28 Dec 2012 11:50:13 +0000 (12:50 +0100)
Moved code into PVE/AutoBalloon.pm, and added a regression tests in bin/test/balloontest.pl

Makefile
PVE/AutoBalloon.pm [new file with mode: 0644]
PVE/Makefile
bin/pvestatd
bin/test/Makefile
bin/test/balloontest.pl [new file with mode: 0755]

index 96b18856dba41de5630f11c32abed31eb8f84d95..6754736f3cc7045cc85e3173d6ce5e1fea97fe02 100644 (file)
--- 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 (file)
index 0000000..edf7332
--- /dev/null
@@ -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;
index bb3b3c726f4596fea51d8b14bc29ffacb3dc1044..6e9e8a27cd0feacd913c64b8b234f90e44cd15e7 100644 (file)
@@ -11,6 +11,7 @@ PERLSOURCE =                  \
        OpenVZ.pm               \
        OpenVZMigrate.pm        \
        APLInfo.pm              \
+       AutoBalloon.pm          \
        VZDump.pm
 
 all: pvecfg.pm ${SUBDIRS}
index 9976a55cc47095b439544f0ad561e8f1a02a67e0..d98660609f8de22de0846c52613e0e31fc27290d 100755 (executable)
@@ -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});
index a8933352f4aabfcc9c3c6cf8898300f9aedb4192..91fe61a4129b098565787718986aa85734846a2f 100644 (file)
@@ -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 (executable)
index 0000000..5ebf7e2
--- /dev/null
@@ -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;
+};