github.com/lingyao2333/mo-zero@v1.4.1/core/stat/internal/cgroup_linux.go (about)

     1  package internal
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/lingyao2333/mo-zero/core/iox"
    14  	"github.com/lingyao2333/mo-zero/core/lang"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  const (
    19  	cgroupDir   = "/sys/fs/cgroup"
    20  	cpuStatFile = cgroupDir + "/cpu.stat"
    21  	cpusetFile  = cgroupDir + "/cpuset.cpus.effective"
    22  )
    23  
    24  var (
    25  	isUnifiedOnce sync.Once
    26  	isUnified     bool
    27  	inUserNS      bool
    28  	nsOnce        sync.Once
    29  )
    30  
    31  type cgroup interface {
    32  	cpuQuotaUs() (int64, error)
    33  	cpuPeriodUs() (uint64, error)
    34  	cpus() ([]uint64, error)
    35  	usageAllCpus() (uint64, error)
    36  }
    37  
    38  func currentCgroup() (cgroup, error) {
    39  	if isCgroup2UnifiedMode() {
    40  		return currentCgroupV2()
    41  	}
    42  
    43  	return currentCgroupV1()
    44  }
    45  
    46  type cgroupV1 struct {
    47  	cgroups map[string]string
    48  }
    49  
    50  func (c *cgroupV1) cpuQuotaUs() (int64, error) {
    51  	data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_quota_us"))
    52  	if err != nil {
    53  		return 0, err
    54  	}
    55  
    56  	return strconv.ParseInt(data, 10, 64)
    57  }
    58  
    59  func (c *cgroupV1) cpuPeriodUs() (uint64, error) {
    60  	data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_period_us"))
    61  	if err != nil {
    62  		return 0, err
    63  	}
    64  
    65  	return parseUint(data)
    66  }
    67  
    68  func (c *cgroupV1) cpus() ([]uint64, error) {
    69  	data, err := iox.ReadText(path.Join(c.cgroups["cpuset"], "cpuset.cpus"))
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return parseUints(data)
    75  }
    76  
    77  func (c *cgroupV1) usageAllCpus() (uint64, error) {
    78  	data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage"))
    79  	if err != nil {
    80  		return 0, err
    81  	}
    82  
    83  	return parseUint(data)
    84  }
    85  
    86  type cgroupV2 struct {
    87  	cgroups map[string]string
    88  }
    89  
    90  func (c *cgroupV2) cpuQuotaUs() (int64, error) {
    91  	data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_quota_us"))
    92  	if err != nil {
    93  		return 0, err
    94  	}
    95  
    96  	return strconv.ParseInt(data, 10, 64)
    97  }
    98  
    99  func (c *cgroupV2) cpuPeriodUs() (uint64, error) {
   100  	data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_period_us"))
   101  	if err != nil {
   102  		return 0, err
   103  	}
   104  
   105  	return parseUint(data)
   106  }
   107  
   108  func (c *cgroupV2) cpus() ([]uint64, error) {
   109  	data, err := iox.ReadText(cpusetFile)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return parseUints(data)
   115  }
   116  
   117  func (c *cgroupV2) usageAllCpus() (uint64, error) {
   118  	usec, err := parseUint(c.cgroups["usage_usec"])
   119  	if err != nil {
   120  		return 0, err
   121  	}
   122  
   123  	return usec * uint64(time.Microsecond), nil
   124  }
   125  
   126  func currentCgroupV1() (cgroup, error) {
   127  	cgroupFile := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
   128  	lines, err := iox.ReadTextLines(cgroupFile, iox.WithoutBlank())
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	cgroups := make(map[string]string)
   134  	for _, line := range lines {
   135  		cols := strings.Split(line, ":")
   136  		if len(cols) != 3 {
   137  			return nil, fmt.Errorf("invalid cgroup line: %s", line)
   138  		}
   139  
   140  		subsys := cols[1]
   141  		// only read cpu staff
   142  		if !strings.HasPrefix(subsys, "cpu") {
   143  			continue
   144  		}
   145  
   146  		// https://man7.org/linux/man-pages/man7/cgroups.7.html
   147  		// comma-separated list of controllers for cgroup version 1
   148  		fields := strings.Split(subsys, ",")
   149  		for _, val := range fields {
   150  			cgroups[val] = path.Join(cgroupDir, val)
   151  		}
   152  	}
   153  
   154  	return &cgroupV1{
   155  		cgroups: cgroups,
   156  	}, nil
   157  }
   158  
   159  func currentCgroupV2() (cgroup, error) {
   160  	lines, err := iox.ReadTextLines(cpuStatFile, iox.WithoutBlank())
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	cgroups := make(map[string]string)
   166  	for _, line := range lines {
   167  		cols := strings.Fields(line)
   168  		if len(cols) != 2 {
   169  			return nil, fmt.Errorf("invalid cgroupV2 line: %s", line)
   170  		}
   171  
   172  		cgroups[cols[0]] = cols[1]
   173  	}
   174  
   175  	return &cgroupV2{
   176  		cgroups: cgroups,
   177  	}, nil
   178  }
   179  
   180  // isCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
   181  func isCgroup2UnifiedMode() bool {
   182  	isUnifiedOnce.Do(func() {
   183  		var st unix.Statfs_t
   184  		err := unix.Statfs(cgroupDir, &st)
   185  		if err != nil {
   186  			if os.IsNotExist(err) && runningInUserNS() {
   187  				// ignore the "not found" error if running in userns
   188  				isUnified = false
   189  				return
   190  			}
   191  			panic(fmt.Sprintf("cannot statfs cgroup root: %s", err))
   192  		}
   193  		isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
   194  	})
   195  
   196  	return isUnified
   197  }
   198  
   199  func parseUint(s string) (uint64, error) {
   200  	v, err := strconv.ParseInt(s, 10, 64)
   201  	if err != nil {
   202  		if err.(*strconv.NumError).Err == strconv.ErrRange {
   203  			return 0, nil
   204  		}
   205  
   206  		return 0, fmt.Errorf("cgroup: bad int format: %s", s)
   207  	}
   208  
   209  	if v < 0 {
   210  		return 0, nil
   211  	}
   212  
   213  	return uint64(v), nil
   214  }
   215  
   216  func parseUints(val string) ([]uint64, error) {
   217  	if val == "" {
   218  		return nil, nil
   219  	}
   220  
   221  	ints := make(map[uint64]lang.PlaceholderType)
   222  	cols := strings.Split(val, ",")
   223  	for _, r := range cols {
   224  		if strings.Contains(r, "-") {
   225  			fields := strings.SplitN(r, "-", 2)
   226  			min, err := parseUint(fields[0])
   227  			if err != nil {
   228  				return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
   229  			}
   230  
   231  			max, err := parseUint(fields[1])
   232  			if err != nil {
   233  				return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
   234  			}
   235  
   236  			if max < min {
   237  				return nil, fmt.Errorf("cgroup: bad int list format: %s", val)
   238  			}
   239  
   240  			for i := min; i <= max; i++ {
   241  				ints[i] = lang.Placeholder
   242  			}
   243  		} else {
   244  			v, err := parseUint(r)
   245  			if err != nil {
   246  				return nil, err
   247  			}
   248  
   249  			ints[v] = lang.Placeholder
   250  		}
   251  	}
   252  
   253  	var sets []uint64
   254  	for k := range ints {
   255  		sets = append(sets, k)
   256  	}
   257  
   258  	return sets, nil
   259  }
   260  
   261  // runningInUserNS detects whether we are currently running in an user namespace.
   262  func runningInUserNS() bool {
   263  	nsOnce.Do(func() {
   264  		file, err := os.Open("/proc/self/uid_map")
   265  		if err != nil {
   266  			// This kernel-provided file only exists if user namespaces are supported
   267  			return
   268  		}
   269  		defer file.Close()
   270  
   271  		buf := bufio.NewReader(file)
   272  		l, _, err := buf.ReadLine()
   273  		if err != nil {
   274  			return
   275  		}
   276  
   277  		line := string(l)
   278  		var a, b, c int64
   279  		fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
   280  
   281  		/*
   282  		 * We assume we are in the initial user namespace if we have a full
   283  		 * range - 4294967295 uids starting at uid 0.
   284  		 */
   285  		if a == 0 && b == 0 && c == 4294967295 {
   286  			return
   287  		}
   288  		inUserNS = true
   289  	})
   290  
   291  	return inUserNS
   292  }