Skip to content

Commit 0de39be

Browse files
committed
Add cgroupV2 CPUQuotaPeriodUSec support
Signed-off-by: Austin Vazquez <[email protected]>
1 parent 32dca23 commit 0de39be

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

cgroup2/cpu.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ func NewCPUMax(quota *int64, period *uint64) CPUMax {
2929
if quota != nil {
3030
max = strconv.FormatInt(*quota, 10)
3131
}
32-
return CPUMax(strings.Join([]string{max, strconv.FormatUint(*period, 10)}, " "))
32+
duration := ""
33+
if period != nil {
34+
duration = strconv.FormatUint(*period, 10)
35+
}
36+
return CPUMax(strings.Join([]string{max, duration}, " "))
3337
}
3438

3539
type CPU struct {

cgroup2/cpuv2_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,11 @@ func TestExtractQuotaAndPeriod(t *testing.T) {
101101

102102
assert.Equal(t, int64(math.MaxInt64), tquota2)
103103
assert.Equal(t, period, tPeriod2)
104+
105+
// case with nil period will extract as zero value.
106+
cpuMax3 := NewCPUMax(&quota, nil)
107+
tquota3, tPeriod3 := cpuMax3.extractQuotaAndPeriod()
108+
109+
assert.Equal(t, quota, tquota3)
110+
assert.Equal(t, uint64(0), tPeriod3)
104111
}

cgroup2/manager.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path/filepath"
2828
"strconv"
2929
"strings"
30+
"sync"
3031
"time"
3132

3233
"github.com/containerd/cgroups/v3/cgroup2/stats"
@@ -47,7 +48,12 @@ const (
4748
defaultSlice = "system.slice"
4849
)
4950

50-
var canDelegate bool
51+
var (
52+
canDelegate bool
53+
54+
versionOnce sync.Once
55+
version int
56+
)
5157

5258
type Event struct {
5359
Low uint64
@@ -876,12 +882,29 @@ func NewSystemd(slice, group string, pid int, resources *Resources) (*Manager, e
876882

877883
if resources.CPU != nil && resources.CPU.Max != "" {
878884
quota, period := resources.CPU.Max.extractQuotaAndPeriod()
885+
if period != 0 {
886+
// systemd only supports CPUQuotaPeriodUSec since v242
887+
if sdVer := systemdVersion(conn); sdVer >= 242 {
888+
properties = append(properties, newSystemdProperty("CPUQuotaPeriodUSec", period))
889+
} else {
890+
log.G(context.TODO()).Debugf("systemd v%d is too old to support CPUQuotaPeriodSec "+
891+
" (setting will still be applied to cgroupfs)", sdVer)
892+
}
893+
}
894+
879895
// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
880896
// corresponds to USEC_INFINITY in systemd
881897
// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
882898
// always setting a property value ensures we can apply a quota and remove it later
883899
cpuQuotaPerSecUSec := uint64(math.MaxUint64)
884900
if quota > 0 {
901+
if period == 0 {
902+
// Default kernel value for cpu quota period is 100000 us (100 ms), same for v1 and v2.
903+
// v1: https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html and
904+
// v2: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.htmlassume the default
905+
period = 100000
906+
}
907+
885908
// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
886909
// (integer percentage of CPU) internally. This means that if a fractional percent of
887910
// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
@@ -915,6 +938,41 @@ func NewSystemd(slice, group string, pid int, resources *Resources) (*Manager, e
915938
}, nil
916939
}
917940

941+
// Adapted from https://github.com/opencontainers/cgroups/blob/9657f5a18b8d60a0f39fbb34d0cb7771e28e6278/systemd/common.go#L245-L281
942+
func systemdVersion(conn *systemdDbus.Conn) int {
943+
versionOnce.Do(func() {
944+
version = -1
945+
verStr, err := conn.GetManagerProperty("Version")
946+
if err == nil {
947+
version, err = systemdVersionAtoi(verStr)
948+
}
949+
950+
if err != nil {
951+
log.G(context.TODO()).WithError(err).Error("Unable to get systemd version")
952+
}
953+
})
954+
955+
return version
956+
}
957+
958+
func systemdVersionAtoi(str string) (int, error) {
959+
// Unconditionally remove the leading prefix ("v).
960+
str = strings.TrimLeft(str, `"v`)
961+
// Match on the first integer we can grab.
962+
for i := range len(str) {
963+
if str[i] < '0' || str[i] > '9' {
964+
// First non-digit: cut the tail.
965+
str = str[:i]
966+
break
967+
}
968+
}
969+
ver, err := strconv.Atoi(str)
970+
if err != nil {
971+
return -1, fmt.Errorf("can't parse version: %w", err)
972+
}
973+
return ver, nil
974+
}
975+
918976
func startUnit(conn *systemdDbus.Conn, group string, properties []systemdDbus.Property, ignoreExists bool) error {
919977
ctx := context.TODO()
920978

0 commit comments

Comments
 (0)