// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2021 SUSE LLC * Copyright (c) Linux Test Project, 2021-2023 */ /*\ * [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, * https://bugzilla.suse.com/show_bug.cgi?id=1179093). * * Also as an reproducer for another bug: * * commit fdaba61ef8a268d4136d0a113d153f7a89eb9984 * Author: Rik van Riel * Date: Mon Jun 21 19:43:30 2021 +0200 * * sched/fair: Ensure that the CFS parent is added after unthrottling */ #include #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"}, { } } };