summaryrefslogtreecommitdiffstats
path: root/client/go/jvm/mem_avail.go
blob: 579b5aa804971ab1ca9eaacdbeb1b6339bdce033 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// Author: arnej

package jvm

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/vespa-engine/vespa/client/go/trace"
	"github.com/vespa-engine/vespa/client/go/util"
)

func parseFree(txt string) AmountOfMemory {
	f := strings.Fields(txt)
	for idx, field := range f {
		if field == "Mem:" && idx+1 < len(f) {
			res, err := strconv.Atoi(f[idx+1])
			if err == nil {
				return MegaBytesOfMemory(res)
			} else {
				trace.Warning(err)
			}
		}
	}
	return BytesOfMemory(0)
}

func parentDir(dir string) string {
	lastSlash := 0
	for idx, ch := range dir {
		if ch == '/' {
			lastSlash = idx
		}
	}
	return dir[:lastSlash]
}

func readLineFrom(filename string) (string, error) {
	content, err := os.ReadFile(filename)
	s := string(content)
	if err != nil {
		return s, err
	}
	s = strings.TrimSuffix(s, "\n")
	if strings.Contains(s, "\n") {
		return s, fmt.Errorf("unexpected multiple lines in file %s", filename)
	}
	return s, nil
}

func vespa_cg2get(limitname string) (output string, err error) {
	return vespa_cg2get_impl("", limitname)
}
func vespa_cg2get_impl(rootdir, limitname string) (output string, err error) {
	_, err = os.Stat(rootdir + "/sys/fs/cgroup/cgroup.controllers")
	if err != nil {
		trace.Trace("no cgroups:", err)
		return
	}
	cgroup_content, err := readLineFrom(rootdir + "/proc/self/cgroup")
	if err != nil {
		trace.Trace("no cgroup for self:", err)
		return
	}
	min_value := "max"
	path := rootdir + "/sys/fs/cgroup"
	slice := strings.TrimPrefix(cgroup_content, "0::")
	dirNames := strings.Split(slice, "/")
	for _, dirName := range dirNames {
		path = path + dirName + "/"
		value, err := readLineFrom(path + limitname)
		trace.Debug("read from", path+limitname, "=>", value)
		if err == nil {
			if value == "max" {
				// nop
			} else if min_value == "max" {
				min_value = value
			} else if len(value) < len(min_value) {
				min_value = value
			} else if len(value) == len(min_value) && value < min_value {
				min_value = value
			}
		}
	}
	trace.Trace("min_value of", limitname, "for cgroups v2:", min_value)
	return min_value, nil
}

func getAvailableMemory() AmountOfMemory {
	result := BytesOfMemory(0)
	backticks := util.BackTicksWithStderr
	freeOutput, err := backticks.Run("free", "-m")
	if err == nil {
		result = parseFree(freeOutput)
		trace.Trace("run 'free' ok, result:", result)
	} else {
		trace.Trace("run 'free' failed:", err)
	}
	available_cgroup := KiloBytesOfMemory(1 << 31)
	cggetOutput, err := backticks.Run("cgget", "-nv", "-r", "memory.limit_in_bytes", "/")
	if err != nil {
		cggetOutput, err = vespa_cg2get("memory.max")
	}
	cggetOutput = strings.TrimSpace(cggetOutput)
	if err != nil {
		trace.Debug("run 'cgget' failed:", err, "=>", cggetOutput)
	}
	if err == nil && cggetOutput != "max" {
		numBytes, err := strconv.ParseInt(cggetOutput, 10, 64)
		if err == nil && numBytes > (1<<28) {
			available_cgroup = BytesOfMemory(numBytes)
		} else {
			trace.Warning("unexpected 'cgget' output:", cggetOutput)
		}
	}
	if result.ToKB() == 0 || result.ToKB() > available_cgroup.ToKB() {
		result = available_cgroup
	}
	trace.Trace("getAvailableMemory returns:", result)
	return result
}