1
16
17 package v2
18
19 import (
20 "bufio"
21 "fmt"
22 "io"
23 "math"
24 "os"
25 "path/filepath"
26 "strconv"
27 "strings"
28 "time"
29
30 "github.com/containerd/cgroups/v2/stats"
31
32 "github.com/godbus/dbus/v5"
33 "github.com/opencontainers/runtime-spec/specs-go"
34 "github.com/sirupsen/logrus"
35 )
36
37 const (
38 cgroupProcs = "cgroup.procs"
39 cgroupThreads = "cgroup.threads"
40 defaultDirPerm = 0755
41 )
42
43
44
45
46
47
48 var defaultFilePerm = os.FileMode(0)
49
50
51
52 func remove(path string) error {
53 var err error
54 delay := 10 * time.Millisecond
55 for i := 0; i < 5; i++ {
56 if i != 0 {
57 time.Sleep(delay)
58 delay *= 2
59 }
60 if err = os.RemoveAll(path); err == nil {
61 return nil
62 }
63 }
64 return fmt.Errorf("cgroups: unable to remove path %q: %w", path, err)
65 }
66
67
68 func parseCgroupProcsFile(path string) ([]uint64, error) {
69 f, err := os.Open(path)
70 if err != nil {
71 return nil, err
72 }
73 defer f.Close()
74 var (
75 out []uint64
76 s = bufio.NewScanner(f)
77 )
78 for s.Scan() {
79 if t := s.Text(); t != "" {
80 pid, err := strconv.ParseUint(t, 10, 0)
81 if err != nil {
82 return nil, err
83 }
84 out = append(out, pid)
85 }
86 }
87 if err := s.Err(); err != nil {
88 return nil, err
89 }
90 return out, nil
91 }
92
93 func parseKV(raw string) (string, interface{}, error) {
94 parts := strings.Fields(raw)
95 switch len(parts) {
96 case 2:
97 v, err := parseUint(parts[1], 10, 64)
98 if err != nil {
99
100 return parts[0], parts[1], nil
101 }
102 return parts[0], v, nil
103 default:
104 return "", 0, ErrInvalidFormat
105 }
106 }
107
108 func parseUint(s string, base, bitSize int) (uint64, error) {
109 v, err := strconv.ParseUint(s, base, bitSize)
110 if err != nil {
111 intValue, intErr := strconv.ParseInt(s, base, bitSize)
112
113
114 if intErr == nil && intValue < 0 {
115 return 0, nil
116 } else if intErr != nil &&
117 intErr.(*strconv.NumError).Err == strconv.ErrRange &&
118 intValue < 0 {
119 return 0, nil
120 }
121 return 0, err
122 }
123 return v, nil
124 }
125
126
127 func parseCgroupFile(path string) (string, error) {
128 f, err := os.Open(path)
129 if err != nil {
130 return "", err
131 }
132 defer f.Close()
133 return parseCgroupFromReader(f)
134 }
135
136 func parseCgroupFromReader(r io.Reader) (string, error) {
137 var (
138 s = bufio.NewScanner(r)
139 )
140 for s.Scan() {
141 var (
142 text = s.Text()
143 parts = strings.SplitN(text, ":", 3)
144 )
145 if len(parts) < 3 {
146 return "", fmt.Errorf("invalid cgroup entry: %q", text)
147 }
148
149 if parts[0] == "0" && parts[1] == "" {
150 return parts[2], nil
151 }
152 }
153 if err := s.Err(); err != nil {
154 return "", err
155 }
156 return "", fmt.Errorf("cgroup path not found")
157 }
158
159
160
161
162
163
164 func ToResources(spec *specs.LinuxResources) *Resources {
165 var resources Resources
166 if cpu := spec.CPU; cpu != nil {
167 resources.CPU = &CPU{
168 Cpus: cpu.Cpus,
169 Mems: cpu.Mems,
170 }
171 if shares := cpu.Shares; shares != nil {
172 convertedWeight := 1 + ((*shares-2)*9999)/262142
173 resources.CPU.Weight = &convertedWeight
174 }
175 if period := cpu.Period; period != nil {
176 resources.CPU.Max = NewCPUMax(cpu.Quota, period)
177 }
178 }
179 if mem := spec.Memory; mem != nil {
180 resources.Memory = &Memory{}
181 if swap := mem.Swap; swap != nil {
182 resources.Memory.Swap = swap
183 }
184 if l := mem.Limit; l != nil {
185 resources.Memory.Max = l
186 }
187 if l := mem.Reservation; l != nil {
188 resources.Memory.Low = l
189 }
190 }
191 if hugetlbs := spec.HugepageLimits; hugetlbs != nil {
192 hugeTlbUsage := HugeTlb{}
193 for _, hugetlb := range hugetlbs {
194 hugeTlbUsage = append(hugeTlbUsage, HugeTlbEntry{
195 HugePageSize: hugetlb.Pagesize,
196 Limit: hugetlb.Limit,
197 })
198 }
199 resources.HugeTlb = &hugeTlbUsage
200 }
201 if pids := spec.Pids; pids != nil {
202 resources.Pids = &Pids{
203 Max: pids.Limit,
204 }
205 }
206 if i := spec.BlockIO; i != nil {
207 resources.IO = &IO{}
208 if i.Weight != nil {
209 resources.IO.BFQ.Weight = 1 + (*i.Weight-10)*9999/990
210 }
211 for t, devices := range map[IOType][]specs.LinuxThrottleDevice{
212 ReadBPS: i.ThrottleReadBpsDevice,
213 WriteBPS: i.ThrottleWriteBpsDevice,
214 ReadIOPS: i.ThrottleReadIOPSDevice,
215 WriteIOPS: i.ThrottleWriteIOPSDevice,
216 } {
217 for _, d := range devices {
218 resources.IO.Max = append(resources.IO.Max, Entry{
219 Type: t,
220 Major: d.Major,
221 Minor: d.Minor,
222 Rate: d.Rate,
223 })
224 }
225 }
226 }
227 if i := spec.Rdma; i != nil {
228 resources.RDMA = &RDMA{}
229 for device, value := range spec.Rdma {
230 if device != "" && (value.HcaHandles != nil && value.HcaObjects != nil) {
231 resources.RDMA.Limit = append(resources.RDMA.Limit, RDMAEntry{
232 Device: device,
233 HcaHandles: *value.HcaHandles,
234 HcaObjects: *value.HcaObjects,
235 })
236 }
237 }
238 }
239
240 return &resources
241 }
242
243
244 func getStatFileContentUint64(filePath string) uint64 {
245 contents, err := os.ReadFile(filePath)
246 if err != nil {
247 return 0
248 }
249 trimmed := strings.TrimSpace(string(contents))
250 if trimmed == "max" {
251 return math.MaxUint64
252 }
253
254 res, err := parseUint(trimmed, 10, 64)
255 if err != nil {
256 logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), filePath)
257 return res
258 }
259
260 return res
261 }
262
263 func readIoStats(path string) []*stats.IOEntry {
264
265 var usage []*stats.IOEntry
266 fpath := filepath.Join(path, "io.stat")
267 currentData, err := os.ReadFile(fpath)
268 if err != nil {
269 return usage
270 }
271 entries := strings.Split(string(currentData), "\n")
272
273 for _, entry := range entries {
274 parts := strings.Split(entry, " ")
275 if len(parts) < 2 {
276 continue
277 }
278 majmin := strings.Split(parts[0], ":")
279 if len(majmin) != 2 {
280 continue
281 }
282 major, err := strconv.ParseUint(majmin[0], 10, 0)
283 if err != nil {
284 return usage
285 }
286 minor, err := strconv.ParseUint(majmin[1], 10, 0)
287 if err != nil {
288 return usage
289 }
290 parts = parts[1:]
291 ioEntry := stats.IOEntry{
292 Major: major,
293 Minor: minor,
294 }
295 for _, s := range parts {
296 keyPairValue := strings.Split(s, "=")
297 if len(keyPairValue) != 2 {
298 continue
299 }
300 v, err := strconv.ParseUint(keyPairValue[1], 10, 0)
301 if err != nil {
302 continue
303 }
304 switch keyPairValue[0] {
305 case "rbytes":
306 ioEntry.Rbytes = v
307 case "wbytes":
308 ioEntry.Wbytes = v
309 case "rios":
310 ioEntry.Rios = v
311 case "wios":
312 ioEntry.Wios = v
313 }
314 }
315 usage = append(usage, &ioEntry)
316 }
317 return usage
318 }
319
320 func rdmaStats(filepath string) []*stats.RdmaEntry {
321 currentData, err := os.ReadFile(filepath)
322 if err != nil {
323 return []*stats.RdmaEntry{}
324 }
325 return toRdmaEntry(strings.Split(string(currentData), "\n"))
326 }
327
328 func parseRdmaKV(raw string, entry *stats.RdmaEntry) {
329 var value uint64
330 var err error
331
332 parts := strings.Split(raw, "=")
333 switch len(parts) {
334 case 2:
335 if parts[1] == "max" {
336 value = math.MaxUint32
337 } else {
338 value, err = parseUint(parts[1], 10, 32)
339 if err != nil {
340 return
341 }
342 }
343 if parts[0] == "hca_handle" {
344 entry.HcaHandles = uint32(value)
345 } else if parts[0] == "hca_object" {
346 entry.HcaObjects = uint32(value)
347 }
348 }
349 }
350
351 func toRdmaEntry(strEntries []string) []*stats.RdmaEntry {
352 var rdmaEntries []*stats.RdmaEntry
353 for i := range strEntries {
354 parts := strings.Fields(strEntries[i])
355 switch len(parts) {
356 case 3:
357 entry := new(stats.RdmaEntry)
358 entry.Device = parts[0]
359 parseRdmaKV(parts[1], entry)
360 parseRdmaKV(parts[2], entry)
361
362 rdmaEntries = append(rdmaEntries, entry)
363 default:
364 continue
365 }
366 }
367 return rdmaEntries
368 }
369
370
371 func isUnitExists(err error) bool {
372 if err != nil {
373 if dbusError, ok := err.(dbus.Error); ok {
374 return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
375 }
376 }
377 return false
378 }
379
380 func systemdUnitFromPath(path string) string {
381 _, unit := filepath.Split(path)
382 return unit
383 }
384
385 func readHugeTlbStats(path string) []*stats.HugeTlbStat {
386 var usage = []*stats.HugeTlbStat{}
387 var keyUsage = make(map[string]*stats.HugeTlbStat)
388 f, err := os.Open(path)
389 if err != nil {
390 return usage
391 }
392 files, err := f.Readdir(-1)
393 f.Close()
394 if err != nil {
395 return usage
396 }
397
398 for _, file := range files {
399 if strings.Contains(file.Name(), "hugetlb") &&
400 (strings.HasSuffix(file.Name(), "max") || strings.HasSuffix(file.Name(), "current")) {
401 var hugeTlb *stats.HugeTlbStat
402 var ok bool
403 fileName := strings.Split(file.Name(), ".")
404 pageSize := fileName[1]
405 if hugeTlb, ok = keyUsage[pageSize]; !ok {
406 hugeTlb = &stats.HugeTlbStat{}
407 }
408 hugeTlb.Pagesize = pageSize
409 out, err := os.ReadFile(filepath.Join(path, file.Name()))
410 if err != nil {
411 continue
412 }
413 var value uint64
414 stringVal := strings.TrimSpace(string(out))
415 if stringVal == "max" {
416 value = math.MaxUint64
417 } else {
418 value, err = strconv.ParseUint(stringVal, 10, 64)
419 }
420 if err != nil {
421 continue
422 }
423 switch fileName[2] {
424 case "max":
425 hugeTlb.Max = value
426 case "current":
427 hugeTlb.Current = value
428 }
429 keyUsage[pageSize] = hugeTlb
430 }
431 }
432 for _, entry := range keyUsage {
433 usage = append(usage, entry)
434 }
435 return usage
436 }
437
View as plain text