190 lines
4.4 KiB
C
190 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com> */
|
|
/*\
|
|
*
|
|
* [Description]
|
|
*
|
|
* Creates a multi-level CGroup hierarchy with the cpu controller
|
|
* enabled. The leaf groups are populated with "busy" processes which
|
|
* simulate intermittent cpu load. They spin for some time then sleep
|
|
* then repeat.
|
|
*
|
|
* Both the trunk and leaf groups are set cpu bandwidth limits. The
|
|
* busy processes will intermittently exceed these limits. Causing
|
|
* them to be throttled. When they begin sleeping this will then cause
|
|
* them to be unthrottle.
|
|
*
|
|
* The test is known to reproduce an issue with an update to
|
|
* SLE-15-SP1 (kernel 4.12.14-197.64, bsc#1179093).
|
|
*
|
|
* Also as an reproducer for another bug:
|
|
*
|
|
* commit fdaba61ef8a268d4136d0a113d153f7a89eb9984
|
|
* Author: Rik van Riel <riel@surriel.com>
|
|
* Date: Mon Jun 21 19:43:30 2021 +0200
|
|
*
|
|
* sched/fair: Ensure that the CFS parent is added after unthrottling
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "tst_test.h"
|
|
#include "tst_timer.h"
|
|
|
|
static struct tst_cg_group *cg_level2, *cg_level3a, *cg_level3b;
|
|
static struct tst_cg_group *cg_workers[3];
|
|
static int may_have_waiters = 0;
|
|
|
|
static void set_cpu_quota(const struct tst_cg_group *const cg,
|
|
const float quota_percent)
|
|
{
|
|
const unsigned int period_us = 10000;
|
|
const unsigned int quota_us = (quota_percent / 100) * (float)period_us;
|
|
|
|
if (!TST_CG_VER_IS_V1(cg, "cpu")) {
|
|
SAFE_CG_PRINTF(cg, "cpu.max",
|
|
"%u %u", quota_us, period_us);
|
|
} else {
|
|
SAFE_CG_PRINTF(cg, "cpu.cfs_period_us",
|
|
"%u", period_us);
|
|
SAFE_CG_PRINTF(cg, "cpu.max",
|
|
"%u", quota_us);
|
|
}
|
|
|
|
tst_res(TINFO, "Set '%s/cpu.max' = '%d %d'",
|
|
tst_cg_group_name(cg), quota_us, period_us);
|
|
}
|
|
|
|
static void mk_cpu_cgroup(struct tst_cg_group **cg,
|
|
const struct tst_cg_group *const cg_parent,
|
|
const char *const cg_child_name,
|
|
const float quota_percent)
|
|
|
|
{
|
|
*cg = tst_cg_group_mk(cg_parent, "%s", cg_child_name);
|
|
|
|
set_cpu_quota(*cg, quota_percent);
|
|
}
|
|
|
|
static void busy_loop(const unsigned int sleep_ms)
|
|
{
|
|
for (;;) {
|
|
tst_timer_start(CLOCK_MONOTONIC_RAW);
|
|
while (!tst_timer_expired_ms(20))
|
|
;
|
|
|
|
const int ret = tst_checkpoint_wait(0, sleep_ms);
|
|
|
|
if (!ret)
|
|
exit(0);
|
|
|
|
if (errno != ETIMEDOUT)
|
|
tst_brk(TBROK | TERRNO, "tst_checkpoint_wait");
|
|
}
|
|
}
|
|
|
|
static void fork_busy_procs_in_cgroup(const struct tst_cg_group *const cg)
|
|
{
|
|
const unsigned int sleeps_ms[] = {3000, 1000, 10};
|
|
const pid_t worker_pid = SAFE_FORK();
|
|
size_t i;
|
|
|
|
if (worker_pid)
|
|
return;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sleeps_ms); i++) {
|
|
const pid_t busy_pid = SAFE_FORK();
|
|
|
|
if (!busy_pid)
|
|
busy_loop(sleeps_ms[i]);
|
|
|
|
SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", busy_pid);
|
|
}
|
|
|
|
tst_reap_children();
|
|
|
|
exit(0);
|
|
}
|
|
|
|
static void do_test(void)
|
|
{
|
|
size_t i;
|
|
|
|
may_have_waiters = 1;
|
|
for (i = 0; i < ARRAY_SIZE(cg_workers); i++)
|
|
fork_busy_procs_in_cgroup(cg_workers[i]);
|
|
|
|
tst_res(TPASS, "Scheduled bandwidth constrained workers");
|
|
|
|
sleep(1);
|
|
|
|
set_cpu_quota(cg_level2, 50);
|
|
|
|
sleep(2);
|
|
|
|
TST_CHECKPOINT_WAKE2(0, 3 * 3);
|
|
tst_reap_children();
|
|
may_have_waiters = 0;
|
|
|
|
tst_res(TPASS, "Workers exited");
|
|
}
|
|
|
|
static void setup(void)
|
|
{
|
|
cg_level2 = tst_cg_group_mk(tst_cg, "level2");
|
|
|
|
cg_level3a = tst_cg_group_mk(cg_level2, "level3a");
|
|
mk_cpu_cgroup(&cg_workers[0], cg_level3a, "worker1", 30);
|
|
mk_cpu_cgroup(&cg_workers[1], cg_level3a, "worker2", 20);
|
|
|
|
cg_level3b = tst_cg_group_mk(cg_level2, "level3b");
|
|
mk_cpu_cgroup(&cg_workers[2], cg_level3b, "worker3", 30);
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
size_t i;
|
|
|
|
if (may_have_waiters) {
|
|
TST_CHECKPOINT_WAKE2(0, 3 * 3);
|
|
tst_reap_children();
|
|
may_have_waiters = 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cg_workers); i++) {
|
|
if (cg_workers[i])
|
|
cg_workers[i] = tst_cg_group_rm(cg_workers[i]);
|
|
}
|
|
|
|
if (cg_level3a)
|
|
cg_level3a = tst_cg_group_rm(cg_level3a);
|
|
if (cg_level3b)
|
|
cg_level3b = tst_cg_group_rm(cg_level3b);
|
|
if (cg_level2)
|
|
cg_level2 = tst_cg_group_rm(cg_level2);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.test_all = do_test,
|
|
.setup = setup,
|
|
.cleanup = cleanup,
|
|
.forks_child = 1,
|
|
.needs_checkpoints = 1,
|
|
.max_runtime = 20,
|
|
.taint_check = TST_TAINT_W | TST_TAINT_D,
|
|
.needs_kconfigs = (const char *[]) {
|
|
"CONFIG_CFS_BANDWIDTH",
|
|
NULL
|
|
},
|
|
.needs_cgroup_ctrls = (const char *const []){"cpu", NULL},
|
|
.tags = (const struct tst_tag[]) {
|
|
{"linux-git", "39f23ce07b93"},
|
|
{"linux-git", "b34cb07dde7c"},
|
|
{"linux-git", "fe61468b2cbc"},
|
|
{"linux-git", "5ab297bab984"},
|
|
{"linux-git", "6d4d22468dae"},
|
|
{"linux-git", "fdaba61ef8a2"},
|
|
{ }
|
|
}
|
|
};
|