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 }