123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- 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()
- }
|