2020-08-11 19:54:13 +00:00
|
|
|
package cgroup
|
|
|
|
|
|
|
|
import (
|
2020-09-22 20:26:44 +00:00
|
|
|
"io/ioutil"
|
2020-08-11 19:54:13 +00:00
|
|
|
"os"
|
2021-02-08 13:46:22 +00:00
|
|
|
"path"
|
2020-08-11 19:54:13 +00:00
|
|
|
"runtime"
|
2020-09-22 20:26:44 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-12-08 18:49:32 +00:00
|
|
|
"sync"
|
2020-08-11 19:54:13 +00:00
|
|
|
)
|
|
|
|
|
2020-12-08 18:49:32 +00:00
|
|
|
// AvailableCPUs returns the number of available CPU cores for the app.
|
|
|
|
func AvailableCPUs() int {
|
|
|
|
availableCPUsOnce.Do(updateGOMAXPROCSToCPUQuota)
|
|
|
|
return runtime.GOMAXPROCS(-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
var availableCPUsOnce sync.Once
|
|
|
|
|
|
|
|
// updateGOMAXPROCSToCPUQuota updates GOMAXPROCS to cgroup CPU quota if GOMAXPROCS isn't set in environment var.
|
|
|
|
func updateGOMAXPROCSToCPUQuota() {
|
2020-08-11 19:54:13 +00:00
|
|
|
if v := os.Getenv("GOMAXPROCS"); v != "" {
|
2020-08-28 06:40:53 +00:00
|
|
|
// Do not override explicitly set GOMAXPROCS.
|
2020-08-11 19:54:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
q := getCPUQuota()
|
|
|
|
if q <= 0 {
|
|
|
|
// Do not change GOMAXPROCS
|
|
|
|
return
|
|
|
|
}
|
|
|
|
gomaxprocs := int(q + 0.5)
|
2020-08-28 06:40:53 +00:00
|
|
|
numCPU := runtime.NumCPU()
|
|
|
|
if gomaxprocs > numCPU {
|
|
|
|
// There is no sense in setting more GOMAXPROCS than the number of available CPU cores.
|
|
|
|
return
|
|
|
|
}
|
2020-08-11 19:54:13 +00:00
|
|
|
if gomaxprocs <= 0 {
|
|
|
|
gomaxprocs = 1
|
|
|
|
}
|
|
|
|
runtime.GOMAXPROCS(gomaxprocs)
|
|
|
|
}
|
|
|
|
|
2021-02-08 13:46:22 +00:00
|
|
|
func getCPUStat(sysPath, cgroupPath, statName string) (int64, error) {
|
|
|
|
n, err := readInt64(path.Join(sysPath, statName))
|
|
|
|
if err == nil {
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
subPath, err := grepFirstMatch(cgroupPath, "cpu,", 2, ":")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return readInt64(path.Join(sysPath, subPath, statName))
|
|
|
|
}
|
|
|
|
|
2020-08-11 19:54:13 +00:00
|
|
|
func getCPUQuota() float64 {
|
2021-02-08 13:46:22 +00:00
|
|
|
quotaUS, err := getCPUStat("/sys/fs/cgroup/cpu", "/proc/self/cgroup", "cpu.cfs_quota_us")
|
2020-08-11 19:54:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
2020-09-22 20:26:44 +00:00
|
|
|
if quotaUS <= 0 {
|
|
|
|
// The quota isn't set. This may be the case in multilevel containers.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/685#issuecomment-674423728
|
|
|
|
return getOnlineCPUCount()
|
|
|
|
}
|
2021-02-08 13:46:22 +00:00
|
|
|
periodUS, err := getCPUStat("/sys/fs/cgroup/cpu", "/proc/self/cgroup", "cpu.cfs_period_us")
|
2020-08-11 19:54:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return float64(quotaUS) / float64(periodUS)
|
|
|
|
}
|
2020-09-22 20:26:44 +00:00
|
|
|
|
|
|
|
func getOnlineCPUCount() float64 {
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/685#issuecomment-674423728
|
|
|
|
data, err := ioutil.ReadFile("/sys/devices/system/cpu/online")
|
|
|
|
if err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
n := float64(countCPUs(string(data)))
|
|
|
|
if n <= 0 {
|
|
|
|
return -1
|
|
|
|
}
|
2020-09-29 10:54:25 +00:00
|
|
|
return n
|
2020-09-22 20:26:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func countCPUs(data string) int {
|
|
|
|
data = strings.TrimSpace(data)
|
|
|
|
n := 0
|
|
|
|
for _, s := range strings.Split(data, ",") {
|
|
|
|
n++
|
|
|
|
if !strings.Contains(s, "-") {
|
|
|
|
if _, err := strconv.Atoi(s); err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bounds := strings.Split(s, "-")
|
|
|
|
if len(bounds) != 2 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
start, err := strconv.Atoi(bounds[0])
|
|
|
|
if err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
end, err := strconv.Atoi(bounds[1])
|
|
|
|
if err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
n += end - start
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|