• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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