plugins/processors/ecsdecorator/cgroup.go (135 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT package ecsdecorator import ( "bufio" "fmt" "log" "math" "os" "path" "path/filepath" "strconv" "strings" ) const ( kernelMagicCodeNotSet = int64(9223372036854771712) // infinity magic number for cgroup: https://unix.stackexchange.com/questions/420906/what-is-the-value-for-the-cgroups-limit-in-bytes-if-the-memory-is-not-restricte ecsInstanceMountConfigPath = "/proc/self/mountinfo" ) type cgroupScanner struct { mountPoint string } func newCGroupScanner(mountConfigPath string) (c *cgroupScanner) { mp, err := getCGroupMountPoint(mountConfigPath) if err != nil { log.Printf("D! failed to get the cgroup mount point, error: %v, fallback to /cgroup", err) mp = "/cgroup" } c = &cgroupScanner{ mountPoint: mp, } return c } func newCGroupScannerForContainer() *cgroupScanner { return newCGroupScanner(ecsInstanceMountConfigPath) } func (c *cgroupScanner) getCPUReserved(taskID string, clusterName string) int64 { cpuPath, err := getCGroupPathForTask(c.mountPoint, "cpu", taskID, clusterName) if err != nil { log.Printf("E! failed to get cpu cgroup path for task: %v", err) return int64(0) } // check if hard limit is configured if cfsQuota, err := readInt64(cpuPath, "cpu.cfs_quota_us"); err == nil && cfsQuota != -1 { if cfsPeriod, err := readInt64(cpuPath, "cpu.cfs_period_us"); err == nil && cfsPeriod > 0 { return int64(math.Ceil(float64(1024*cfsQuota) / float64(cfsPeriod))) } } if shares, err := readInt64(cpuPath, "cpu.shares"); err == nil { return shares } return int64(0) } func (c *cgroupScanner) getMEMReserved(taskID string, clusterName string, containers []ECSContainer) int64 { memPath, err := getCGroupPathForTask(c.mountPoint, "memory", taskID, clusterName) if err != nil { log.Printf("E! failed to get memory cgroup path for task: %v", err) return int64(0) } if memReserved, err := readInt64(memPath, "memory.limit_in_bytes"); err == nil && memReserved != kernelMagicCodeNotSet { return memReserved } // sum the containers' memory if the task's memory limit is not configured sum := int64(0) for _, container := range containers { containerPath := path.Join(memPath, container.DockerId) //soft limit first if softLimit, err := readInt64(containerPath, "memory.soft_limit_in_bytes"); err == nil && softLimit != kernelMagicCodeNotSet { sum += softLimit continue } // try hard limit when soft limit is not configured if hardLimit, err := readInt64(containerPath, "memory.limit_in_bytes"); err == nil && hardLimit != kernelMagicCodeNotSet { sum += hardLimit } } return sum } func readString(dirpath string, file string) (string, error) { cgroupFile := path.Join(dirpath, file) // Read out, err := os.ReadFile(cgroupFile) if err != nil { // Ignore non-existent files log.Printf("W! readString: Failed to read %q: %s", cgroupFile, err) return "", err } return strings.TrimSpace(string(out)), nil } func readInt64(dirpath string, file string) (int64, error) { out, err := readString(dirpath, file) if err != nil { return 0, err } if out == "" || out == "max" { return 0, err } val, err := strconv.ParseInt(out, 10, 64) if err != nil { log.Printf("W! readInt64: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err) return 0, err } return val, nil } func getCGroupMountPoint(mountConfigPath string) (string, error) { f, err := os.Open(mountConfigPath) if err != nil { return "", err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { if err := scanner.Err(); err != nil { return "", err } var ( text = scanner.Text() fields = strings.Split(text, " ") // safe as mountinfo encodes mountpoints with spaces as \040. // an example: 26 22 0:23 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu index = strings.Index(text, " - ") postSeparatorFields = strings.Fields(text[index+3:]) numPostFields = len(postSeparatorFields) ) // this is an error as we can't detect if the mount is for "cgroup" if numPostFields == 0 { return "", fmt.Errorf("Found no fields post '-' in %q", text) } if postSeparatorFields[0] == "cgroup" { // check that the mount is properly formated. if numPostFields < 3 { return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text) } return filepath.Dir(fields[4]), nil } } return "", fmt.Errorf("mount point not existed") } func getCGroupPathForTask(cgroupMount, controller, taskID, clusterName string) (string, error) { taskPath := path.Join(cgroupMount, controller, "ecs", taskID) if _, err := os.Stat(taskPath); os.IsNotExist(err) { // Task cgroup path does not exist, fallback to try legacy Task cgroup path, // legacy cgroup path of task with new format ARN used to contain cluster name, // before ECS Agent PR https://github.com/aws/amazon-ecs-agent/pull/2497/ taskPath = path.Join(cgroupMount, controller, "ecs", clusterName, taskID) if _, err := os.Stat(taskPath); os.IsNotExist(err) { return "", fmt.Errorf("CGroup Path %q does not exist", taskPath) } } return taskPath, nil }