From fd95216684463f30144d5f5e41b6f54528feedee Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 5 Jun 2025 13:03:20 -0700 Subject: [PATCH 1/2] ConvertCPUSharesToCgroupV2Value: improve The old formula, while correctly converting the cgroup v1 range to cgroup v2 range, had a few issues: 1. When cgroup v1 value is out of range, it returned invalid cgroup v2 value, leading to vague errors down the line (see [1]). 2. Default value cgroup v1 shares of 1024 is converted to 39, which is much less than the cgroup v2 default weight of 100 (see [2], [3]). The new conversion formula fixes both issues (see discussion in [2] for more details). Amend the test case accordingly. [1]: https://github.com/opencontainers/runc/issues/4755 [2]: https://github.com/kubernetes/kubernetes/issues/131216 [3]: https://github.com/opencontainers/runc/issues/4772 Signed-off-by: Kir Kolyshkin --- utils.go | 27 +++++++++++++++++++++------ utils_test.go | 21 ++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/utils.go b/utils.go index 98b6a07..95b3310 100644 --- a/utils.go +++ b/utils.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "math" "os" "path/filepath" "strconv" @@ -413,16 +414,30 @@ func WriteCgroupProc(dir string, pid int) error { return err } -// Since the OCI spec is designed for cgroup v1, in some cases -// there is need to convert from the cgroup v1 configuration to cgroup v2 -// the formula for cpuShares is y = (1 + ((x - 2) * 9999) / 262142) -// convert from [2-262144] to [1-10000] -// 262144 comes from Linux kernel definition "#define MAX_SHARES (1UL << 18)" +// 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. func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { + // The value of 0 means "unset". if cpuShares == 0 { return 0 } - return (1 + ((cpuShares-2)*9999)/262142) + 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))) } // ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec diff --git a/utils_test.go b/utils_test.go index 2d30373..82cef69 100644 --- a/utils_test.go +++ b/utils_test.go @@ -535,10 +535,25 @@ func TestGetHugePageSizeImpl(t *testing.T) { } func TestConvertCPUSharesToCgroupV2Value(t *testing.T) { + const ( + sharesMin = 2 + sharesMax = 262144 + sharesDef = 1024 + weightMin = 1 + weightMax = 10000 + weightDef = 100 + unset = 0 + ) cases := map[uint64]uint64{ - 0: 0, - 2: 1, - 262144: 10000, + 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 i, expected := range cases { got := ConvertCPUSharesToCgroupV2Value(i) From 75f91bdd601fdc7a75332da38d8a5edf0f74ebb7 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Wed, 11 Jun 2025 13:54:22 -0700 Subject: [PATCH 2/2] TestConvertCPUSharesToCgroupV2Value: nits Rename some variables, and simplify the error message using the got/want pattern. Signed-off-by: Kir Kolyshkin --- utils_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils_test.go b/utils_test.go index 82cef69..79023b7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -555,10 +555,10 @@ func TestConvertCPUSharesToCgroupV2Value(t *testing.T) { sharesMax: weightMax, // Maximum. sharesMax + 1: weightMax, // Above the maximum (out of range). } - for i, expected := range cases { - got := ConvertCPUSharesToCgroupV2Value(i) - if got != expected { - t.Errorf("expected ConvertCPUSharesToCgroupV2Value(%d) to be %d, got %d", i, expected, got) + for shares, want := range cases { + got := ConvertCPUSharesToCgroupV2Value(shares) + if got != want { + t.Errorf("ConvertCPUSharesToCgroupV2Value(%d): got %d, want %d", shares, got, want) } } }