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 }