pkg/cgroups/memory.go (105 lines of code) (raw):

// Licensed to Apache Software Foundation (ASF) under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Apache Software Foundation (ASF) licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package cgroups import ( "bufio" "fmt" "io/fs" "os" "path/filepath" "strconv" "strings" ) const ( procCgroupPath = "proc/self/cgroup" procMountInfoPath = "proc/self/mountinfo" ) // MemoryLimit returns the memory limit in bytes for the current process. func MemoryLimit() (int64, error) { return getCgroupMemoryLimit(os.DirFS("/")) } func getCgroupMemoryLimit(fsys fs.FS) (int64, error) { isV2, err := isCGroupV2(fsys) if err != nil { return 0, fmt.Errorf("failed to determine cgroup version: %w", err) } cgroupData, err := fs.ReadFile(fsys, procCgroupPath) if err != nil { return 0, fmt.Errorf("failed to read cgroup data: %w", err) } memoryCgroupPath, err := findMemoryCgroupPath(string(cgroupData), isV2) if err != nil { return 0, fmt.Errorf("failed to find memory cgroup path: %w", err) } var limitPath string if isV2 { limitPath = filepath.Join("sys/fs/cgroup", memoryCgroupPath, "memory.max") } else { limitPath = filepath.Join("sys/fs/cgroup/memory", memoryCgroupPath, "memory.limit_in_bytes") } limit, err := parseMemoryLimit(fsys, limitPath) if err != nil { return 0, fmt.Errorf("failed to parse memory limit: %w", err) } if !isV2 { hierarchicalLimitPath := filepath.Join("sys/fs/cgroup/memory", memoryCgroupPath, "memory.hierarchical_memory_limit") hierarchicalLimit, err := parseMemoryLimit(fsys, hierarchicalLimitPath) if err != nil { return 0, fmt.Errorf("failed to parse hierarchical memory limit: %w", err) } if hierarchicalLimit < limit { limit = hierarchicalLimit } } return limit, nil } func isCGroupV2(fsys fs.FS) (bool, error) { file, err := fsys.Open(procMountInfoPath) if err != nil { return false, fmt.Errorf("failed to open mountinfo: %w", err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() fields := strings.Fields(line) if len(fields) < 5 { continue } mountPoint := fields[4] if mountPoint == "/sys/fs/cgroup" { for _, field := range fields { if strings.HasPrefix(field, "cgroup2") { return true, nil } } } } return false, nil } func findMemoryCgroupPath(cgroupData string, isV2 bool) (string, error) { lines := strings.Split(cgroupData, "\n") for _, line := range lines { fields := strings.Split(line, ":") if len(fields) >= 3 { if isV2 && fields[1] == "" { // For cgroup v2, the second field is empty return fields[2], nil } else if !isV2 && fields[1] == "memory" { // For cgroup v1, the second field is "memory" return fields[2], nil } } } return "", fmt.Errorf("memory cgroup not found") } func parseMemoryLimit(fsys fs.FS, path string) (int64, error) { limitData, err := fs.ReadFile(fsys, path) if err != nil { return 0, fmt.Errorf("failed to read memory limit: %w", err) } limitStr := strings.TrimSpace(string(limitData)) if limitStr == "max" { return -1, nil // -1 represents unlimited } limit, err := strconv.ParseInt(limitStr, 10, 64) if err != nil { return 0, fmt.Errorf("failed to parse memory limit: %w", err) } return limit, nil }