Skip to content

Commit 83f2519

Browse files
committed
systemd: Write rounded CPU quota to cgroupfs
When CPU quota is updated, the value is converted to CPUQuotaPerSecUSec property for passing to systemd. The value can be rounded in the following cases: - The value is rounded up to the nearest 10ms. - Depending on CPU period, the value may be rounded during division. Because of this rounding, systemd and systemd driver may write different values to cgroupfs. In order to avoid this inconsistency, this fix makes systemd driver write the same rounded value to cgroupfs. Even if systemd writes CPU quota and CPU period to cgroupfs, systemd driver still needs to write to cgroupfs for a case where CPU period is updated to less than the minimum value. In this case, systemd accepts this value by adjusting CPU period while direct update by systemd driver fails. In order to keep this case invalid, systemd driver still needs to update cgroupfs. Signed-off-by: Hironori Shiina <[email protected]>
1 parent 2a61bab commit 83f2519

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed

systemd/common.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ func systemdVersionAtoi(str string) (int, error) {
280280
return ver, nil
281281
}
282282

283-
func addCpuQuota(cm *dbusConnManager, properties *[]systemdDbus.Property, quota int64, period uint64) {
283+
// addCPUQuota adds CPUQuotaPeriodUSec and CPUQuotaPerSecUSec to the properties. The passed quota may be modified
284+
// along with round-up during calculation in order to write the same value to cgroupfs later.
285+
func addCPUQuota(cm *dbusConnManager, properties *[]systemdDbus.Property, quota *int64, period uint64) {
284286
if period != 0 {
285287
// systemd only supports CPUQuotaPeriodUSec since v242
286288
sdVer := systemdVersion(cm)
@@ -292,10 +294,10 @@ func addCpuQuota(cm *dbusConnManager, properties *[]systemdDbus.Property, quota
292294
" (setting will still be applied to cgroupfs)", sdVer)
293295
}
294296
}
295-
if quota != 0 || period != 0 {
297+
if *quota != 0 || period != 0 {
296298
// corresponds to USEC_INFINITY in systemd
297299
cpuQuotaPerSecUSec := uint64(math.MaxUint64)
298-
if quota > 0 {
300+
if *quota > 0 {
299301
if period == 0 {
300302
// assume the default
301303
period = defCPUQuotaPeriod
@@ -304,9 +306,11 @@ func addCpuQuota(cm *dbusConnManager, properties *[]systemdDbus.Property, quota
304306
// (integer percentage of CPU) internally. This means that if a fractional percent of
305307
// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
306308
// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
307-
cpuQuotaPerSecUSec = uint64(quota*1000000) / period
309+
cpuQuotaPerSecUSec = uint64(*quota*1000000) / period
308310
if cpuQuotaPerSecUSec%10000 != 0 {
309311
cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
312+
// Update the requested quota along with the round-up in order to write the same value to cgroupfs.
313+
*quota = int64(cpuQuotaPerSecUSec) * int64(period) / 1000000
310314
}
311315
}
312316
*properties = append(*properties,

systemd/systemd_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,61 @@ func TestUnifiedResToSystemdProps(t *testing.T) {
178178
})
179179
}
180180
}
181+
182+
func TestAddCPUQuota(t *testing.T) {
183+
if !IsRunningSystemd() {
184+
t.Skip("Test requires systemd.")
185+
}
186+
187+
cm := newDbusConnManager(os.Geteuid() != 0)
188+
189+
testCases := []struct {
190+
name string
191+
quota int64
192+
period uint64
193+
expectedCPUQuotaPerSecUSec uint64
194+
expectedQuota int64
195+
}{
196+
{
197+
name: "No round up",
198+
quota: 500000,
199+
period: 1000000,
200+
expectedCPUQuotaPerSecUSec: 500000,
201+
expectedQuota: 500000,
202+
},
203+
{
204+
name: "With fraction",
205+
quota: 123456,
206+
expectedCPUQuotaPerSecUSec: 1240000,
207+
expectedQuota: 124000,
208+
},
209+
{
210+
name: "Round up at division",
211+
quota: 500000,
212+
period: 900000,
213+
expectedCPUQuotaPerSecUSec: 560000,
214+
expectedQuota: 504000,
215+
},
216+
}
217+
218+
for _, tc := range testCases {
219+
t.Run(tc.name, func(t *testing.T) {
220+
props := []systemdDbus.Property{}
221+
addCPUQuota(cm, &props, &tc.quota, tc.period)
222+
var cpuQuotaPerSecUSec uint64
223+
for _, p := range props {
224+
if p.Name == "CPUQuotaPerSecUSec" {
225+
if err := p.Value.Store(&cpuQuotaPerSecUSec); err != nil {
226+
t.Errorf("failed to parse CPUQuotaPerSecUSec: %v", err)
227+
}
228+
}
229+
}
230+
if cpuQuotaPerSecUSec != tc.expectedCPUQuotaPerSecUSec {
231+
t.Errorf("CPUQuotaPerSecUSec is not set as expected (exp: %v, got: %v)", tc.expectedCPUQuotaPerSecUSec, cpuQuotaPerSecUSec)
232+
}
233+
if tc.quota != tc.expectedQuota {
234+
t.Errorf("quota is not updated as expected (exp: %v, got: %v)", tc.expectedQuota, tc.quota)
235+
}
236+
})
237+
}
238+
}

systemd/v1.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func genV1ResourcesProperties(r *cgroups.Resources, cm *dbusConnManager) ([]syst
9090
newProp("CPUShares", r.CpuShares))
9191
}
9292

93-
addCpuQuota(cm, &properties, r.CpuQuota, r.CpuPeriod)
93+
addCPUQuota(cm, &properties, &r.CpuQuota, r.CpuPeriod)
9494

9595
if r.BlkioWeight != 0 {
9696
properties = append(properties,
@@ -334,6 +334,9 @@ func (m *LegacyManager) Set(r *cgroups.Resources) error {
334334
if r.Unified != nil {
335335
return cgroups.ErrV1NoUnified
336336
}
337+
// Use a copy since CpuQuota in r may be modified.
338+
rCopy := *r
339+
r = &rCopy
337340
properties, err := genV1ResourcesProperties(r, m.dbus)
338341
if err != nil {
339342
return err

systemd/v2.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func unifiedResToSystemdProps(cm *dbusConnManager, res map[string]string) (props
113113
return nil, fmt.Errorf("unified resource %q quota value conversion error: %w", k, err)
114114
}
115115
}
116-
addCpuQuota(cm, &props, quota, period)
116+
addCPUQuota(cm, &props, &quota, period)
117117

118118
case "cpu.weight":
119119
if shouldSetCPUIdle(cm, strings.TrimSpace(res["cpu.idle"])) {
@@ -254,7 +254,7 @@ func genV2ResourcesProperties(dirPath string, r *cgroups.Resources, cm *dbusConn
254254
}
255255
}
256256

257-
addCpuQuota(cm, &properties, r.CpuQuota, r.CpuPeriod)
257+
addCPUQuota(cm, &properties, &r.CpuQuota, r.CpuPeriod)
258258

259259
if r.PidsLimit > 0 || r.PidsLimit == -1 {
260260
properties = append(properties,
@@ -480,6 +480,9 @@ func (m *UnifiedManager) Set(r *cgroups.Resources) error {
480480
if r == nil {
481481
return nil
482482
}
483+
// Use a copy since CpuQuota in r may be modified.
484+
rCopy := *r
485+
r = &rCopy
483486
properties, err := genV2ResourcesProperties(m.fsMgr.Path(""), r, m.dbus)
484487
if err != nil {
485488
return err

0 commit comments

Comments
 (0)