Merge pull request #9594 from filbranden/cpu_quota_period1

core: add CPUQuotaPeriodSec=
This commit is contained in:
Lennart Poettering 2019-02-15 12:11:42 +01:00 committed by GitHub
commit eca3d5d567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 193 additions and 9 deletions

View File

@ -224,6 +224,7 @@ All cgroup/resource control settings are available for transient units
✓ CPUShares=
✓ StartupCPUShares=
✓ CPUQuota=
✓ CPUQuotaPeriodSec=
✓ MemoryAccounting=
✓ MemoryMin=
✓ MemoryLow=

View File

@ -198,6 +198,25 @@
</listitem>
</varlistentry>
<varlistentry>
<term><varname>CPUQuotaPeriodSec=</varname></term>
<listitem>
<para>Assign the duration over which the CPU time quota specified by <varname>CPUQuota=</varname> is measured.
Takes a time duration value in seconds, with an optional suffix such as "ms" for milliseconds (or "s" for seconds.)
The default setting is 100ms. The period is clamped to the range supported by the kernel, which is [1ms, 1000ms].
Additionally, the period is adjusted up so that the quota interval is also at least 1ms.
Setting <varname>CPUQuotaPeriodSec=</varname> to an empty value resets it to the default.</para>
<para>This controls the second field of <literal>cpu.max</literal> attribute on the unified control group hierarchy
and <literal>cpu.cfs_period_us</literal> on legacy. For details about these control group attributes, see
<ulink url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink> and
<ulink url="https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt">sched-design-CFS.txt</ulink>.</para>
<para>Example: <varname>CPUQuotaPeriodSec=10ms</varname> to request that the CPU quota is measured in periods of 10ms.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>MemoryAccounting=</varname></term>

View File

@ -1031,6 +1031,15 @@ int parse_sec_fix_0(const char *t, usec_t *ret) {
return r;
}
int parse_sec_def_infinity(const char *t, usec_t *ret) {
t += strspn(t, WHITESPACE);
if (isempty(t)) {
*ret = USEC_INFINITY;
return 0;
}
return parse_sec(t, ret);
}
static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
static const struct {
const char *suffix;

View File

@ -112,6 +112,7 @@ int parse_timestamp(const char *t, usec_t *usec);
int parse_sec(const char *t, usec_t *usec);
int parse_sec_fix_0(const char *t, usec_t *usec);
int parse_sec_def_infinity(const char *t, usec_t *usec);
int parse_time(const char *t, usec_t *usec, usec_t default_unit);
int parse_nsec(const char *t, nsec_t *nsec);

View File

@ -25,7 +25,7 @@
#include "string-util.h"
#include "virt.h"
#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC)
#define CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC)
/* Returns the log level to use when cgroup attribute writes fail. When an attribute is missing or we have access
* problems we downgrade to LOG_DEBUG. This is supposed to be nice to container managers and kernels which want to mask
@ -98,6 +98,7 @@ void cgroup_context_init(CGroupContext *c) {
.cpu_weight = CGROUP_WEIGHT_INVALID,
.startup_cpu_weight = CGROUP_WEIGHT_INVALID,
.cpu_quota_per_sec_usec = USEC_INFINITY,
.cpu_quota_period_usec = USEC_INFINITY,
.cpu_shares = CGROUP_CPU_SHARES_INVALID,
.startup_cpu_shares = CGROUP_CPU_SHARES_INVALID,
@ -206,6 +207,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
CGroupDeviceAllow *a;
IPAddressAccessItem *iaai;
char u[FORMAT_TIMESPAN_MAX];
char v[FORMAT_TIMESPAN_MAX];
assert(c);
assert(f);
@ -224,6 +226,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
"%sCPUShares=%" PRIu64 "\n"
"%sStartupCPUShares=%" PRIu64 "\n"
"%sCPUQuotaPerSecSec=%s\n"
"%sCPUQuotaPeriodSec=%s\n"
"%sIOWeight=%" PRIu64 "\n"
"%sStartupIOWeight=%" PRIu64 "\n"
"%sBlockIOWeight=%" PRIu64 "\n"
@ -248,6 +251,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
prefix, c->cpu_shares,
prefix, c->startup_cpu_shares,
prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1),
prefix, format_timespan(v, sizeof(v), c->cpu_quota_period_usec, 1),
prefix, c->io_weight,
prefix, c->startup_io_weight,
prefix, c->blockio_weight,
@ -656,6 +660,40 @@ static uint64_t cgroup_context_cpu_shares(CGroupContext *c, ManagerState state)
return CGROUP_CPU_SHARES_DEFAULT;
}
usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period) {
/* kernel uses a minimum resolution of 1ms, so both period and (quota * period)
* need to be higher than that boundary. quota is specified in USecPerSec.
* Additionally, period must be at most max_period. */
assert(quota > 0);
return MIN(MAX3(period, resolution, resolution * USEC_PER_SEC / quota), max_period);
}
static usec_t cgroup_cpu_adjust_period_and_log(Unit *u, usec_t period, usec_t quota) {
usec_t new_period;
if (quota == USEC_INFINITY)
/* Always use default period for infinity quota. */
return CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC;
if (period == USEC_INFINITY)
/* Default period was requested. */
period = CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC;
/* Clamp to interval [1ms, 1s] */
new_period = cgroup_cpu_adjust_period(period, quota, USEC_PER_MSEC, USEC_PER_SEC);
if (new_period != period) {
char v[FORMAT_TIMESPAN_MAX];
log_unit_full(u, u->warned_clamping_cpu_quota_period ? LOG_DEBUG : LOG_WARNING, 0,
"Clamping CPU interval for cpu.max: period is now %s",
format_timespan(v, sizeof(v), new_period, 1));
u->warned_clamping_cpu_quota_period = true;
}
return new_period;
}
static void cgroup_apply_unified_cpu_weight(Unit *u, uint64_t weight) {
char buf[DECIMAL_STR_MAX(uint64_t) + 2];
@ -663,14 +701,15 @@ static void cgroup_apply_unified_cpu_weight(Unit *u, uint64_t weight) {
(void) set_attribute_and_warn(u, "cpu", "cpu.weight", buf);
}
static void cgroup_apply_unified_cpu_quota(Unit *u, usec_t quota) {
static void cgroup_apply_unified_cpu_quota(Unit *u, usec_t quota, usec_t period) {
char buf[(DECIMAL_STR_MAX(usec_t) + 1) * 2 + 1];
period = cgroup_cpu_adjust_period_and_log(u, period, quota);
if (quota != USEC_INFINITY)
xsprintf(buf, USEC_FMT " " USEC_FMT "\n",
quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC, CGROUP_CPU_QUOTA_PERIOD_USEC);
MAX(quota * period / USEC_PER_SEC, USEC_PER_MSEC), period);
else
xsprintf(buf, "max " USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC);
xsprintf(buf, "max " USEC_FMT "\n", period);
(void) set_attribute_and_warn(u, "cpu", "cpu.max", buf);
}
@ -681,14 +720,16 @@ static void cgroup_apply_legacy_cpu_shares(Unit *u, uint64_t shares) {
(void) set_attribute_and_warn(u, "cpu", "cpu.shares", buf);
}
static void cgroup_apply_legacy_cpu_quota(Unit *u, usec_t quota) {
static void cgroup_apply_legacy_cpu_quota(Unit *u, usec_t quota, usec_t period) {
char buf[DECIMAL_STR_MAX(usec_t) + 2];
xsprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC);
period = cgroup_cpu_adjust_period_and_log(u, period, quota);
xsprintf(buf, USEC_FMT "\n", period);
(void) set_attribute_and_warn(u, "cpu", "cpu.cfs_period_us", buf);
if (quota != USEC_INFINITY) {
xsprintf(buf, USEC_FMT "\n", quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC);
xsprintf(buf, USEC_FMT "\n", MAX(quota * period / USEC_PER_SEC, USEC_PER_MSEC));
(void) set_attribute_and_warn(u, "cpu", "cpu.cfs_quota_us", buf);
} else
(void) set_attribute_and_warn(u, "cpu", "cpu.cfs_quota_us", "-1\n");
@ -911,7 +952,7 @@ static void cgroup_context_apply(
weight = CGROUP_WEIGHT_DEFAULT;
cgroup_apply_unified_cpu_weight(u, weight);
cgroup_apply_unified_cpu_quota(u, c->cpu_quota_per_sec_usec);
cgroup_apply_unified_cpu_quota(u, c->cpu_quota_per_sec_usec, c->cpu_quota_period_usec);
} else {
uint64_t shares;
@ -930,7 +971,7 @@ static void cgroup_context_apply(
shares = CGROUP_CPU_SHARES_DEFAULT;
cgroup_apply_legacy_cpu_shares(u, shares);
cgroup_apply_legacy_cpu_quota(u, c->cpu_quota_per_sec_usec);
cgroup_apply_legacy_cpu_quota(u, c->cpu_quota_per_sec_usec, c->cpu_quota_period_usec);
}
}

View File

@ -83,6 +83,7 @@ struct CGroupContext {
uint64_t cpu_weight;
uint64_t startup_cpu_weight;
usec_t cpu_quota_per_sec_usec;
usec_t cpu_quota_period_usec;
uint64_t io_weight;
uint64_t startup_io_weight;
@ -135,6 +136,8 @@ typedef enum CGroupIPAccountingMetric {
typedef struct Unit Unit;
typedef struct Manager Manager;
usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period);
void cgroup_context_init(CGroupContext *c);
void cgroup_context_done(CGroupContext *c);
void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix);

View File

@ -330,6 +330,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0),
SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0),
SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0),
SD_BUS_PROPERTY("CPUQuotaPeriodUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_period_usec), 0),
SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0),
SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0),
SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0),
@ -713,6 +714,7 @@ int bus_cgroup_set_property(
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->cpu_quota_per_sec_usec = u64;
u->warned_clamping_cpu_quota_period = false;
unit_invalidate_cgroup(u, CGROUP_MASK_CPU);
if (c->cpu_quota_per_sec_usec == USEC_INFINITY)
@ -727,6 +729,29 @@ int bus_cgroup_set_property(
return 1;
} else if (streq(name, "CPUQuotaPeriodUSec")) {
uint64_t u64;
r = sd_bus_message_read(message, "t", &u64);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->cpu_quota_period_usec = u64;
u->warned_clamping_cpu_quota_period = false;
unit_invalidate_cgroup(u, CGROUP_MASK_CPU);
if (c->cpu_quota_period_usec == USEC_INFINITY)
unit_write_setting(u, flags, "CPUQuotaPeriodSec", "CPUQuotaPeriodSec=");
else {
char v[FORMAT_TIMESPAN_MAX];
unit_write_settingf(u, flags, "CPUQuotaPeriodSec",
"CPUQuotaPeriodSec=%s",
format_timespan(v, sizeof(v), c->cpu_quota_period_usec, 1));
}
}
return 1;
} else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) {
const char *path;
unsigned n = 0;

View File

@ -165,6 +165,7 @@ $1.StartupCPUWeight, config_parse_cg_weight, 0,
$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares)
$1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares)
$1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context)
$1.CPUQuotaPeriodSec, config_parse_sec_def_infinity, 0, offsetof($1, cgroup_context.cpu_quota_period_usec)
$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting)
$1.MemoryMin, config_parse_memory_limit, 0, offsetof($1, cgroup_context)
$1.MemoryLow, config_parse_memory_limit, 0, offsetof($1, cgroup_context)

View File

@ -54,6 +54,7 @@
#include "unit-name.h"
#include "unit-printf.h"
#include "user-util.h"
#include "time-util.h"
#include "web-util.h"
static int parse_socket_protocol(const char *s) {

View File

@ -349,6 +349,9 @@ typedef struct Unit {
bool exported_log_rate_limit_interval:1;
bool exported_log_rate_limit_burst:1;
/* Whether we warned about clamping the CPU quota period */
bool warned_clamping_cpu_quota_period:1;
/* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If
* == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */
signed int last_section_private:2;

View File

@ -464,6 +464,20 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
return 1;
}
if (streq(field, "CPUQuotaPeriodSec")) {
usec_t u = USEC_INFINITY;
r = parse_sec_def_infinity(eq, &u);
if (r < 0)
return log_error_errno(r, "CPU quota period '%s' invalid.", eq);
r = sd_bus_message_append(m, "(sv)", "CPUQuotaPeriodUSec", "t", u);
if (r < 0)
return bus_log_create_error(r);
return 1;
}
if (streq(field, "DeviceAllow")) {
if (isempty(eq))

View File

@ -506,6 +506,7 @@ DEFINE_PARSER(unsigned, unsigned, safe_atou);
DEFINE_PARSER(double, double, safe_atod);
DEFINE_PARSER(nsec, nsec_t, parse_nsec);
DEFINE_PARSER(sec, usec_t, parse_sec);
DEFINE_PARSER(sec_def_infinity, usec_t, parse_sec_def_infinity);
DEFINE_PARSER(mode, mode_t, parse_mode);
int config_parse_iec_size(const char* unit,

View File

@ -127,6 +127,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_string);
CONFIG_PARSER_PROTOTYPE(config_parse_path);
CONFIG_PARSER_PROTOTYPE(config_parse_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_sec);
CONFIG_PARSER_PROTOTYPE(config_parse_sec_def_infinity);
CONFIG_PARSER_PROTOTYPE(config_parse_nsec);
CONFIG_PARSER_PROTOTYPE(config_parse_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_warn_compat);

View File

@ -560,6 +560,11 @@ tests += [
[],
'', 'manual'],
[['src/test/test-cgroup-cpu.c'],
[libcore,
libshared],
[]],
[['src/test/test-cgroup-mask.c',
'src/test/test-helper.c'],
[libcore,

View File

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "cgroup.h"
#include "log.h"
static void test_cgroup_cpu_adjust_period(void) {
log_info("/* %s */", __func__);
/* Period 1ms, quota 40% -> Period 2.5ms */
assert_se(2500 == cgroup_cpu_adjust_period(USEC_PER_MSEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 10ms, quota 10% -> keep. */
assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 1ms, quota 1000% -> keep. */
assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(USEC_PER_MSEC, 10000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 100ms, quota 30% -> keep. */
assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(100 * USEC_PER_MSEC, 300 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 5s, quota 40% -> adjust to 1s. */
assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(5 * USEC_PER_SEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 2s, quota 250% -> adjust to 1s. */
assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(2 * USEC_PER_SEC, 2500 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 10us, quota 5,000,000% -> adjust to 1ms. */
assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(10, 50000000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 10ms, quota 50,000% -> keep. */
assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 500000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 10ms, quota 1% -> adjust to 100ms. */
assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 10ms, quota .001% -> adjust to 1s. */
assert_se(1 * USEC_PER_SEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 0ms, quota 200% -> adjust to 1ms. */
assert_se(1 * USEC_PER_MSEC == cgroup_cpu_adjust_period(0, 2 * USEC_PER_SEC, USEC_PER_MSEC, USEC_PER_SEC));
/* Period 0ms, quota 40% -> adjust to 2.5ms. */
assert_se(2500 == cgroup_cpu_adjust_period(0, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC));
}
int main(int argc, char *argv[]) {
test_cgroup_cpu_adjust_period();
return 0;
}

View File

@ -85,6 +85,26 @@ static void test_parse_sec_fix_0(void) {
assert_se(u == USEC_INFINITY);
}
static void test_parse_sec_def_infinity(void) {
usec_t u;
log_info("/* %s */", __func__);
assert_se(parse_sec_def_infinity("5s", &u) >= 0);
assert_se(u == 5 * USEC_PER_SEC);
assert_se(parse_sec_def_infinity("", &u) >= 0);
assert_se(u == USEC_INFINITY);
assert_se(parse_sec_def_infinity(" ", &u) >= 0);
assert_se(u == USEC_INFINITY);
assert_se(parse_sec_def_infinity("0s", &u) >= 0);
assert_se(u == 0);
assert_se(parse_sec_def_infinity("0", &u) >= 0);
assert_se(u == 0);
assert_se(parse_sec_def_infinity(" 0", &u) >= 0);
assert_se(u == 0);
assert_se(parse_sec_def_infinity("-5s", &u) < 0);
}
static void test_parse_time(void) {
usec_t u;
@ -472,6 +492,7 @@ int main(int argc, char *argv[]) {
test_parse_sec();
test_parse_sec_fix_0();
test_parse_sec_def_infinity();
test_parse_time();
test_parse_nsec();
test_format_timespan(1);