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