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 }