From ad436757c3162ee62c0c794d7db0af5ccf4d134c Mon Sep 17 00:00:00 2001
From: Aliaksandr Valialkin <valyala@gmail.com>
Date: Fri, 28 Jun 2019 15:34:24 +0300
Subject: [PATCH] lib/memory: an attempt to read proper memory limit inside LXC
 container

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/84
---
 lib/memory/memory_linux.go      | 48 +++++++++++++++++++++++++++------
 lib/memory/memory_linux_test.go | 19 +++++++++++++
 2 files changed, 59 insertions(+), 8 deletions(-)
 create mode 100644 lib/memory/memory_linux_test.go

diff --git a/lib/memory/memory_linux.go b/lib/memory/memory_linux.go
index 4dc3510fb3..5e75eb33d0 100644
--- a/lib/memory/memory_linux.go
+++ b/lib/memory/memory_linux.go
@@ -2,6 +2,7 @@ package memory
 
 import (
 	"io/ioutil"
+	"os/exec"
 	"strconv"
 	"syscall"
 
@@ -19,17 +20,48 @@ func sysTotalMemory() int {
 	// See https://stackoverflow.com/questions/42187085/check-mem-limit-within-a-docker-container .
 	data, err := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes")
 	if err != nil {
-		return totalMem
+		// Try determining the amount of memory inside lxc container.
+		mem, err := readLXCMemoryLimit(totalMem)
+		if err != nil {
+			return totalMem
+		}
+		return mem
 	}
-	for len(data) > 0 && data[len(data)-1] == '\n' {
-		data = data[:len(data)-1]
-	}
-	mem, err := strconv.Atoi(string(data))
+	mem, err := readPositiveInt(data, totalMem)
 	if err != nil {
 		return totalMem
 	}
-	if mem > totalMem {
-		mem = totalMem
-	}
 	return mem
 }
+
+func readLXCMemoryLimit(totalMem int) (int, error) {
+	// Read memory limit according to https://unix.stackexchange.com/questions/242718/how-to-find-out-how-much-memory-lxc-container-is-allowed-to-consume
+	// This should properly determine the limit inside lxc container.
+	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/84
+	cmd := exec.Command("/bin/sh", "-c",
+		`cat $(mount | grep cgroup | grep memory | cut -d" " -f3)$(cat /proc/self/cgroup | grep memory | cut -d: -f3)/memory.limit_in_bytes`)
+	data, err := cmd.Output()
+	if err != nil {
+		return 0, err
+	}
+	return readPositiveInt(data, totalMem)
+}
+
+func readPositiveInt(data []byte, maxN int) (int, error) {
+	for len(data) > 0 && data[len(data)-1] == '\n' {
+		data = data[:len(data)-1]
+	}
+	n, err := strconv.ParseUint(string(data), 10, 64)
+	if err != nil {
+		return 0, err
+	}
+	if int64(n) < 0 || int64(int(n)) != int64(n) {
+		// Int overflow.
+		return maxN, nil
+	}
+	ni := int(n)
+	if ni > maxN {
+		return maxN, nil
+	}
+	return ni, nil
+}
diff --git a/lib/memory/memory_linux_test.go b/lib/memory/memory_linux_test.go
new file mode 100644
index 0000000000..b74bb98589
--- /dev/null
+++ b/lib/memory/memory_linux_test.go
@@ -0,0 +1,19 @@
+package memory
+
+import (
+	"testing"
+)
+
+func TestReadLXCMemoryLimit(t *testing.T) {
+	const maxMem = 1<<31 - 1
+	n, err := readLXCMemoryLimit(maxMem)
+	if err != nil {
+		t.Fatalf("unexpected error in readLXCMemoryLimit: %s", err)
+	}
+	if n < 0 {
+		t.Fatalf("n must be positive; got %d", n)
+	}
+	if n > maxMem {
+		t.Fatalf("n must be smaller than maxMem=%d; got %d", maxMem, n)
+	}
+}