Skip to content

Commit 5c01766

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 f08b4a9 commit 5c01766

File tree

11 files changed

+97
-4
lines changed

11 files changed

+97
-4
lines changed

.github/workflows/test.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ env:
1919

2020
jobs:
2121
test:
22-
runs-on: ubuntu-20.04
22+
runs-on: ubuntu-22.04
2323
strategy:
2424
fail-fast: false
2525
matrix:
@@ -42,7 +42,7 @@ jobs:
4242
- name: install deps
4343
if: matrix.criu == ''
4444
env:
45-
REPO: https://download.opensuse.org/repositories/devel:/tools:/criu/xUbuntu_20.04
45+
REPO: https://download.opensuse.org/repositories/devel:/tools:/criu/xUbuntu_22.04
4646
run: |
4747
# criu repo
4848
curl -fSsl $REPO/Release.key | sudo apt-key add -
@@ -101,7 +101,7 @@ jobs:
101101
# However, we do not have 32-bit ARM CI, so we use i386 for testing 32bit stuff.
102102
# We are not interested in providing official support for i386.
103103
cross-i386:
104-
runs-on: ubuntu-20.04
104+
runs-on: ubuntu-22.04
105105

106106
steps:
107107

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

+22
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
8484
period = ""
8585
}
8686
}
87+
88+
var burst string
89+
burst = strconv.FormatUint(r.CpuBurst, 10)
90+
if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
91+
// Sometimes when the burst to be set is larger
92+
// than the current one, it is rejected by the kernel
93+
// (EINVAL) as old_quota/new_burst exceeds the parent
94+
// cgroup quota limit. If this happens and the quota is
95+
// going to be set, ignore the error for now and retry
96+
// after setting the quota.
97+
if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
98+
return err
99+
}
100+
} else {
101+
burst = ""
102+
}
103+
87104
if r.CpuQuota != 0 {
88105
if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
89106
return err
@@ -93,6 +110,11 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
93110
return err
94111
}
95112
}
113+
if burst != "" {
114+
if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
115+
return err
116+
}
117+
}
96118
}
97119

98120
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/fs2/cpu.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ package fs2
22

33
import (
44
"bufio"
5+
"errors"
56
"os"
67
"strconv"
78

89
"github.com/opencontainers/runc/libcontainer/cgroups"
910
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
1011
"github.com/opencontainers/runc/libcontainer/configs"
12+
"golang.org/x/sys/unix"
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,22 @@ func setCpu(dirPath string, r *configs.Resources) error {
3234
}
3335
}
3436

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

5275
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
@@ -308,6 +308,15 @@ function check_cpu_quota() {
308308
check_systemd_value "CPUQuotaPeriodUSec" $sd_period $sd_infinity
309309
}
310310

311+
function check_cpu_burst() {
312+
local burst=$1
313+
if [ -v CGROUP_V2 ]; then
314+
check_cgroup_value "cpu.max.burst" "$burst"
315+
else
316+
check_cgroup_value "cpu.cfs_burst_us" "$burst"
317+
fi
318+
}
319+
311320
# Works for cgroup v1 and v2, accepts v1 shares as an argument.
312321
function check_cpu_shares() {
313322
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 600000
292+
[ "$status" -eq 0 ]
293+
check_cpu_burst 600
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)