@@ -27,6 +27,7 @@ import (
27
27
"path/filepath"
28
28
"strconv"
29
29
"strings"
30
+ "sync"
30
31
"time"
31
32
32
33
"github.com/containerd/cgroups/v3/cgroup2/stats"
@@ -47,7 +48,12 @@ const (
47
48
defaultSlice = "system.slice"
48
49
)
49
50
50
- var canDelegate bool
51
+ var (
52
+ canDelegate bool
53
+
54
+ versionOnce sync.Once
55
+ version int
56
+ )
51
57
52
58
type Event struct {
53
59
Low uint64
@@ -876,12 +882,29 @@ func NewSystemd(slice, group string, pid int, resources *Resources) (*Manager, e
876
882
877
883
if resources .CPU != nil && resources .CPU .Max != "" {
878
884
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
+
879
895
// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
880
896
// corresponds to USEC_INFINITY in systemd
881
897
// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
882
898
// always setting a property value ensures we can apply a quota and remove it later
883
899
cpuQuotaPerSecUSec := uint64 (math .MaxUint64 )
884
900
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
+
885
908
// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
886
909
// (integer percentage of CPU) internally. This means that if a fractional percent of
887
910
// 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
915
938
}, nil
916
939
}
917
940
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
+
918
976
func startUnit (conn * systemdDbus.Conn , group string , properties []systemdDbus.Property , ignoreExists bool ) error {
919
977
ctx := context .TODO ()
920
978
0 commit comments