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

     1  package cgroup
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  )
    10  
    11  // Cgroup defines the common interface to control cgroups
    12  // including v1 and v2 implementations.
    13  // TODO: implement systemd integration
    14  type Cgroup interface {
    15  	// AddProc add a process into the cgroup
    16  	AddProc(pid ...int) error
    17  
    18  	// Destroy deletes the cgroup
    19  	Destroy() error
    20  
    21  	// Existing returns true if the cgroup was opened rather than created
    22  	Existing() bool
    23  
    24  	//Nest creates a sub-cgroup, moves current process into that cgroup
    25  	Nest(name string) (Cgroup, error)
    26  
    27  	// CPUUsage reads total cpu usage of cgroup
    28  	CPUUsage() (uint64, error)
    29  
    30  	// MemoryUsage reads current total memory usage
    31  	MemoryUsage() (uint64, error)
    32  
    33  	// MemoryMaxUsageInBytes reads max total memory usage. Not exist in cgroup v2 with kernel version < 5.19
    34  	MemoryMaxUsage() (uint64, error)
    35  
    36  	// SetCPUBandwidth sets the cpu bandwidth. Times in ns
    37  	SetCPUBandwidth(quota, period uint64) error
    38  
    39  	// SetCpusetCpus sets the available cpu to use (cpuset.cpus).
    40  	SetCPUSet([]byte) error
    41  
    42  	// SetMemoryLimit sets memory.limit_in_bytes
    43  	SetMemoryLimit(uint64) error
    44  
    45  	// SetProcLimit sets pids.max
    46  	SetProcLimit(uint64) error
    47  
    48  	// Processes lists all existing process pid from the cgroup
    49  	Processes() ([]int, error)
    50  
    51  	// New creates a sub-cgroup based on the existing one
    52  	New(string) (Cgroup, error)
    53  
    54  	// Random creates a sub-cgroup based on the existing one but the name is randomly generated
    55  	Random(string) (Cgroup, error)
    56  }
    57  
    58  // DetectedCgroupType defines the current cgroup type of the system
    59  var DetectedCgroupType = DetectType()
    60  
    61  // New creates a new cgroup with provided prefix, it opens existing one if existed
    62  func New(prefix string, ct *Controllers) (Cgroup, error) {
    63  	if DetectedCgroupType == TypeV1 {
    64  		return newV1(prefix, ct)
    65  	}
    66  	return newV2(prefix, ct)
    67  }
    68  
    69  func loopV1Controllers(ct *Controllers, v1 *V1, f func(string, **v1controller) error) error {
    70  	for _, c := range []struct {
    71  		available bool
    72  		name      string
    73  		cg        **v1controller
    74  	}{
    75  		{ct.CPU, CPU, &v1.cpu},
    76  		{ct.CPUSet, CPUSet, &v1.cpuset},
    77  		{ct.CPUAcct, CPUAcct, &v1.cpuacct},
    78  		{ct.Memory, Memory, &v1.memory},
    79  		{ct.Pids, Pids, &v1.pids},
    80  	} {
    81  		if !c.available {
    82  			continue
    83  		}
    84  		if err := f(c.name, c.cg); err != nil {
    85  			return err
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  func newV1(prefix string, ct *Controllers) (cg Cgroup, err error) {
    92  	v1 := &V1{
    93  		prefix: prefix,
    94  	}
    95  	// if failed, remove potential created directory
    96  	defer func() {
    97  		if err != nil && !v1.existing {
    98  			for _, p := range v1.all {
    99  				remove(p.path)
   100  			}
   101  		}
   102  	}()
   103  
   104  	if err = loopV1Controllers(ct, v1, func(name string, cg **v1controller) error {
   105  		path, err := CreateV1ControllerPath(name, prefix)
   106  		*cg = newV1Controller(path)
   107  		if errors.Is(err, os.ErrExist) {
   108  			if len(v1.all) == 0 {
   109  				v1.existing = true
   110  			}
   111  			return nil
   112  		}
   113  		if err != nil {
   114  			return err
   115  		}
   116  		v1.all = append(v1.all, *cg)
   117  		return nil
   118  	}); err != nil {
   119  		return
   120  	}
   121  
   122  	// init cpu set before use, otherwise it is not functional
   123  	if v1.cpuset != nil {
   124  		if err = initCpuset(v1.cpuset.path); err != nil {
   125  			return
   126  		}
   127  	}
   128  	return v1, err
   129  }
   130  
   131  func newV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
   132  	v2 := &V2{
   133  		path: path.Join(basePath, prefix),
   134  	}
   135  	if _, err := os.Stat(v2.path); err == nil {
   136  		v2.existing = true
   137  	}
   138  	defer func() {
   139  		if err != nil && !v2.existing {
   140  			remove(v2.path)
   141  		}
   142  	}()
   143  
   144  	// ensure controllers were enabled
   145  	s := ct.Names()
   146  	controlMsg := []byte("+" + strings.Join(s, " +"))
   147  
   148  	// start from base dir
   149  	entries := strings.Split(prefix, "/")
   150  	current := ""
   151  	for _, e := range entries {
   152  		parent := current
   153  		current = current + "/" + e
   154  		// try mkdir if not exists
   155  		if _, err := os.Stat(path.Join(basePath, current)); os.IsNotExist(err) {
   156  			if err := os.Mkdir(path.Join(basePath, current), dirPerm); err != nil {
   157  				return nil, err
   158  			}
   159  		} else if err != nil {
   160  			return nil, err
   161  		}
   162  
   163  		// no err means create success, need to enable it in its parent folder
   164  		ect, err := getAvailableControllerV2(current)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		if ect.Contains(ct) {
   169  			continue
   170  		}
   171  		if err := writeFile(path.Join(basePath, parent, cgroupSubtreeControl), controlMsg, filePerm); err != nil {
   172  			return nil, err
   173  		}
   174  	}
   175  	return v2, nil
   176  }
   177  
   178  // OpenExisting opens a existing cgroup with provided prefix
   179  func OpenExisting(prefix string, ct *Controllers) (Cgroup, error) {
   180  	if DetectedCgroupType == TypeV1 {
   181  		return openExistingV1(prefix, ct)
   182  	}
   183  	return openExistingV2(prefix, ct)
   184  }
   185  
   186  func openExistingV1(prefix string, ct *Controllers) (cg Cgroup, err error) {
   187  	v1 := &V1{
   188  		prefix:   prefix,
   189  		existing: true,
   190  	}
   191  
   192  	if err = loopV1Controllers(ct, v1, func(name string, cg **v1controller) error {
   193  		p := path.Join(basePath, name, prefix)
   194  		*cg = newV1Controller(p)
   195  		// os.IsNotExist
   196  		if _, err := os.Stat(p); err != nil {
   197  			return err
   198  		}
   199  		v1.all = append(v1.all, *cg)
   200  		return nil
   201  	}); err != nil {
   202  		return
   203  	}
   204  
   205  	// init cpu set before use, otherwise it is not functional
   206  	if v1.cpuset != nil {
   207  		if err = initCpuset(v1.cpuset.path); err != nil {
   208  			return
   209  		}
   210  	}
   211  	return
   212  }
   213  
   214  func openExistingV2(prefix string, ct *Controllers) (cg Cgroup, err error) {
   215  	ect, err := getAvailableControllerV2(prefix)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	if !ect.Contains(ct) {
   220  		return nil, fmt.Errorf("openCgroupV2: requesting %v controllers but %v found", ct, ect)
   221  	}
   222  	return &V2{
   223  		path:     path.Join(basePath, prefix),
   224  		existing: true,
   225  	}, nil
   226  }