cgroup/util.go (184 lines of code) (raw):

package cgroup import ( "bufio" "bytes" "errors" "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" ) var ( // ErrCgroupsMissing indicates the /proc/cgroups was not found. This means // that cgroups were disabled at compile time (CONFIG_CGROUPS=n) or that // an invalid rootfs path was given. ErrCgroupsMissing = errors.New("cgroups not found or unsupported by OS") // ErrInvalidFormat indicates a malformed key/value pair on a line. ErrInvalidFormat = errors.New("error invalid key/value format") ) // mountinfo represents a subset of the fields containing /proc/[pid]/mountinfo. type mountinfo struct { mountpoint string filesystemType string superOptions []string } // Parses a cgroup param and returns the key name and value. func parseCgroupParamKeyValue(t string) (string, uint64, error) { parts := strings.Fields(t) if len(parts) != 2 { return "", 0, ErrInvalidFormat } value, err := parseUint([]byte(parts[1])) if err != nil { return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err) } return parts[0], value, nil } // parseUintFromFile reads a single uint value from a file. func parseUintFromFile(path ...string) (uint64, error) { value, err := ioutil.ReadFile(filepath.Join(path...)) if err != nil { // Not all features are implemented/enabled by each OS. if os.IsNotExist(err) { return 0, nil } return 0, err } return parseUint(value) } // parseUint reads a single uint value. It will trip any whitespace before // attempting to parse string. If the value is negative it will return 0. func parseUint(value []byte) (uint64, error) { strValue := string(bytes.TrimSpace(value)) uintValue, err := strconv.ParseUint(strValue, 10, 64) if err != nil { // Munge negative values to 0. intValue, intErr := strconv.ParseInt(strValue, 10, 64) if intErr == nil && intValue < 0 { return 0, nil } else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 { return 0, nil } return 0, err } return uintValue, nil } // parseMountinfoLine parses a line from the /proc/[pid]/mountinfo file on // Linux. The format of the line is specified in section 3.5 of // https://www.kernel.org/doc/Documentation/filesystems/proc.txt. func parseMountinfoLine(line string) (mountinfo, error) { mount := mountinfo{} fields := strings.Fields(line) if len(fields) < 10 { return mount, fmt.Errorf("invalid mountinfo line, expected at least "+ "10 fields but got %d from line='%s'", len(fields), line) } mount.mountpoint = fields[4] var seperatorIndex int for i, value := range fields { if value == "-" { seperatorIndex = i break } } if fields[seperatorIndex] != "-" { return mount, fmt.Errorf("invalid mountinfo line, separator ('-') not "+ "found in line='%s'", line) } if len(fields)-seperatorIndex-1 < 3 { return mount, fmt.Errorf("invalid mountinfo line, expected at least "+ "3 fields after seperator but got %d from line='%s'", len(fields)-seperatorIndex-1, line) } fields = fields[seperatorIndex+1:] mount.filesystemType = fields[0] mount.superOptions = strings.Split(fields[2], ",") return mount, nil } // SupportedSubsystems returns the subsystems that are supported by the // kernel. The returned map contains a entry for each subsystem. func SupportedSubsystems(rootfsMountpoint string) (map[string]struct{}, error) { if rootfsMountpoint == "" { rootfsMountpoint = "/" } cgroups, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "cgroups")) if err != nil { if os.IsNotExist(err) { return nil, ErrCgroupsMissing } return nil, err } defer cgroups.Close() subsystemSet := map[string]struct{}{} sc := bufio.NewScanner(cgroups) for sc.Scan() { line := sc.Text() // Ignore the header. if len(line) > 0 && line[0] == '#' { continue } // Parse the cgroup subsystems. // Format: subsys_name hierarchy num_cgroups enabled // Example: cpuset 4 1 1 fields := strings.Fields(line) if len(fields) == 0 { continue } // Check the enabled flag. if len(fields) > 3 { enabled := fields[3] if enabled == "0" { // Ignore cgroup subsystems that are disabled (via the // cgroup_disable kernel command-line boot parameter). continue } } subsystem := fields[0] subsystemSet[subsystem] = struct{}{} } return subsystemSet, sc.Err() } // SubsystemMountpoints returns the mountpoints for each of the given subsystems. // The returned map contains the subsystem name as a key and the value is the // mountpoint. func SubsystemMountpoints(rootfsMountpoint string, subsystems map[string]struct{}) (map[string]string, error) { if rootfsMountpoint == "" { rootfsMountpoint = "/" } mountinfo, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "self", "mountinfo")) if err != nil { return nil, err } defer mountinfo.Close() mounts := map[string]string{} sc := bufio.NewScanner(mountinfo) for sc.Scan() { // https://www.kernel.org/doc/Documentation/filesystems/proc.txt // Example: // 25 21 0:20 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu line := strings.TrimSpace(sc.Text()) if line == "" { continue } mount, err := parseMountinfoLine(line) if err != nil { return nil, err } if mount.filesystemType != "cgroup" { continue } if !strings.HasPrefix(mount.mountpoint, rootfsMountpoint) { continue } for _, opt := range mount.superOptions { // Sometimes the subsystem name is written like "name=blkio". fields := strings.SplitN(opt, "=", 2) if len(fields) > 1 { opt = fields[1] } // Test if option is a subsystem name. if _, found := subsystems[opt]; found { // Add the subsystem mount if it does not already exist. if _, exists := mounts[opt]; !exists { mounts[opt] = mount.mountpoint } } } } return mounts, sc.Err() } // ProcessCgroupPaths returns the cgroups to which a process belongs and the // pathname of the cgroup relative to the mountpoint of the subsystem. func ProcessCgroupPaths(rootfsMountpoint string, pid int) (map[string]string, error) { if rootfsMountpoint == "" { rootfsMountpoint = "/" } cgroup, err := os.Open(filepath.Join(rootfsMountpoint, "proc", strconv.Itoa(pid), "cgroup")) if err != nil { return nil, err } defer cgroup.Close() paths := map[string]string{} sc := bufio.NewScanner(cgroup) for sc.Scan() { // http://man7.org/linux/man-pages/man7/cgroups.7.html // Format: hierarchy-ID:subsystem-list:cgroup-path // Example: // 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242 line := sc.Text() fields := strings.Split(line, ":") if len(fields) != 3 { continue } path := fields[2] subsystems := strings.Split(fields[1], ",") for _, subsystem := range subsystems { paths[subsystem] = path } } return paths, sc.Err() }