From 7d713866c5912646cd7eebbfa423673a2670e1ac Mon Sep 17 00:00:00 2001
From: Florent Gluck <florent.gluck@hesge.ch>
Date: Wed, 7 Sep 2022 16:49:42 +0200
Subject: [PATCH] Takes into account real RAM usage when estimating how much
 RAM is left. This is used when a VM is stated in order to know whether there
 is enough RAM left. We assume that KSM allows us to save ~30% or RAM.

---
 src/utils/memory.go | 56 ++++++++++++++++++++++++++++++++++++++++-----
 src/vms/vms.go      | 20 ++++++++--------
 2 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/utils/memory.go b/src/utils/memory.go
index 1a6e9ab..a75f0b2 100644
--- a/src/utils/memory.go
+++ b/src/utils/memory.go
@@ -1,17 +1,59 @@
 package utils
 
 import (
-	"syscall"
+	"os"
+	"bufio"
+	"strconv"
+	"strings"
 )
 
-var totalRAM int
+// This function returns info about the system's RAM
+// by reading /proc/meminfo.
+// Beware: it is Linux SPECIFIC!
+// Returns total and avaiable memory in MB.
+func GetRAM() (int, int, error) {
+	f, err := os.Open("/proc/meminfo")
+    if err != nil {
+		return 0, 0, err
+    }
+	defer f.Close()
 
-// Returns: total RAM, shared RAM, free RAM and possibly an error.
+    scan := bufio.NewScanner(f)
+    scan.Split(bufio.ScanLines)
+
+	var memTotal   int = -1
+	var memAvail   int = -1
+
+	const units = 1024
+
+    for scan.Scan() {
+		s := scan.Text()
+		fields := strings.Split(s, ":")
+		attr := fields[0]
+		vals := strings.Split(strings.TrimSpace(fields[1]), " ")
+		val := strings.TrimSpace(vals[0])
+		attr = strings.TrimSpace(strings.ToLower(attr))
+		switch attr {
+		case "memtotal":
+			memTotal, _ = strconv.Atoi(val)
+			memTotal = memTotal/units
+		case "memavailable":
+			memAvail, _ = strconv.Atoi(val)
+			memAvail = memAvail/units
+		}
+    }
+
+	return memTotal, memAvail, nil
+}
+
+/*
+// This function is not used because it DOES NOT return the available RAM!
+// Returns: total RAM, shared RAM, buffered RAM, free RAM and possibly an error.
 // RAM amounts are given in megabytes (MB).
-func GetRAM() (int, int, int, error) {
+func GetRAM() (int, int, int, int, error) {
 	in := &syscall.Sysinfo_t{}
 	if err := syscall.Sysinfo(in); err != nil {
-		return 0, 0, 0, err
+		return 0, 0, 0, 0, err
 	}
 
 	const MB = 1024*1024
@@ -19,6 +61,8 @@ func GetRAM() (int, int, int, error) {
 	// We always convert to uint64 to match signature.
 	totalRam :=  int(uint64(in.Totalram)*uint64(in.Unit)/MB)
 	sharedRam :=  int(uint64(in.Sharedram)*uint64(in.Unit)/MB)
+	bufferedRam := int(uint64(in.Bufferram)*uint64(in.Unit)/MB)
 	freeRam := int(uint64(in.Freeram)*uint64(in.Unit)/MB)
-	return totalRam, sharedRam, freeRam, nil
+	return totalRam, sharedRam, bufferedRam, freeRam, nil
 }
+*/
\ No newline at end of file
diff --git a/src/vms/vms.go b/src/vms/vms.go
index 0e5277e..3112bc5 100644
--- a/src/vms/vms.go
+++ b/src/vms/vms.go
@@ -2,6 +2,7 @@ package vms
 
 import (
 	"os"
+	"math"
 	"sort"
 	"sync"
 	"errors"
@@ -29,7 +30,6 @@ type (
 
 var log = logger.GetInstance()
 var vms *VMs
-var totalRAM int
 
 // Returns a VMs "singleton".
 // IMPORTANT: the InitVMs function must have been previously called!
@@ -40,13 +40,6 @@ func GetVMsInstance() *VMs {
 // Creates all VMs from their files on disk.
 // NOTE: path is the root directory where VMs reside.
 func InitVMs() error {
-	// Retrieves the total amount of RAM (in MB).
-	ram, _, _, err := utils.GetRAM()
-	if err != nil {
-		return errors.New("Failed obtaining memory info: "+err.Error())
-	}
-	totalRAM = ram
-
 	vmsDir := paths.GetInstance().VMsDir
 	vms = &VMs { m: make(map[string]VM), dir: vmsDir, rwlock: new(sync.RWMutex), usedRAM: 0 }
 
@@ -179,10 +172,17 @@ func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) {
 		return 0, "", err
 	}
 
-	// Check there will be at least 1GB of RAM left after the VM has started,
+	// Check there will be at least 2GB of RAM left after the VM has started,
 	// otherwise, we prevent the execution of the VM to avoid RAM saturation.
+	// Furthermore, given Linux's KSVM technology allows page sharing, we
+	// assume it's able to save 30% of RAM (should be a conservative estimate).
+	_, availRAM, err := utils.GetRAM()
+	if err != nil {
+		return -1, "", errors.New("Failed obtaining memory info: "+err.Error())
+	}
+
 	vms.usedRAM += vm.Ram
-	if totalRAM - vms.usedRAM < 1024 {
+	if availRAM - int(math.Round(float64(vms.usedRAM)*0.7)) < 2048 {
 		vms.usedRAM -= vm.Ram
 		return -1, "", errors.New("Insufficient free RAM to start VM")
 	}
-- 
GitLab