diff --git a/cgroup2/utils.go b/cgroup2/utils.go index adc0e03f..1f6ecb09 100644 --- a/cgroup2/utils.go +++ b/cgroup2/utils.go @@ -146,6 +146,35 @@ func parseCgroupFromReader(r io.Reader) (string, error) { return "", fmt.Errorf("cgroup path not found") } +// ConvertCPUSharesToCgroupV2Value converts CPU shares, used by cgroup v1, +// to CPU weight, used by cgroup v2. +// +// Cgroup v1 CPU shares has a range of [2^1...2^18], i.e. [2...262144], +// and the default value is 1024. +// +// Cgroup v2 CPU weight has a range of [10^0...10^4], i.e. [1...10000], +// and the default value is 100. +// +// Taken from https://github.com/opencontainers/cgroups/blob/v0.0.5/utils.go#L417-L441 +// (Apache License 2.0) +func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { + // The value of 0 means "unset". + if cpuShares == 0 { + return 0 + } + if cpuShares <= 2 { + return 1 + } + if cpuShares >= 262144 { + return 10000 + } + l := math.Log2(float64(cpuShares)) + // Quadratic function which fits min, max, and default. + exponent := (l*l+125*l)/612.0 - 7.0/34.0 + + return uint64(math.Ceil(math.Pow(10, exponent))) +} + // ToResources converts the oci LinuxResources struct into a // v2 Resources type for use with this package. // @@ -159,7 +188,7 @@ func ToResources(spec *specs.LinuxResources) *Resources { Mems: cpu.Mems, } if shares := cpu.Shares; shares != nil { - convertedWeight := 1 + ((*shares-2)*9999)/262142 + convertedWeight := ConvertCPUSharesToCgroupV2Value(*shares) resources.CPU.Weight = &convertedWeight } if period := cpu.Period; period != nil { diff --git a/cgroup2/utils_test.go b/cgroup2/utils_test.go index 37e315fb..f481fb4c 100644 --- a/cgroup2/utils_test.go +++ b/cgroup2/utils_test.go @@ -75,6 +75,38 @@ full avg10=1.00 avg60=1.01 avg300=1.00 total=157622356` assert.Equal(t, &st.Full, &expected.Full) } +// TestConvertCPUSharesToCgroupV2Value tests the ConvertCPUSharesToCgroupV2Value function. +// Taken from https://github.com/opencontainers/cgroups/blob/v0.0.5/utils_test.go#L537-L564 +// (Apache License 2.0) +func TestConvertCPUSharesToCgroupV2Value(t *testing.T) { + const ( + sharesMin = 2 + sharesMax = 262144 + sharesDef = 1024 + weightMin = 1 + weightMax = 10000 + weightDef = 100 + unset = 0 + ) + cases := map[uint64]uint64{ + unset: unset, + + sharesMin - 1: weightMin, // Below the minimum (out of range). + sharesMin: weightMin, // Minimum. + sharesMin + 1: weightMin + 1, // Just above the minimum. + sharesDef: weightDef, // Default. + sharesMax - 1: weightMax, // Just below the maximum. + sharesMax: weightMax, // Maximum. + sharesMax + 1: weightMax, // Above the maximum (out of range). + } + for shares, want := range cases { + got := ConvertCPUSharesToCgroupV2Value(shares) + if got != want { + t.Errorf("ConvertCPUSharesToCgroupV2Value(%d): got %d, want %d", shares, got, want) + } + } +} + func TestToResources(t *testing.T) { var ( quota int64 = 8000 @@ -84,7 +116,7 @@ func TestToResources(t *testing.T) { mem int64 = 300 swap int64 = 500 ) - weight := 1 + ((shares-2)*9999)/262142 + weight := ConvertCPUSharesToCgroupV2Value(shares) res := specs.LinuxResources{ CPU: &specs.LinuxCPU{Quota: "a, Period: &period, Shares: &shares}, Memory: &specs.LinuxMemory{Limit: &mem, Swap: &swap},