1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com>
4 * Copyright (c) Linux Test Project, 2021-2023
5 */
6
7 /*\
8 * [Description]
9 *
10 * Creates a multi-level CGroup hierarchy with the cpu controller
11 * enabled. The leaf groups are populated with "busy" processes which
12 * simulate intermittent cpu load. They spin for some time then sleep
13 * then repeat.
14 *
15 * Both the trunk and leaf groups are set cpu bandwidth limits. The
16 * busy processes will intermittently exceed these limits. Causing
17 * them to be throttled. When they begin sleeping this will then cause
18 * them to be unthrottle.
19 *
20 * The test is known to reproduce an issue with an update to
21 * SLE-15-SP1 (kernel 4.12.14-197.64,
22 * https://bugzilla.suse.com/show_bug.cgi?id=1179093).
23 *
24 * Also as an reproducer for another bug:
25 *
26 * commit fdaba61ef8a268d4136d0a113d153f7a89eb9984
27 * Author: Rik van Riel <riel@surriel.com>
28 * Date: Mon Jun 21 19:43:30 2021 +0200
29 *
30 * sched/fair: Ensure that the CFS parent is added after unthrottling
31 */
32
33 #include <stdlib.h>
34
35 #include "tst_test.h"
36 #include "tst_timer.h"
37
38 static struct tst_cg_group *cg_level2, *cg_level3a, *cg_level3b;
39 static struct tst_cg_group *cg_workers[3];
40 static int may_have_waiters = 0;
41
set_cpu_quota(const struct tst_cg_group * const cg,const float quota_percent)42 static void set_cpu_quota(const struct tst_cg_group *const cg,
43 const float quota_percent)
44 {
45 const unsigned int period_us = 10000;
46 const unsigned int quota_us = (quota_percent / 100) * (float)period_us;
47
48 if (!TST_CG_VER_IS_V1(cg, "cpu")) {
49 SAFE_CG_PRINTF(cg, "cpu.max",
50 "%u %u", quota_us, period_us);
51 } else {
52 SAFE_CG_PRINTF(cg, "cpu.cfs_period_us",
53 "%u", period_us);
54 SAFE_CG_PRINTF(cg, "cpu.max",
55 "%u", quota_us);
56 }
57
58 tst_res(TINFO, "Set '%s/cpu.max' = '%d %d'",
59 tst_cg_group_name(cg), quota_us, period_us);
60 }
61
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)62 static void mk_cpu_cgroup(struct tst_cg_group **cg,
63 const struct tst_cg_group *const cg_parent,
64 const char *const cg_child_name,
65 const float quota_percent)
66
67 {
68 *cg = tst_cg_group_mk(cg_parent, "%s", cg_child_name);
69
70 set_cpu_quota(*cg, quota_percent);
71 }
72
busy_loop(const unsigned int sleep_ms)73 static void busy_loop(const unsigned int sleep_ms)
74 {
75 for (;;) {
76 tst_timer_start(CLOCK_MONOTONIC_RAW);
77 while (!tst_timer_expired_ms(20))
78 ;
79
80 const int ret = tst_checkpoint_wait(0, sleep_ms);
81
82 if (!ret)
83 exit(0);
84
85 if (errno != ETIMEDOUT)
86 tst_brk(TBROK | TERRNO, "tst_checkpoint_wait");
87 }
88 }
89
fork_busy_procs_in_cgroup(const struct tst_cg_group * const cg)90 static void fork_busy_procs_in_cgroup(const struct tst_cg_group *const cg)
91 {
92 const unsigned int sleeps_ms[] = {3000, 1000, 10};
93 const pid_t worker_pid = SAFE_FORK();
94 size_t i;
95
96 if (worker_pid)
97 return;
98
99 for (i = 0; i < ARRAY_SIZE(sleeps_ms); i++) {
100 const pid_t busy_pid = SAFE_FORK();
101
102 if (!busy_pid)
103 busy_loop(sleeps_ms[i]);
104
105 SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", busy_pid);
106 }
107
108 tst_reap_children();
109
110 exit(0);
111 }
112
do_test(void)113 static void do_test(void)
114 {
115 size_t i;
116
117 may_have_waiters = 1;
118 for (i = 0; i < ARRAY_SIZE(cg_workers); i++)
119 fork_busy_procs_in_cgroup(cg_workers[i]);
120
121 tst_res(TPASS, "Scheduled bandwidth constrained workers");
122
123 sleep(1);
124
125 set_cpu_quota(cg_level2, 50);
126
127 sleep(2);
128
129 TST_CHECKPOINT_WAKE2(0, 3 * 3);
130 tst_reap_children();
131 may_have_waiters = 0;
132
133 tst_res(TPASS, "Workers exited");
134 }
135
setup(void)136 static void setup(void)
137 {
138 cg_level2 = tst_cg_group_mk(tst_cg, "level2");
139
140 cg_level3a = tst_cg_group_mk(cg_level2, "level3a");
141 mk_cpu_cgroup(&cg_workers[0], cg_level3a, "worker1", 30);
142 mk_cpu_cgroup(&cg_workers[1], cg_level3a, "worker2", 20);
143
144 cg_level3b = tst_cg_group_mk(cg_level2, "level3b");
145 mk_cpu_cgroup(&cg_workers[2], cg_level3b, "worker3", 30);
146 }
147
cleanup(void)148 static void cleanup(void)
149 {
150 size_t i;
151
152 if (may_have_waiters) {
153 TST_CHECKPOINT_WAKE2(0, 3 * 3);
154 tst_reap_children();
155 may_have_waiters = 0;
156 }
157
158 for (i = 0; i < ARRAY_SIZE(cg_workers); i++) {
159 if (cg_workers[i])
160 cg_workers[i] = tst_cg_group_rm(cg_workers[i]);
161 }
162
163 if (cg_level3a)
164 cg_level3a = tst_cg_group_rm(cg_level3a);
165 if (cg_level3b)
166 cg_level3b = tst_cg_group_rm(cg_level3b);
167 if (cg_level2)
168 cg_level2 = tst_cg_group_rm(cg_level2);
169 }
170
171 static struct tst_test test = {
172 .test_all = do_test,
173 .setup = setup,
174 .cleanup = cleanup,
175 .forks_child = 1,
176 .needs_checkpoints = 1,
177 .max_runtime = 20,
178 .taint_check = TST_TAINT_W | TST_TAINT_D,
179 .needs_kconfigs = (const char *[]) {
180 "CONFIG_CFS_BANDWIDTH",
181 NULL
182 },
183 .needs_cgroup_ctrls = (const char *const []){"cpu", NULL},
184 .tags = (const struct tst_tag[]) {
185 {"linux-git", "39f23ce07b93"},
186 {"linux-git", "b34cb07dde7c"},
187 {"linux-git", "fe61468b2cbc"},
188 {"linux-git", "5ab297bab984"},
189 {"linux-git", "6d4d22468dae"},
190 {"linux-git", "fdaba61ef8a2"},
191 { }
192 }
193 };
194