// Copyright 2025 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package cgroupimport ()// Include explicit NUL to be sure we include it in the slice.const ( v2MaxFile = "/cpu.max\x00" v1QuotaFile = "/cpu.cfs_quota_us\x00" v1PeriodFile = "/cpu.cfs_period_us\x00")// CPU owns the FDs required to read the CPU limit from a cgroup.typeCPUstruct { version Version// For cgroup v1, this is cpu.cfs_quota_us. // For cgroup v2, this is cpu.max. quotaFD int// For cgroup v1, this is cpu.cfs_period_us. // For cgroup v2, this is unused. periodFD int}func ( CPU) () {switch .version {caseV1:linux.Close(.quotaFD)linux.Close(.periodFD)caseV2:linux.Close(.quotaFD)default:throw("impossible cgroup version") }}func checkBufferSize( []byte, int) {iflen() != {println("runtime: cgroup buffer length", len(), "want", )throw("runtime: cgroup invalid buffer length") }}// OpenCPU returns a CPU for the CPU cgroup containing the current process, or// ErrNoCgroup if the process is not in a CPU cgroup.//// scratch must have length ScratchSize.func ( []byte) (CPU, error) {checkBufferSize(, ScratchSize) := [:PathSize] := [PathSize:] , , := FindCPU(, )if != nil {returnCPU{}, }switch {case1: := copy([:], v1QuotaFile) := [:+] , := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)if != 0 {// This may fail if this process was migrated out of // the cgroup found by FindCPU and that cgroup has been // deleted.returnCPU{}, errSyscallFailed } = copy([:], v1PeriodFile) = [:+] , := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)if != 0 {// This may fail if this process was migrated out of // the cgroup found by FindCPU and that cgroup has been // deleted.returnCPU{}, errSyscallFailed } := CPU{version: 1,quotaFD: ,periodFD: , }return , nilcase2: := copy([:], v2MaxFile) := [:+] , := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)if != 0 {// This may fail if this process was migrated out of // the cgroup found by FindCPU and that cgroup has been // deleted.returnCPU{}, errSyscallFailed } := CPU{version: 2,quotaFD: ,periodFD: -1, }return , nildefault:throw("impossible cgroup version")panic("unreachable") }}// Returns average CPU throughput limit from the cgroup, or ok false if there// is no limit.func ( CPU) (float64, bool, error) {switch .version {case1: , := readV1Number(.quotaFD)if != nil {return0, false, errMalformedFile }if < 0 {// No limit.return0, false, nil } , := readV1Number(.periodFD)if != nil {return0, false, errMalformedFile }returnfloat64() / float64(), true, nilcase2:// quotaFD is the cpu.max FD.returnreadV2Limit(.quotaFD)default:throw("impossible cgroup version")panic("unreachable") }}// Returns the value from the quota/period file.func readV1Number( int) (int64, error) {// The format of the file is "<value>\n" where the value is in // int64 microseconds and, if quota, may be -1 to indicate no limit. // // MaxInt64 requires 19 bytes to display in base 10, thus the // conservative max size of this file is 19 + 1 (newline) = 20 bytes. // We'll provide a bit more for good measure. // // Always read from the beginning of the file to get a fresh value.var [64]byte , := linux.Pread(, [:], 0)if != 0 {return0, errSyscallFailed }if == len() {return0, errMalformedFile } := [:]returnparseV1Number()}// Returns CPU throughput limit, or ok false if there is no limit.func readV2Limit( int) (float64, bool, error) {// The format of the file is "<quota> <period>\n" where quota and // period are microseconds and quota may be "max" to indicate no limit. // // Note that the kernel is inconsistent about whether the values are // uint64 or int64: values are parsed as uint64 but printed as int64. // See kernel/sched/core.c:cpu_max_{show,write}. // // In practice, the kernel limits the period to 1s (1000000us) (see // max_cfs_quota_period), and the quota to (1<<44)us (see // max_cfs_runtime), so these values can't get large enough for the // distinction to matter. // // MaxInt64 requires 19 bytes to display in base 10, thus the // conservative max size of this file is 19 + 19 + 1 (space) + 1 // (newline) = 40 bytes. We'll provide a bit more for good measure. // // Always read from the beginning of the file to get a fresh value.var [64]byte , := linux.Pread(, [:], 0)if != 0 {return0, false, errSyscallFailed }if == len() {return0, false, errMalformedFile } := [:]returnparseV2Limit()}// FindCPU finds the path to the CPU cgroup that this process is a member of// and places it in out. scratch is a scratch buffer for internal use.//// out must have length PathSize. scratch must have length ParseSize.//// Returns the number of bytes written to out and the cgroup version (1 or 2).//// Returns ErrNoCgroup if the process is not in a CPU cgroup.func ( []byte, []byte) (int, Version, error) {checkBufferSize(, PathSize)checkBufferSize(, ParseSize)// The cgroup path is <cgroup mount point> + <relative path>. // relative path is the cgroup relative to the mount root. , , := FindCPUCgroup(, )if != nil {return0, 0, } , = FindCPUMountPoint(, [:], , )return , , }// FindCPUCgroup finds the path to the CPU cgroup that this process is a member of// and places it in out. scratch is a scratch buffer for internal use.//// out must have length PathSize. scratch must have length ParseSize.//// Returns the number of bytes written to out and the cgroup version (1 or 2).//// Returns ErrNoCgroup if the process is not in a CPU cgroup.func ( []byte, []byte) (int, Version, error) { := []byte("/proc/self/cgroup\x00") , := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)if == linux.ENOENT {return0, 0, ErrNoCgroup } elseif != 0 {return0, 0, errSyscallFailed }// The relative path always starts with /, so we can directly append it // to the mount point. , , := parseCPUCgroup(, linux.Read, [:], )if != nil {linux.Close()return0, 0, }linux.Close()return , , nil}// FindCPUMountPoint finds the mount point containing the specified cgroup and// version with cpu controller, and compose the full path to the cgroup in out.// scratch is a scratch buffer for internal use.//// out must have length PathSize, may overlap with cgroup.// scratch must have length ParseSize.//// Returns the number of bytes written to out.//// Returns ErrNoCgroup if no matching mount point is found.func (, []byte, Version, []byte) (int, error) {checkBufferSize(, PathSize)checkBufferSize(, ParseSize) := []byte("/proc/self/mountinfo\x00") , := linux.Open(&[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)if == linux.ENOENT {return0, ErrNoCgroup } elseif != 0 {return0, errSyscallFailed } , := parseCPUMount(, linux.Read, , , , )if != nil {linux.Close()return0, }linux.Close()return , nil}
The pages are generated with Goldsv0.8.3-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.