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
}
|