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