github.com/criyle/go-sandbox@v0.10.3/pkg/cgroup/v1_linux.go (about)

     1  package cgroup
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  var _ Cgroup = &V1{}
    13  
    14  // V1 is the combination of v1 controllers
    15  type V1 struct {
    16  	prefix string
    17  
    18  	cpu     *v1controller
    19  	cpuset  *v1controller
    20  	cpuacct *v1controller
    21  	memory  *v1controller
    22  	pids    *v1controller
    23  
    24  	all []*v1controller
    25  
    26  	existing bool
    27  }
    28  
    29  func (c *V1) String() string {
    30  	names := make([]string, 0, numberOfControllers)
    31  	for _, v := range []struct {
    32  		now  *v1controller
    33  		name string
    34  	}{
    35  		{c.cpu, CPU},
    36  		{c.cpuset, CPUSet},
    37  		{c.cpuacct, CPUAcct},
    38  		{c.memory, Memory},
    39  		{c.pids, Pids},
    40  	} {
    41  		if v.now == nil {
    42  			continue
    43  		}
    44  		names = append(names, v.name)
    45  	}
    46  	return "v1(" + c.prefix + ")[" + strings.Join(names, ", ") + "]"
    47  }
    48  
    49  // AddProc writes cgroup.procs to all controller
    50  func (c *V1) AddProc(pids ...int) error {
    51  	for _, s := range c.all {
    52  		if err := s.AddProc(pids...); err != nil {
    53  			return err
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  // Processes lists all existing process pid from the cgroup
    60  func (c *V1) Processes() ([]int, error) {
    61  	if len(c.all) == 0 {
    62  		return nil, os.ErrInvalid
    63  	}
    64  	return ReadProcesses(path.Join(c.all[0].path, cgroupProcs))
    65  }
    66  
    67  // New creates a sub-cgroup based on the existing one
    68  func (c *V1) New(name string) (cg Cgroup, err error) {
    69  	v1 := &V1{
    70  		prefix: path.Join(c.prefix, name),
    71  	}
    72  	defer func() {
    73  		if err != nil {
    74  			for _, v := range v1.all {
    75  				remove(v.path)
    76  			}
    77  		}
    78  	}()
    79  	for _, v := range []struct {
    80  		now *v1controller
    81  		new **v1controller
    82  	}{
    83  		{c.cpu, &v1.cpu},
    84  		{c.cpuset, &v1.cpuset},
    85  		{c.cpuacct, &v1.cpuacct},
    86  		{c.memory, &v1.memory},
    87  		{c.pids, &v1.pids},
    88  	} {
    89  		if v.now == nil {
    90  			continue
    91  		}
    92  		p := path.Join(v.now.path, name)
    93  		*v.new = &v1controller{path: p}
    94  		err = EnsureDirExists(p)
    95  		if os.IsExist(err) {
    96  			err = nil
    97  			if len(v1.all) == 0 {
    98  				v1.existing = true
    99  			}
   100  			continue
   101  		}
   102  		if err != nil {
   103  			return
   104  		}
   105  		v1.all = append(v1.all, *v.new)
   106  	}
   107  	// init cpu set before use, otherwise it is not functional
   108  	if v1.cpuset != nil {
   109  		if err = initCpuset(v1.cpuset.path); err != nil {
   110  			return
   111  		}
   112  	}
   113  	return v1, nil
   114  }
   115  
   116  // Random creates a sub-cgroup based on the existing one but the name is randomly generated
   117  func (c *V1) Random(pattern string) (Cgroup, error) {
   118  	return randomBuild(pattern, c.New)
   119  }
   120  
   121  // Nest creates a sub-cgroup, moves current process into that cgroup
   122  func (c *V1) Nest(name string) (Cgroup, error) {
   123  	v1, err := c.New(name)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	p, err := c.Processes()
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	if err := v1.AddProc(p...); err != nil {
   132  		return nil, err
   133  	}
   134  	return v1, nil
   135  }
   136  
   137  // Destroy removes dir for controllers recursively, errors are ignored if remove one failed
   138  func (c *V1) Destroy() error {
   139  	var err1 error
   140  	for _, s := range c.all {
   141  		if c.existing {
   142  			continue
   143  		}
   144  		if err := remove(s.path); err != nil {
   145  			err1 = err
   146  		}
   147  	}
   148  	return err1
   149  }
   150  
   151  // Existing returns true if the cgroup was opened rather than created
   152  func (c *V1) Existing() bool {
   153  	return c.existing
   154  }
   155  
   156  // SetCPUBandwidth set cpu quota via cfs interface
   157  func (c *V1) SetCPUBandwidth(quota, period uint64) error {
   158  	if err := c.SetCPUCfsQuota(quota); err != nil {
   159  		return err
   160  	}
   161  	return c.SetCPUCfsPeriod(period)
   162  }
   163  
   164  // SetCPUSet set cpuset.cpus
   165  func (c *V1) SetCPUSet(b []byte) error {
   166  	return c.cpuset.WriteFile("cpuset.cpus", b)
   167  }
   168  
   169  // CPUUsage read cpuacct.usage in ns
   170  func (c *V1) CPUUsage() (uint64, error) {
   171  	return c.cpuacct.ReadUint("cpuacct.usage")
   172  }
   173  
   174  // MemoryUsage read memory.usage_in_bytes
   175  func (c *V1) MemoryUsage() (uint64, error) {
   176  	return c.memory.ReadUint("memory.usage_in_bytes")
   177  }
   178  
   179  // MemoryMaxUsage read memory.max_usage_in_bytes
   180  func (c *V1) MemoryMaxUsage() (uint64, error) {
   181  	return c.memory.ReadUint("memory.max_usage_in_bytes")
   182  }
   183  
   184  // SetMemoryLimit write memory.limit_in_bytes
   185  func (c *V1) SetMemoryLimit(i uint64) error {
   186  	return c.memory.WriteUint("memory.limit_in_bytes", i)
   187  }
   188  
   189  // SetProcLimit write pids.max
   190  func (c *V1) SetProcLimit(i uint64) error {
   191  	return c.pids.WriteUint("pids.max", i)
   192  }
   193  
   194  // SetCpuacctUsage write cpuacct.usage in ns
   195  func (c *V1) SetCpuacctUsage(i uint64) error {
   196  	return c.cpuacct.WriteUint("cpuacct.usage", i)
   197  }
   198  
   199  // SetMemoryMaxUsageInBytes write cpuacct.usage in ns
   200  func (c *V1) SetMemoryMaxUsageInBytes(i uint64) error {
   201  	return c.memory.WriteUint("memory.max_usage_in_bytes", i)
   202  }
   203  
   204  // MemoryMemswMaxUsageInBytes read memory.memsw.max_usage_in_bytes
   205  func (c *V1) MemoryMemswMaxUsageInBytes() (uint64, error) {
   206  	return c.memory.ReadUint("memory.memsw.max_usage_in_bytes")
   207  }
   208  
   209  // SetMemoryMemswLimitInBytes write memory.memsw.limit_in_bytes
   210  func (c *V1) SetMemoryMemswLimitInBytes(i uint64) error {
   211  	return c.memory.WriteUint("memory.memsw.limit_in_bytes", i)
   212  }
   213  
   214  // SetCPUCfsPeriod set cpu.cfs_period_us in us
   215  func (c *V1) SetCPUCfsPeriod(p uint64) error {
   216  	return c.cpu.WriteUint("cpu.cfs_period_us", p)
   217  }
   218  
   219  // SetCPUCfsQuota set cpu.cfs_quota_us in us
   220  func (c *V1) SetCPUCfsQuota(p uint64) error {
   221  	return c.cpu.WriteUint("cpu.cfs_quota_us", p)
   222  }
   223  
   224  // SetCpusetMems set cpuset.mems
   225  func (c *V1) SetCpusetMems(b []byte) error {
   226  	return c.cpuset.WriteFile("cpuset.mems", b)
   227  }
   228  
   229  // FindMemoryStatProperty find certain property from memory.stat
   230  func (c *V1) FindMemoryStatProperty(prop string) (uint64, error) {
   231  	content, err := c.memory.ReadFile("memory.stat")
   232  	if err != nil {
   233  		return 0, err
   234  	}
   235  	r := bytes.NewReader(content)
   236  	for {
   237  		var p string
   238  		var i uint64
   239  		_, err = fmt.Fscanln(r, &p, &i)
   240  		if err != nil {
   241  			return 0, err
   242  		}
   243  		if p == prop {
   244  			return i, nil
   245  		}
   246  	}
   247  }
   248  
   249  // initCpuset will copy the config from the parent cpu sets if not exists
   250  func initCpuset(path string) error {
   251  	for _, f := range []string{"cpuset.cpus", "cpuset.mems"} {
   252  		if err := copyCgroupPropertyFromParent(path, f); err != nil {
   253  			return err
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  func copyCgroupPropertyFromParent(path, name string) error {
   260  	// ensure current one empty
   261  	b, err := os.ReadFile(filepath.Join(path, name))
   262  	if err != nil {
   263  		return err
   264  	}
   265  	if len(bytes.TrimSpace(b)) > 0 {
   266  		return nil
   267  	}
   268  	// otherwise copy from parent, first to ensure it is empty by recursion
   269  	if err := copyCgroupPropertyFromParent(filepath.Dir(path), name); err != nil {
   270  		return err
   271  	}
   272  	b, err = os.ReadFile(filepath.Join(filepath.Dir(path), name))
   273  	if err != nil {
   274  		return err
   275  	}
   276  	return os.WriteFile(filepath.Join(path, name), b, filePerm)
   277  }