@@ -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
5258type 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+
918976func startUnit (conn * systemdDbus.Conn , group string , properties []systemdDbus.Property , ignoreExists bool ) error {
919977 ctx := context .TODO ()
920978
0 commit comments