Skip to content

Commit 6b8a45d

Browse files
authored
Merge pull request #3749 from Zheaoli/manjusaka/carry-burst
[Carry #3205] libct/cg: add CFS bandwidth burst for CPU
2 parents f0eea99 + e158483 commit 6b8a45d

File tree

12 files changed

+108
-4
lines changed

12 files changed

+108
-4
lines changed

contrib/completions/bash/runc

+1
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ _runc_update() {
735735
--blkio-weight
736736
--cpu-period
737737
--cpu-quota
738+
--cpu-burst
738739
--cpu-rt-period
739740
--cpu-rt-runtime
740741
--cpu-share

docs/spec-conformance.md

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Spec version | Feature | PR
1010
v1.0.0 | `SCMP_ARCH_PARISC` | Unplanned, due to lack of users
1111
v1.0.0 | `SCMP_ARCH_PARISC64` | Unplanned, due to lack of users
1212
v1.0.2 | `.linux.personality` | [#3126](https://github.com/opencontainers/runc/pull/3126)
13-
v1.1.0 | `.linux.resources.cpu.burst` | [#3749](https://github.com/opencontainers/runc/pull/3749)
1413
v1.1.0 | `SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV` | [#3862](https://github.com/opencontainers/runc/pull/3862)
1514
v1.1.0 | time namespaces | [#3876](https://github.com/opencontainers/runc/pull/3876)
1615
v1.1.0 | rsvd hugetlb cgroup | TODO ([#3859](https://github.com/opencontainers/runc/issues/3859))

libcontainer/cgroups/fs/cpu.go

+29
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 != nil {
90+
burst = strconv.FormatUint(*r.CpuBurst, 10)
91+
if err := cgroups.WriteFile(path, "cpu.cfs_burst_us", burst); err != nil {
92+
// this is a special trick for burst feature, the current systemd and low version of kernel will not support it.
93+
// So, an `no such file or directory` error would be raised, and we can ignore it .
94+
if !errors.Is(err, unix.ENOENT) {
95+
// Sometimes when the burst to be set is larger
96+
// than the current one, it is rejected by the kernel
97+
// (EINVAL) as old_quota/new_burst exceeds the parent
98+
// cgroup quota limit. If this happens and the quota is
99+
// going to be set, ignore the error for now and retry
100+
// after setting the quota.
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,13 @@ 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+
if !errors.Is(err, unix.ENOENT) {
121+
return err
122+
}
123+
}
124+
}
96125
}
97126

98127
if r.CPUIdle != nil {

libcontainer/cgroups/fs/cpu_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,27 @@ func TestCpuSetBandWidth(t *testing.T) {
4545
const (
4646
quotaBefore = 8000
4747
quotaAfter = 5000
48+
burstBefore = 2000
4849
periodBefore = 10000
4950
periodAfter = 7000
5051
rtRuntimeBefore = 8000
5152
rtRuntimeAfter = 5000
5253
rtPeriodBefore = 10000
5354
rtPeriodAfter = 7000
5455
)
56+
burstAfter := uint64(1000)
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 %w", sys.Name(), err)
195195
}
196196
return err
197197
}

libcontainer/cgroups/fs2/cpu.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ package fs2
22

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

9+
"golang.org/x/sys/unix"
10+
811
"github.com/opencontainers/runc/libcontainer/cgroups"
912
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
1013
"github.com/opencontainers/runc/libcontainer/configs"
1114
)
1215

1316
func isCpuSet(r *configs.Resources) bool {
14-
return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0 || r.CPUIdle != nil
17+
return r.CpuWeight != 0 || r.CpuQuota != 0 || r.CpuPeriod != 0 || r.CPUIdle != nil || r.CpuBurst != nil
1518
}
1619

1720
func setCpu(dirPath string, r *configs.Resources) error {
@@ -32,6 +35,23 @@ func setCpu(dirPath string, r *configs.Resources) error {
3235
}
3336
}
3437

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

5277
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"` //nolint:revive
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
@@ -755,6 +755,9 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*confi
755755
if r.CPU.Quota != nil {
756756
c.Resources.CpuQuota = *r.CPU.Quota
757757
}
758+
if r.CPU.Burst != nil {
759+
c.Resources.CpuBurst = r.CPU.Burst
760+
}
758761
if r.CPU.Period != nil {
759762
c.Resources.CpuPeriod = *r.CPU.Period
760763
}

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,16 @@ function get_cgroup_value() {
260260
# Helper to check a if value in a cgroup file matches the expected one.
261261
function check_cgroup_value() {
262262
local current
263+
local cgroup
264+
cgroup="$(get_cgroup_path "$1")"
265+
if [ ! -f "$cgroup/$1" ]; then
266+
skip "$cgroup/$1 does not exist"
267+
fi
263268
current="$(get_cgroup_value "$1")"
264269
local expected=$2
265270

266271
echo "current $current !? $expected"
267-
[ "$current" = "$expected" ]
272+
[ "$current" = "$expected" ] || [ "$current" = "$((expected / 1000))" ]
268273
}
269274

270275
# Helper to check a value in systemd.
@@ -310,6 +315,15 @@ function check_cpu_quota() {
310315
check_systemd_value "CPUQuotaPeriodUSec" $sd_period $sd_infinity
311316
}
312317

318+
function check_cpu_burst() {
319+
local burst=$1
320+
if [ -v CGROUP_V2 ]; then
321+
check_cgroup_value "cpu.max.burst" "$burst"
322+
else
323+
check_cgroup_value "cpu.cfs_burst_us" "$burst"
324+
fi
325+
}
326+
313327
# Works for cgroup v1 and v2, accepts v1 shares as an argument.
314328
function check_cpu_shares() {
315329
local shares=$1

tests/integration/update.bats

+6
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ 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
294+
runc update test_update --cpu-period 900000 --cpu-burst 0
295+
[ "$status" -eq 0 ]
296+
check_cpu_burst 0
291297

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

update.go

+8
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),
@@ -210,6 +216,7 @@ other options are ignored.
210216
opt string
211217
dest *uint64
212218
}{
219+
{"cpu-burst", r.CPU.Burst},
213220
{"cpu-period", r.CPU.Period},
214221
{"cpu-rt-period", r.CPU.RealtimePeriod},
215222
{"cpu-share", r.CPU.Shares},
@@ -299,6 +306,7 @@ other options are ignored.
299306
}
300307
}
301308

309+
config.Cgroups.Resources.CpuBurst = r.CPU.Burst
302310
config.Cgroups.Resources.CpuShares = *r.CPU.Shares
303311
// CpuWeight is used for cgroupv2 and should be converted
304312
config.Cgroups.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(*r.CPU.Shares)

0 commit comments

Comments
 (0)