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

     1  package cgroup
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"os"
     7  	"path"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  )
    12  
    13  // V2 provides cgroup interface for v2
    14  type V2 struct {
    15  	path        string
    16  	subtreeOnce sync.Once
    17  	subtreeErr  error
    18  	existing    bool
    19  }
    20  
    21  var _ Cgroup = &V2{}
    22  
    23  func (c *V2) String() string {
    24  	ct, _ := getAvailableControllerV2path(path.Join(c.path, cgroupControllers))
    25  	return "v2(" + c.path + ")" + ct.String()
    26  }
    27  
    28  // AddProc adds processes into the cgroup
    29  func (c *V2) AddProc(pids ...int) error {
    30  	return AddProcesses(path.Join(c.path, cgroupProcs), pids)
    31  }
    32  
    33  // Processes returns all processes within the cgroup
    34  func (c *V2) Processes() ([]int, error) {
    35  	return ReadProcesses(path.Join(c.path, cgroupProcs))
    36  }
    37  
    38  // New creates a sub-cgroup based on the existing one
    39  func (c *V2) New(name string) (Cgroup, error) {
    40  	if err := c.enableSubtreeControl(); err != nil {
    41  		return nil, err
    42  	}
    43  	v2 := &V2{
    44  		path: path.Join(c.path, name),
    45  	}
    46  	if err := os.Mkdir(v2.path, dirPerm); err != nil {
    47  		if !os.IsExist(err) {
    48  			return nil, err
    49  		}
    50  		v2.existing = true
    51  	}
    52  	return v2, nil
    53  }
    54  
    55  // Nest creates a sub-cgroup, moves current process into that cgroup
    56  func (c *V2) Nest(name string) (Cgroup, error) {
    57  	v2 := &V2{
    58  		path: path.Join(c.path, name),
    59  	}
    60  	if err := os.Mkdir(v2.path, dirPerm); err != nil {
    61  		if !os.IsExist(err) {
    62  			return nil, err
    63  		}
    64  		v2.existing = true
    65  	}
    66  	p, err := c.Processes()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	if err := v2.AddProc(p...); err != nil {
    71  		return nil, err
    72  	}
    73  	if err := c.enableSubtreeControl(); err != nil {
    74  		return nil, err
    75  	}
    76  	return v2, nil
    77  }
    78  
    79  func (c *V2) enableSubtreeControl() error {
    80  	c.subtreeOnce.Do(func() {
    81  		ct, err := getAvailableControllerV2path(path.Join(c.path, cgroupControllers))
    82  		if err != nil {
    83  			c.subtreeErr = err
    84  			return
    85  		}
    86  		ect, err := getAvailableControllerV2path(path.Join(c.path, cgroupSubtreeControl))
    87  		if err != nil {
    88  			c.subtreeErr = err
    89  			return
    90  		}
    91  		if ect.Contains(ct) {
    92  			return
    93  		}
    94  		s := ct.Names()
    95  		controlMsg := []byte("+" + strings.Join(s, " +"))
    96  		c.subtreeErr = writeFile(path.Join(c.path, cgroupSubtreeControl), controlMsg, filePerm)
    97  	})
    98  	return c.subtreeErr
    99  }
   100  
   101  // Random creates a sub-cgroup based on the existing one but the name is randomly generated
   102  func (c *V2) Random(pattern string) (Cgroup, error) {
   103  	return randomBuild(pattern, c.New)
   104  }
   105  
   106  // Destroy destroys the cgroup
   107  func (c *V2) Destroy() error {
   108  	if !c.existing {
   109  		return remove(c.path)
   110  	}
   111  	return nil
   112  }
   113  
   114  // Existing returns true if the cgroup was opened rather than created
   115  func (c *V2) Existing() bool {
   116  	return c.existing
   117  }
   118  
   119  // CPUUsage reads cpu.stat usage_usec
   120  func (c *V2) CPUUsage() (uint64, error) {
   121  	b, err := c.ReadFile("cpu.stat")
   122  	if err != nil {
   123  		return 0, err
   124  	}
   125  	s := bufio.NewScanner(bytes.NewReader(b))
   126  	for s.Scan() {
   127  		parts := strings.Fields(s.Text())
   128  		if len(parts) == 2 && parts[0] == "usage_usec" {
   129  			v, err := strconv.Atoi(parts[1])
   130  			if err != nil {
   131  				return 0, err
   132  			}
   133  			return uint64(v) * 1000, nil // to ns
   134  		}
   135  	}
   136  	return 0, os.ErrNotExist
   137  }
   138  
   139  // MemoryUsage reads memory.current
   140  func (c *V2) MemoryUsage() (uint64, error) {
   141  	return c.ReadUint("memory.current")
   142  }
   143  
   144  // MemoryMaxUsage reads memory.peak
   145  func (c *V2) MemoryMaxUsage() (uint64, error) {
   146  	return c.ReadUint("memory.peak")
   147  }
   148  
   149  // SetCPUBandwidth set cpu.max quota period
   150  func (c *V2) SetCPUBandwidth(quota, period uint64) error {
   151  	content := strconv.FormatUint(quota, 10) + " " + strconv.FormatUint(period, 10)
   152  	return c.WriteFile("cpu.max", []byte(content))
   153  }
   154  
   155  // SetCPUSet sets cpuset.cpus
   156  func (c *V2) SetCPUSet(content []byte) error {
   157  	return c.WriteFile("cpuset.cpus", content)
   158  }
   159  
   160  // SetMemoryLimit memory.max
   161  func (c *V2) SetMemoryLimit(l uint64) error {
   162  	return c.WriteUint("memory.max", l)
   163  }
   164  
   165  // SetProcLimit pids.max
   166  func (c *V2) SetProcLimit(l uint64) error {
   167  	return c.WriteUint("pids.max", l)
   168  }
   169  
   170  // WriteUint writes uint64 into given file
   171  func (c *V2) WriteUint(filename string, i uint64) error {
   172  	return c.WriteFile(filename, []byte(strconv.FormatUint(i, 10)))
   173  }
   174  
   175  // ReadUint read uint64 from given file
   176  func (c *V2) ReadUint(filename string) (uint64, error) {
   177  	b, err := c.ReadFile(filename)
   178  	if err != nil {
   179  		return 0, err
   180  	}
   181  	s, err := strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64)
   182  	if err != nil {
   183  		return 0, err
   184  	}
   185  	return s, nil
   186  }
   187  
   188  // WriteFile writes cgroup file and handles potential EINTR error while writes to
   189  // the slow device (cgroup)
   190  func (c *V2) WriteFile(name string, content []byte) error {
   191  	p := path.Join(c.path, name)
   192  	return writeFile(p, content, filePerm)
   193  }
   194  
   195  // ReadFile reads cgroup file and handles potential EINTR error while read to
   196  // the slow device (cgroup)
   197  func (c *V2) ReadFile(name string) ([]byte, error) {
   198  	p := path.Join(c.path, name)
   199  	return readFile(p)
   200  }