github.com/lingyao2333/mo-zero@v1.4.1/core/stat/internal/cgroup_linux.go (about) 1 package internal 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "path" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/lingyao2333/mo-zero/core/iox" 14 "github.com/lingyao2333/mo-zero/core/lang" 15 "golang.org/x/sys/unix" 16 ) 17 18 const ( 19 cgroupDir = "/sys/fs/cgroup" 20 cpuStatFile = cgroupDir + "/cpu.stat" 21 cpusetFile = cgroupDir + "/cpuset.cpus.effective" 22 ) 23 24 var ( 25 isUnifiedOnce sync.Once 26 isUnified bool 27 inUserNS bool 28 nsOnce sync.Once 29 ) 30 31 type cgroup interface { 32 cpuQuotaUs() (int64, error) 33 cpuPeriodUs() (uint64, error) 34 cpus() ([]uint64, error) 35 usageAllCpus() (uint64, error) 36 } 37 38 func currentCgroup() (cgroup, error) { 39 if isCgroup2UnifiedMode() { 40 return currentCgroupV2() 41 } 42 43 return currentCgroupV1() 44 } 45 46 type cgroupV1 struct { 47 cgroups map[string]string 48 } 49 50 func (c *cgroupV1) cpuQuotaUs() (int64, error) { 51 data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_quota_us")) 52 if err != nil { 53 return 0, err 54 } 55 56 return strconv.ParseInt(data, 10, 64) 57 } 58 59 func (c *cgroupV1) cpuPeriodUs() (uint64, error) { 60 data, err := iox.ReadText(path.Join(c.cgroups["cpu"], "cpu.cfs_period_us")) 61 if err != nil { 62 return 0, err 63 } 64 65 return parseUint(data) 66 } 67 68 func (c *cgroupV1) cpus() ([]uint64, error) { 69 data, err := iox.ReadText(path.Join(c.cgroups["cpuset"], "cpuset.cpus")) 70 if err != nil { 71 return nil, err 72 } 73 74 return parseUints(data) 75 } 76 77 func (c *cgroupV1) usageAllCpus() (uint64, error) { 78 data, err := iox.ReadText(path.Join(c.cgroups["cpuacct"], "cpuacct.usage")) 79 if err != nil { 80 return 0, err 81 } 82 83 return parseUint(data) 84 } 85 86 type cgroupV2 struct { 87 cgroups map[string]string 88 } 89 90 func (c *cgroupV2) cpuQuotaUs() (int64, error) { 91 data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_quota_us")) 92 if err != nil { 93 return 0, err 94 } 95 96 return strconv.ParseInt(data, 10, 64) 97 } 98 99 func (c *cgroupV2) cpuPeriodUs() (uint64, error) { 100 data, err := iox.ReadText(path.Join(cgroupDir, "cpu.cfs_period_us")) 101 if err != nil { 102 return 0, err 103 } 104 105 return parseUint(data) 106 } 107 108 func (c *cgroupV2) cpus() ([]uint64, error) { 109 data, err := iox.ReadText(cpusetFile) 110 if err != nil { 111 return nil, err 112 } 113 114 return parseUints(data) 115 } 116 117 func (c *cgroupV2) usageAllCpus() (uint64, error) { 118 usec, err := parseUint(c.cgroups["usage_usec"]) 119 if err != nil { 120 return 0, err 121 } 122 123 return usec * uint64(time.Microsecond), nil 124 } 125 126 func currentCgroupV1() (cgroup, error) { 127 cgroupFile := fmt.Sprintf("/proc/%d/cgroup", os.Getpid()) 128 lines, err := iox.ReadTextLines(cgroupFile, iox.WithoutBlank()) 129 if err != nil { 130 return nil, err 131 } 132 133 cgroups := make(map[string]string) 134 for _, line := range lines { 135 cols := strings.Split(line, ":") 136 if len(cols) != 3 { 137 return nil, fmt.Errorf("invalid cgroup line: %s", line) 138 } 139 140 subsys := cols[1] 141 // only read cpu staff 142 if !strings.HasPrefix(subsys, "cpu") { 143 continue 144 } 145 146 // https://man7.org/linux/man-pages/man7/cgroups.7.html 147 // comma-separated list of controllers for cgroup version 1 148 fields := strings.Split(subsys, ",") 149 for _, val := range fields { 150 cgroups[val] = path.Join(cgroupDir, val) 151 } 152 } 153 154 return &cgroupV1{ 155 cgroups: cgroups, 156 }, nil 157 } 158 159 func currentCgroupV2() (cgroup, error) { 160 lines, err := iox.ReadTextLines(cpuStatFile, iox.WithoutBlank()) 161 if err != nil { 162 return nil, err 163 } 164 165 cgroups := make(map[string]string) 166 for _, line := range lines { 167 cols := strings.Fields(line) 168 if len(cols) != 2 { 169 return nil, fmt.Errorf("invalid cgroupV2 line: %s", line) 170 } 171 172 cgroups[cols[0]] = cols[1] 173 } 174 175 return &cgroupV2{ 176 cgroups: cgroups, 177 }, nil 178 } 179 180 // isCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode. 181 func isCgroup2UnifiedMode() bool { 182 isUnifiedOnce.Do(func() { 183 var st unix.Statfs_t 184 err := unix.Statfs(cgroupDir, &st) 185 if err != nil { 186 if os.IsNotExist(err) && runningInUserNS() { 187 // ignore the "not found" error if running in userns 188 isUnified = false 189 return 190 } 191 panic(fmt.Sprintf("cannot statfs cgroup root: %s", err)) 192 } 193 isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC 194 }) 195 196 return isUnified 197 } 198 199 func parseUint(s string) (uint64, error) { 200 v, err := strconv.ParseInt(s, 10, 64) 201 if err != nil { 202 if err.(*strconv.NumError).Err == strconv.ErrRange { 203 return 0, nil 204 } 205 206 return 0, fmt.Errorf("cgroup: bad int format: %s", s) 207 } 208 209 if v < 0 { 210 return 0, nil 211 } 212 213 return uint64(v), nil 214 } 215 216 func parseUints(val string) ([]uint64, error) { 217 if val == "" { 218 return nil, nil 219 } 220 221 ints := make(map[uint64]lang.PlaceholderType) 222 cols := strings.Split(val, ",") 223 for _, r := range cols { 224 if strings.Contains(r, "-") { 225 fields := strings.SplitN(r, "-", 2) 226 min, err := parseUint(fields[0]) 227 if err != nil { 228 return nil, fmt.Errorf("cgroup: bad int list format: %s", val) 229 } 230 231 max, err := parseUint(fields[1]) 232 if err != nil { 233 return nil, fmt.Errorf("cgroup: bad int list format: %s", val) 234 } 235 236 if max < min { 237 return nil, fmt.Errorf("cgroup: bad int list format: %s", val) 238 } 239 240 for i := min; i <= max; i++ { 241 ints[i] = lang.Placeholder 242 } 243 } else { 244 v, err := parseUint(r) 245 if err != nil { 246 return nil, err 247 } 248 249 ints[v] = lang.Placeholder 250 } 251 } 252 253 var sets []uint64 254 for k := range ints { 255 sets = append(sets, k) 256 } 257 258 return sets, nil 259 } 260 261 // runningInUserNS detects whether we are currently running in an user namespace. 262 func runningInUserNS() bool { 263 nsOnce.Do(func() { 264 file, err := os.Open("/proc/self/uid_map") 265 if err != nil { 266 // This kernel-provided file only exists if user namespaces are supported 267 return 268 } 269 defer file.Close() 270 271 buf := bufio.NewReader(file) 272 l, _, err := buf.ReadLine() 273 if err != nil { 274 return 275 } 276 277 line := string(l) 278 var a, b, c int64 279 fmt.Sscanf(line, "%d %d %d", &a, &b, &c) 280 281 /* 282 * We assume we are in the initial user namespace if we have a full 283 * range - 4294967295 uids starting at uid 0. 284 */ 285 if a == 0 && b == 0 && c == 4294967295 { 286 return 287 } 288 inUserNS = true 289 }) 290 291 return inUserNS 292 }