]> git.proxmox.com Git - pve-manager.git/blobdiff - bin/pvestatd
implement auto_balloning
[pve-manager.git] / bin / pvestatd
index 5847c10549c303002166ac70e734f161613934a0..9976a55cc47095b439544f0ad561e8f1a02a67e0 100755 (executable)
@@ -174,12 +174,157 @@ sub update_node_status {
     PVE::Cluster::broadcast_rrd("pve2-node/$nodename", $data);
 }
 
+sub auto_balloning {
+    my ($vmstatus) =  @_;
+
+    my $log = sub {
+       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
+
+    my $hostfreemem = $hostmeminfo->{memtotal} - $hostmeminfo->{memused};
+
+    # we try to use about 80% host memory
+    # 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
+    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});
+       my $absdiff = $diff < 0 ? -$diff : $diff;
+       if ($absdiff > 0) {
+           &$log("BALLOON $vmid to $res->{$vmid} ($diff)\n");
+           eval {
+               PVE::QemuServer::vm_mon_cmd($vmid, "balloon", 
+                                           value => int($res->{$vmid}));
+           };
+           warn $@ if $@;
+       }
+    }
+}
+
 sub update_qemu_status {
 
     my $ctime = time();
 
     my $vmstatus = PVE::QemuServer::vmstatus(undef, 1);
 
+    eval { auto_balloning($vmstatus); };
+    syslog('err', "auto ballooning error: $@") if $@;
+
     foreach my $vmid (keys %$vmstatus) {
        my $d = $vmstatus->{$vmid};
        my $data;