1 // SPDX-License-Identifier: GPL-2.0
4 #include <linux/limits.h>
5 #include <sys/sysinfo.h>
12 #include "../kselftest.h"
13 #include "cgroup_util.h"
16 // Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock.
17 CPU_HOG_CLOCK_PROCESS,
18 // Count elapsed time using system wallclock time.
28 struct cpu_hog_func_param {
31 enum hog_clock_type clock_type;
35 * This test creates two nested cgroups with and without enabling
38 static int test_cpucg_subtree_control(const char *root)
40 char *parent = NULL, *child = NULL, *parent2 = NULL, *child2 = NULL;
43 // Create two nested cgroups with the cpu controller enabled.
44 parent = cg_name(root, "cpucg_test_0");
48 if (cg_create(parent))
51 if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
54 child = cg_name(parent, "cpucg_test_child");
61 if (cg_read_strstr(child, "cgroup.controllers", "cpu"))
64 // Create two nested cgroups without enabling the cpu controller.
65 parent2 = cg_name(root, "cpucg_test_1");
69 if (cg_create(parent2))
72 child2 = cg_name(parent2, "cpucg_test_child");
76 if (cg_create(child2))
79 if (!cg_read_strstr(child2, "cgroup.controllers", "cpu"))
97 static void *hog_cpu_thread_func(void *arg)
105 static struct timespec
106 timespec_sub(const struct timespec *lhs, const struct timespec *rhs)
108 struct timespec zero = {
114 if (lhs->tv_sec < rhs->tv_sec)
117 ret.tv_sec = lhs->tv_sec - rhs->tv_sec;
119 if (lhs->tv_nsec < rhs->tv_nsec) {
124 ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec;
126 ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec;
131 static int hog_cpus_timed(const char *cgroup, void *arg)
133 const struct cpu_hog_func_param *param =
134 (struct cpu_hog_func_param *)arg;
135 struct timespec ts_run = param->ts;
136 struct timespec ts_remaining = ts_run;
137 struct timespec ts_start;
140 ret = clock_gettime(CLOCK_MONOTONIC, &ts_start);
144 for (i = 0; i < param->nprocs; i++) {
147 ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
152 while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
153 struct timespec ts_total;
155 ret = nanosleep(&ts_remaining, NULL);
156 if (ret && errno != EINTR)
159 if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
160 ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total);
164 struct timespec ts_current;
166 ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
170 ts_total = timespec_sub(&ts_current, &ts_start);
173 ts_remaining = timespec_sub(&ts_run, &ts_total);
180 * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that
181 * cpu.stat shows the expected output.
183 static int test_cpucg_stats(const char *root)
186 long usage_usec, user_usec, system_usec;
187 long usage_seconds = 2;
188 long expected_usage_usec = usage_seconds * USEC_PER_SEC;
191 cpucg = cg_name(root, "cpucg_test");
195 if (cg_create(cpucg))
198 usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
199 user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
200 system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec");
201 if (usage_usec != 0 || user_usec != 0 || system_usec != 0)
204 struct cpu_hog_func_param param = {
207 .tv_sec = usage_seconds,
210 .clock_type = CPU_HOG_CLOCK_PROCESS,
212 if (cg_run(cpucg, hog_cpus_timed, (void *)¶m))
215 usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
216 user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
220 if (!values_close(usage_usec, expected_usage_usec, 1))
233 run_cpucg_weight_test(
235 pid_t (*spawn_child)(const struct cpu_hogger *child),
236 int (*validate)(const struct cpu_hogger *children, int num_children))
238 int ret = KSFT_FAIL, i;
240 struct cpu_hogger children[3] = {NULL};
242 parent = cg_name(root, "cpucg_test_0");
246 if (cg_create(parent))
249 if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
252 for (i = 0; i < ARRAY_SIZE(children); i++) {
253 children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i);
254 if (!children[i].cgroup)
257 if (cg_create(children[i].cgroup))
260 if (cg_write_numeric(children[i].cgroup, "cpu.weight",
265 for (i = 0; i < ARRAY_SIZE(children); i++) {
266 pid_t pid = spawn_child(&children[i]);
269 children[i].pid = pid;
272 for (i = 0; i < ARRAY_SIZE(children); i++) {
275 waitpid(children[i].pid, &retcode, 0);
276 if (!WIFEXITED(retcode))
278 if (WEXITSTATUS(retcode))
282 for (i = 0; i < ARRAY_SIZE(children); i++)
283 children[i].usage = cg_read_key_long(children[i].cgroup,
284 "cpu.stat", "usage_usec");
286 if (validate(children, ARRAY_SIZE(children)))
291 for (i = 0; i < ARRAY_SIZE(children); i++) {
292 cg_destroy(children[i].cgroup);
293 free(children[i].cgroup);
301 static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)
303 long usage_seconds = 10;
304 struct cpu_hog_func_param param = {
307 .tv_sec = usage_seconds,
310 .clock_type = CPU_HOG_CLOCK_WALL,
312 return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)¶m);
315 static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)
317 return weight_hog_ncpus(child, get_nprocs());
321 overprovision_validate(const struct cpu_hogger *children, int num_children)
323 int ret = KSFT_FAIL, i;
325 for (i = 0; i < num_children - 1; i++) {
328 if (children[i + 1].usage <= children[i].usage)
331 delta = children[i + 1].usage - children[i].usage;
332 if (!values_close(delta, children[0].usage, 35))
342 * First, this test creates the following hierarchy:
344 * A/B cpu.weight = 50
345 * A/C cpu.weight = 100
346 * A/D cpu.weight = 150
348 * A separate process is then created for each child cgroup which spawns as
349 * many threads as there are cores, and hogs each CPU as much as possible
350 * for some time interval.
352 * Once all of the children have exited, we verify that each child cgroup
353 * was given proportional runtime as informed by their cpu.weight.
355 static int test_cpucg_weight_overprovisioned(const char *root)
357 return run_cpucg_weight_test(root, weight_hog_all_cpus,
358 overprovision_validate);
361 static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)
363 return weight_hog_ncpus(child, 1);
367 underprovision_validate(const struct cpu_hogger *children, int num_children)
369 int ret = KSFT_FAIL, i;
371 for (i = 0; i < num_children - 1; i++) {
372 if (!values_close(children[i + 1].usage, children[0].usage, 15))
382 * First, this test creates the following hierarchy:
384 * A/B cpu.weight = 50
385 * A/C cpu.weight = 100
386 * A/D cpu.weight = 150
388 * A separate process is then created for each child cgroup which spawns a
389 * single thread that hogs a CPU. The testcase is only run on systems that
390 * have at least one core per-thread in the child processes.
392 * Once all of the children have exited, we verify that each child cgroup
393 * had roughly the same runtime despite having different cpu.weight.
395 static int test_cpucg_weight_underprovisioned(const char *root)
397 // Only run the test if there are enough cores to avoid overprovisioning
399 if (get_nprocs() < 4)
402 return run_cpucg_weight_test(root, weight_hog_one_cpu,
403 underprovision_validate);
407 run_cpucg_nested_weight_test(const char *root, bool overprovisioned)
409 int ret = KSFT_FAIL, i;
410 char *parent = NULL, *child = NULL;
411 struct cpu_hogger leaf[3] = {NULL};
412 long nested_leaf_usage, child_usage;
413 int nprocs = get_nprocs();
415 if (!overprovisioned) {
418 * Only run the test if there are enough cores to avoid overprovisioning
425 parent = cg_name(root, "cpucg_test");
426 child = cg_name(parent, "cpucg_child");
427 if (!parent || !child)
430 if (cg_create(parent))
432 if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
435 if (cg_create(child))
437 if (cg_write(child, "cgroup.subtree_control", "+cpu"))
439 if (cg_write(child, "cpu.weight", "1000"))
442 for (i = 0; i < ARRAY_SIZE(leaf); i++) {
443 const char *ancestor;
453 leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i);
457 if (cg_create(leaf[i].cgroup))
460 if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight))
464 for (i = 0; i < ARRAY_SIZE(leaf); i++) {
466 struct cpu_hog_func_param param = {
472 .clock_type = CPU_HOG_CLOCK_WALL,
475 pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed,
482 for (i = 0; i < ARRAY_SIZE(leaf); i++) {
485 waitpid(leaf[i].pid, &retcode, 0);
486 if (!WIFEXITED(retcode))
488 if (WEXITSTATUS(retcode))
492 for (i = 0; i < ARRAY_SIZE(leaf); i++) {
493 leaf[i].usage = cg_read_key_long(leaf[i].cgroup,
494 "cpu.stat", "usage_usec");
495 if (leaf[i].usage <= 0)
499 nested_leaf_usage = leaf[1].usage + leaf[2].usage;
500 if (overprovisioned) {
501 if (!values_close(leaf[0].usage, nested_leaf_usage, 15))
503 } else if (!values_close(leaf[0].usage * 2, nested_leaf_usage, 15))
507 child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec");
508 if (child_usage <= 0)
510 if (!values_close(child_usage, nested_leaf_usage, 1))
515 for (i = 0; i < ARRAY_SIZE(leaf); i++) {
516 cg_destroy(leaf[i].cgroup);
517 free(leaf[i].cgroup);
528 * First, this test creates the following hierarchy:
530 * A/B cpu.weight = 1000
531 * A/C cpu.weight = 1000
532 * A/C/D cpu.weight = 5000
533 * A/C/E cpu.weight = 5000
535 * A separate process is then created for each leaf, which spawn nproc threads
536 * that burn a CPU for a few seconds.
538 * Once all of those processes have exited, we verify that each of the leaf
539 * cgroups have roughly the same usage from cpu.stat.
542 test_cpucg_nested_weight_overprovisioned(const char *root)
544 return run_cpucg_nested_weight_test(root, true);
548 * First, this test creates the following hierarchy:
550 * A/B cpu.weight = 1000
551 * A/C cpu.weight = 1000
552 * A/C/D cpu.weight = 5000
553 * A/C/E cpu.weight = 5000
555 * A separate process is then created for each leaf, which nproc / 4 threads
556 * that burns a CPU for a few seconds.
558 * Once all of those processes have exited, we verify that each of the leaf
559 * cgroups have roughly the same usage from cpu.stat.
562 test_cpucg_nested_weight_underprovisioned(const char *root)
564 return run_cpucg_nested_weight_test(root, false);
568 * This test creates a cgroup with some maximum value within a period, and
569 * verifies that a process in the cgroup is not overscheduled.
571 static int test_cpucg_max(const char *root)
574 long usage_usec, user_usec;
575 long usage_seconds = 1;
576 long expected_usage_usec = usage_seconds * USEC_PER_SEC;
579 cpucg = cg_name(root, "cpucg_test");
583 if (cg_create(cpucg))
586 if (cg_write(cpucg, "cpu.max", "1000"))
589 struct cpu_hog_func_param param = {
592 .tv_sec = usage_seconds,
595 .clock_type = CPU_HOG_CLOCK_WALL,
597 if (cg_run(cpucg, hog_cpus_timed, (void *)¶m))
600 usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
601 user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
605 if (user_usec >= expected_usage_usec)
608 if (values_close(usage_usec, expected_usage_usec, 95))
621 * This test verifies that a process inside of a nested cgroup whose parent
622 * group has a cpu.max value set, is properly throttled.
624 static int test_cpucg_max_nested(const char *root)
627 long usage_usec, user_usec;
628 long usage_seconds = 1;
629 long expected_usage_usec = usage_seconds * USEC_PER_SEC;
630 char *parent, *child;
632 parent = cg_name(root, "cpucg_parent");
633 child = cg_name(parent, "cpucg_child");
634 if (!parent || !child)
637 if (cg_create(parent))
640 if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
643 if (cg_create(child))
646 if (cg_write(parent, "cpu.max", "1000"))
649 struct cpu_hog_func_param param = {
652 .tv_sec = usage_seconds,
655 .clock_type = CPU_HOG_CLOCK_WALL,
657 if (cg_run(child, hog_cpus_timed, (void *)¶m))
660 usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec");
661 user_usec = cg_read_key_long(child, "cpu.stat", "user_usec");
665 if (user_usec >= expected_usage_usec)
668 if (values_close(usage_usec, expected_usage_usec, 95))
682 #define T(x) { x, #x }
684 int (*fn)(const char *root);
687 T(test_cpucg_subtree_control),
689 T(test_cpucg_weight_overprovisioned),
690 T(test_cpucg_weight_underprovisioned),
691 T(test_cpucg_nested_weight_overprovisioned),
692 T(test_cpucg_nested_weight_underprovisioned),
694 T(test_cpucg_max_nested),
698 int main(int argc, char *argv[])
701 int i, ret = EXIT_SUCCESS;
703 if (cg_find_unified_root(root, sizeof(root)))
704 ksft_exit_skip("cgroup v2 isn't mounted\n");
706 if (cg_read_strstr(root, "cgroup.subtree_control", "cpu"))
707 if (cg_write(root, "cgroup.subtree_control", "+cpu"))
708 ksft_exit_skip("Failed to set cpu controller\n");
710 for (i = 0; i < ARRAY_SIZE(tests); i++) {
711 switch (tests[i].fn(root)) {
713 ksft_test_result_pass("%s\n", tests[i].name);
716 ksft_test_result_skip("%s\n", tests[i].name);
720 ksft_test_result_fail("%s\n", tests[i].name);