1 package fs2
2
3 import (
4 "bufio"
5 "errors"
6 "math"
7 "os"
8 "strconv"
9 "strings"
10
11 "golang.org/x/sys/unix"
12
13 "github.com/opencontainers/runc/libcontainer/cgroups"
14 "github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
15 "github.com/opencontainers/runc/libcontainer/configs"
16 )
17
18
19
20
21
22 func numToStr(value int64) (ret string) {
23 switch {
24 case value == 0:
25 ret = ""
26 case value == -1:
27 ret = "max"
28 default:
29 ret = strconv.FormatInt(value, 10)
30 }
31
32 return ret
33 }
34
35 func isMemorySet(r *configs.Resources) bool {
36 return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
37 }
38
39 func setMemory(dirPath string, r *configs.Resources) error {
40 if !isMemorySet(r) {
41 return nil
42 }
43 swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
44 if err != nil {
45 return err
46 }
47 swapStr := numToStr(swap)
48 if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
49
50 swapStr = "0"
51 }
52
53 if swapStr != "" {
54 if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
55 return err
56 }
57 }
58
59 if val := numToStr(r.Memory); val != "" {
60 if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
61 return err
62 }
63 }
64
65
66
67 if val := numToStr(r.MemoryReservation); val != "" {
68 if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
69 return err
70 }
71 }
72
73 return nil
74 }
75
76 func statMemory(dirPath string, stats *cgroups.Stats) error {
77 const file = "memory.stat"
78 statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
79 if err != nil {
80 return err
81 }
82 defer statsFile.Close()
83
84 sc := bufio.NewScanner(statsFile)
85 for sc.Scan() {
86 t, v, err := fscommon.ParseKeyValue(sc.Text())
87 if err != nil {
88 return &parseError{Path: dirPath, File: file, Err: err}
89 }
90 stats.MemoryStats.Stats[t] = v
91 }
92 if err := sc.Err(); err != nil {
93 return &parseError{Path: dirPath, File: file, Err: err}
94 }
95 stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
96
97
98 stats.MemoryStats.UseHierarchy = true
99
100 memoryUsage, err := getMemoryDataV2(dirPath, "")
101 if err != nil {
102 if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
103
104
105
106 return rootStatsFromMeminfo(stats)
107 }
108 return err
109 }
110 stats.MemoryStats.Usage = memoryUsage
111 swapOnlyUsage, err := getMemoryDataV2(dirPath, "swap")
112 if err != nil {
113 return err
114 }
115 stats.MemoryStats.SwapOnlyUsage = swapOnlyUsage
116 swapUsage := swapOnlyUsage
117
118
119
120 swapUsage.Usage += memoryUsage.Usage
121 if swapUsage.Limit != math.MaxUint64 {
122 swapUsage.Limit += memoryUsage.Limit
123 }
124
125
126 swapUsage.MaxUsage = 0
127 stats.MemoryStats.SwapUsage = swapUsage
128
129 return nil
130 }
131
132 func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
133 memoryData := cgroups.MemoryData{}
134
135 moduleName := "memory"
136 if name != "" {
137 moduleName = "memory." + name
138 }
139 usage := moduleName + ".current"
140 limit := moduleName + ".max"
141 maxUsage := moduleName + ".peak"
142
143 value, err := fscommon.GetCgroupParamUint(path, usage)
144 if err != nil {
145 if name != "" && os.IsNotExist(err) {
146
147
148
149 return cgroups.MemoryData{}, nil
150 }
151 return cgroups.MemoryData{}, err
152 }
153 memoryData.Usage = value
154
155 value, err = fscommon.GetCgroupParamUint(path, limit)
156 if err != nil {
157 return cgroups.MemoryData{}, err
158 }
159 memoryData.Limit = value
160
161
162
163 value, err = fscommon.GetCgroupParamUint(path, maxUsage)
164 if err != nil && !os.IsNotExist(err) {
165 return cgroups.MemoryData{}, err
166 }
167 memoryData.MaxUsage = value
168
169 return memoryData, nil
170 }
171
172 func rootStatsFromMeminfo(stats *cgroups.Stats) error {
173 const file = "/proc/meminfo"
174 f, err := os.Open(file)
175 if err != nil {
176 return err
177 }
178 defer f.Close()
179
180
181 var (
182 swap_free uint64
183 swap_total uint64
184 )
185 mem := map[string]*uint64{
186 "SwapFree": &swap_free,
187 "SwapTotal": &swap_total,
188 }
189
190 found := 0
191 sc := bufio.NewScanner(f)
192 for sc.Scan() {
193 parts := strings.SplitN(sc.Text(), ":", 3)
194 if len(parts) != 2 {
195
196 continue
197 }
198 k := parts[0]
199 p, ok := mem[k]
200 if !ok {
201
202 continue
203 }
204 vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
205 *p, err = strconv.ParseUint(vStr, 10, 64)
206 if err != nil {
207 return &parseError{File: file, Err: errors.New("bad value for " + k)}
208 }
209
210 found++
211 if found == len(mem) {
212
213 break
214 }
215 }
216 if err := sc.Err(); err != nil {
217 return &parseError{Path: "", File: file, Err: err}
218 }
219
220
221
222
223
224
225
226
227 stats.MemoryStats.Usage.Usage = stats.MemoryStats.Stats["anon"] + stats.MemoryStats.Stats["file"]
228 stats.MemoryStats.Usage.Limit = math.MaxUint64
229 stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
230 stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
231 stats.MemoryStats.SwapUsage.Usage += stats.MemoryStats.Usage.Usage
232
233 return nil
234 }
235
View as plain text