Skip to content

Commit a0f01f4

Browse files
kailun-qinZheaoli
authored andcommitted
libct/cg: add CFS bandwidth burst for CPU
Burstable CFS controller is introduced in Linux 5.14. This helps with parallel workloads that might be bursty. They can get throttled even when their average utilization is under quota. And they may be latency sensitive at the same time so that throttling them is undesired. This feature borrows time now against the future underrun, at the cost of increased interference against the other system users, by introducing cfs_burst_us into CFS bandwidth control to enact the cap on unused bandwidth accumulation, which will then used additionally for burst. The patch adds the support/control for CFS bandwidth burst. runtime-spec: opencontainers/runtime-spec#1120 Signed-off-by: Kailun Qin <[email protected]> Signed-off-by: Manjusaka <[email protected]>
1 parent 30f9f80 commit a0f01f4

File tree

11 files changed

+101
-2
lines changed

11 files changed

+101
-2
lines changed

contrib/completions/bash/runc

+1
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ _runc_update() {
732732
--blkio-weight
733733
--cpu-period
734734
--cpu-quota
735+
--cpu-burst
735736
--cpu-rt-period
736737
--cpu-rt-runtime
737738
--cpu-share

libcontainer/cgroups/fs/cpu.go

+27
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
8484
period = ""
8585
}
8686
}
87+
88+
var burst string
89+
if r.CpuBurst != 0 {
90+
burst = strconv.FormatUint(r.CpuBurst, 10)
91+
if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
92+
// Sometimes when the burst to be set is larger
93+
// than the current one, it is rejected by the kernel
94+
// (EINVAL) as old_quota/new_burst exceeds the parent
95+
// cgroup quota limit. If this happens and the quota is
96+
// going to be set, ignore the error for now and retry
97+
// after setting the quota.
98+
if !errors.Is(err, unix.ENOENT) {
99+
// this is a special trick for burst feature, the current systemd and low version of kernel will not support it.
100+
// So, an `no such file or directory` error would be raised, and we can ignore it .
101+
if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
102+
return err
103+
}
104+
}
105+
} else {
106+
burst = ""
107+
}
108+
}
87109
if r.CpuQuota != 0 {
88110
if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
89111
return err
@@ -93,6 +115,11 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
93115
return err
94116
}
95117
}
118+
if burst != "" {
119+
if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
120+
return err
121+
}
122+
}
96123
}
97124

98125
if r.CPUIdle != nil {

libcontainer/cgroups/fs/cpu_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func TestCpuSetBandWidth(t *testing.T) {
4545
const (
4646
quotaBefore = 8000
4747
quotaAfter = 5000
48+
burstBefore = 2000
49+
burstAfter = 1000
4850
periodBefore = 10000
4951
periodAfter = 7000
5052
rtRuntimeBefore = 8000
@@ -55,13 +57,15 @@ func TestCpuSetBandWidth(t *testing.T) {
5557

5658
writeFileContents(t, path, map[string]string{
5759
"cpu.cfs_quota_us": strconv.Itoa(quotaBefore),
60+
"cpu.cfs_burst_us": strconv.Itoa(burstBefore),
5861
"cpu.cfs_period_us": strconv.Itoa(periodBefore),
5962
"cpu.rt_runtime_us": strconv.Itoa(rtRuntimeBefore),
6063
"cpu.rt_period_us": strconv.Itoa(rtPeriodBefore),
6164
})
6265

6366
r := &configs.Resources{
6467
CpuQuota: quotaAfter,
68+
CpuBurst: burstAfter,
6569
CpuPeriod: periodAfter,
6670
CpuRtRuntime: rtRuntimeAfter,
6771
CpuRtPeriod: rtPeriodAfter,
@@ -79,6 +83,14 @@ func TestCpuSetBandWidth(t *testing.T) {
7983
t.Fatal("Got the wrong value, set cpu.cfs_quota_us failed.")
8084
}
8185

86+
burst, err := fscommon.GetCgroupParamUint(path, "cpu.cfs_burst_us")
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
if burst != burstAfter {
91+
t.Fatal("Got the wrong value, set cpu.cfs_burst_us failed.")
92+
}
93+
8294
period, err := fscommon.GetCgroupParamUint(path, "cpu.cfs_period_us")
8395
if err != nil {
8496
t.Fatal(err)

libcontainer/cgroups/fs/fs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ func (m *Manager) Set(r *configs.Resources) error {
191191
if path == "" {
192192
// We never created a path for this cgroup, so we cannot set
193193
// limits for it (though we have already tried at this point).
194-
return fmt.Errorf("cannot set %s limit: container could not join or create cgroup", sys.Name())
194+
return fmt.Errorf("cannot set %s limit: container could not join or create cgroup, and the error is %v", sys.Name(), err)
195195
}
196196
return err
197197
}

libcontainer/cgroups/fs2/cpu.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package fs2
22

33
import (
44
"bufio"
5+
"errors"
6+
"golang.org/x/sys/unix"
57
"os"
68
"strconv"
79

@@ -11,7 +13,7 @@ import (
1113
)
1214

1315
func isCpuSet(r *configs.Resources) bool {
14-
return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0 || r.CPUIdle != nil
16+
return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0 || r.CPUIdle != nil || r.CpuBurst != 0
1517
}
1618

1719
func setCpu(dirPath string, r *configs.Resources) error {
@@ -32,6 +34,23 @@ func setCpu(dirPath string, r *configs.Resources) error {
3234
}
3335
}
3436

37+
var burst string
38+
if r.CpuBurst != 0 {
39+
burst = strconv.FormatUint(r.CpuBurst, 10)
40+
if err := cgroups.WriteFile(dirPath, "cpu.max.burst", burst); err != nil {
41+
// Sometimes when the burst to be set is larger
42+
// than the current one, it is rejected by the kernel
43+
// (EINVAL) as old_quota/new_burst exceeds the parent
44+
// cgroup quota limit. If this happens and the quota is
45+
// going to be set, ignore the error for now and retry
46+
// after setting the quota.
47+
if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
48+
return err
49+
}
50+
} else {
51+
burst = ""
52+
}
53+
}
3554
if r.CpuQuota != 0 || r.CpuPeriod != 0 {
3655
str := "max"
3756
if r.CpuQuota > 0 {
@@ -47,6 +66,11 @@ func setCpu(dirPath string, r *configs.Resources) error {
4766
if err := cgroups.WriteFile(dirPath, "cpu.max", str); err != nil {
4867
return err
4968
}
69+
if burst != "" {
70+
if err := cgroups.WriteFile(dirPath, "cpu.max.burst", burst); err != nil {
71+
return err
72+
}
73+
}
5074
}
5175

5276
return nil

libcontainer/configs/cgroup_linux.go

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ type Resources struct {
6969
// CPU hardcap limit (in usecs). Allowed cpu time in a given period.
7070
CpuQuota int64 `json:"cpu_quota"`
7171

72+
// CPU hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst in a given period.
73+
CpuBurst uint64 `json:"cpu_burst"`
74+
7275
// CPU period to be used for hardcapping (in usecs). 0 to use system default.
7376
CpuPeriod uint64 `json:"cpu_period"`
7477

libcontainer/specconv/spec_linux.go

+3
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,9 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*confi
737737
if r.CPU.Quota != nil {
738738
c.Resources.CpuQuota = *r.CPU.Quota
739739
}
740+
if r.CPU.Burst != nil {
741+
c.Resources.CpuBurst = *r.CPU.Burst
742+
}
740743
if r.CPU.Period != nil {
741744
c.Resources.CpuPeriod = *r.CPU.Period
742745
}

man/runc-update.8.md

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ In case **-r** is used, the JSON format is like this:
2828
"cpu": {
2929
"shares": 0,
3030
"quota": 0,
31+
"burst": 0,
3132
"period": 0,
3233
"realtimeRuntime": 0,
3334
"realtimePeriod": 0,
@@ -53,6 +54,9 @@ stdin. If this option is used, all other options are ignored.
5354
**--cpu-quota** _num_
5455
: Set CPU usage limit within a given period (in microseconds).
5556

57+
**--cpu-burst** _num_
58+
: Set CPU burst limit within a given period (in microseconds).
59+
5660
**--cpu-rt-period** _num_
5761
: Set CPU realtime period to be used for hardcapping (in microseconds).
5862

tests/integration/helpers.bash

+9
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ function check_cpu_quota() {
304304
check_systemd_value "CPUQuotaPeriodUSec" $sd_period $sd_infinity
305305
}
306306

307+
function check_cpu_burst() {
308+
local burst=$1
309+
if [ -v CGROUP_V2 ]; then
310+
check_cgroup_value "cpu.max.burst" "$burst"
311+
else
312+
check_cgroup_value "cpu.cfs_burst_us" "$burst"
313+
fi
314+
}
315+
307316
# Works for cgroup v1 and v2, accepts v1 shares as an argument.
308317
function check_cpu_shares() {
309318
local shares=$1

tests/integration/update.bats

+3
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ EOF
288288
runc update test_update --cpu-share 200
289289
[ "$status" -eq 0 ]
290290
check_cpu_shares 200
291+
runc update test_update --cpu-period 900000 --cpu-burst 500000
292+
[ "$status" -eq 0 ]
293+
check_cpu_burst 500000
291294

292295
# Revert to the test initial value via json on stding
293296
runc update -r - test_update <<EOF

update.go

+13
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The accepted format is as follow (unchanged values can be omitted):
4444
"cpu": {
4545
"shares": 0,
4646
"quota": 0,
47+
"burst": 0,
4748
"period": 0,
4849
"realtimeRuntime": 0,
4950
"realtimePeriod": 0,
@@ -73,6 +74,10 @@ other options are ignored.
7374
Name: "cpu-quota",
7475
Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
7576
},
77+
cli.StringFlag{
78+
Name: "cpu-burst",
79+
Usage: "CPU CFS hardcap burst limit (in usecs). Allowed accumulated cpu time additionally for burst a given period",
80+
},
7681
cli.StringFlag{
7782
Name: "cpu-share",
7883
Usage: "CPU shares (relative weight vs. other containers)",
@@ -153,6 +158,7 @@ other options are ignored.
153158
CPU: &specs.LinuxCPU{
154159
Shares: u64Ptr(0),
155160
Quota: i64Ptr(0),
161+
Burst: u64Ptr(0),
156162
Period: u64Ptr(0),
157163
RealtimeRuntime: i64Ptr(0),
158164
RealtimePeriod: u64Ptr(0),
@@ -209,6 +215,7 @@ other options are ignored.
209215
opt string
210216
dest *uint64
211217
}{
218+
{"cpu-burst", r.CPU.Burst},
212219
{"cpu-period", r.CPU.Period},
213220
{"cpu-rt-period", r.CPU.RealtimePeriod},
214221
{"cpu-share", r.CPU.Shares},
@@ -298,6 +305,12 @@ other options are ignored.
298305
}
299306
}
300307

308+
b := *r.CPU.Burst
309+
if config.Cgroups.Resources.CpuPeriod == 0 && b != 0 {
310+
return errors.New("period is not set when setting burst")
311+
}
312+
config.Cgroups.Resources.CpuBurst = b
313+
301314
config.Cgroups.Resources.CpuShares = *r.CPU.Shares
302315
// CpuWeight is used for cgroupv2 and should be converted
303316
config.Cgroups.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(*r.CPU.Shares)

0 commit comments

Comments
 (0)