xref: /linux/tools/testing/selftests/cgroup/test_cpu.c (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #define _GNU_SOURCE
4 #include <linux/limits.h>
5 #include <sys/param.h>
6 #include <sys/sysinfo.h>
7 #include <sys/wait.h>
8 #include <errno.h>
9 #include <pthread.h>
10 #include <stdio.h>
11 #include <time.h>
12 #include <unistd.h>
13 
14 #include "../kselftest.h"
15 #include "cgroup_util.h"
16 
17 enum hog_clock_type {
18 	// Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock.
19 	CPU_HOG_CLOCK_PROCESS,
20 	// Count elapsed time using system wallclock time.
21 	CPU_HOG_CLOCK_WALL,
22 };
23 
24 struct cpu_hogger {
25 	char *cgroup;
26 	pid_t pid;
27 	long usage;
28 };
29 
30 struct cpu_hog_func_param {
31 	int nprocs;
32 	struct timespec ts;
33 	enum hog_clock_type clock_type;
34 };
35 
36 /*
37  * This test creates two nested cgroups with and without enabling
38  * the cpu controller.
39  */
40 static int test_cpucg_subtree_control(const char *root)
41 {
42 	char *parent = NULL, *child = NULL, *parent2 = NULL, *child2 = NULL;
43 	int ret = KSFT_FAIL;
44 
45 	// Create two nested cgroups with the cpu controller enabled.
46 	parent = cg_name(root, "cpucg_test_0");
47 	if (!parent)
48 		goto cleanup;
49 
50 	if (cg_create(parent))
51 		goto cleanup;
52 
53 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
54 		goto cleanup;
55 
56 	child = cg_name(parent, "cpucg_test_child");
57 	if (!child)
58 		goto cleanup;
59 
60 	if (cg_create(child))
61 		goto cleanup;
62 
63 	if (cg_read_strstr(child, "cgroup.controllers", "cpu"))
64 		goto cleanup;
65 
66 	// Create two nested cgroups without enabling the cpu controller.
67 	parent2 = cg_name(root, "cpucg_test_1");
68 	if (!parent2)
69 		goto cleanup;
70 
71 	if (cg_create(parent2))
72 		goto cleanup;
73 
74 	child2 = cg_name(parent2, "cpucg_test_child");
75 	if (!child2)
76 		goto cleanup;
77 
78 	if (cg_create(child2))
79 		goto cleanup;
80 
81 	if (!cg_read_strstr(child2, "cgroup.controllers", "cpu"))
82 		goto cleanup;
83 
84 	ret = KSFT_PASS;
85 
86 cleanup:
87 	cg_destroy(child);
88 	free(child);
89 	cg_destroy(child2);
90 	free(child2);
91 	cg_destroy(parent);
92 	free(parent);
93 	cg_destroy(parent2);
94 	free(parent2);
95 
96 	return ret;
97 }
98 
99 static void *hog_cpu_thread_func(void *arg)
100 {
101 	while (1)
102 		;
103 
104 	return NULL;
105 }
106 
107 static struct timespec
108 timespec_sub(const struct timespec *lhs, const struct timespec *rhs)
109 {
110 	struct timespec zero = {
111 		.tv_sec = 0,
112 		.tv_nsec = 0,
113 	};
114 	struct timespec ret;
115 
116 	if (lhs->tv_sec < rhs->tv_sec)
117 		return zero;
118 
119 	ret.tv_sec = lhs->tv_sec - rhs->tv_sec;
120 
121 	if (lhs->tv_nsec < rhs->tv_nsec) {
122 		if (ret.tv_sec == 0)
123 			return zero;
124 
125 		ret.tv_sec--;
126 		ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec;
127 	} else
128 		ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec;
129 
130 	return ret;
131 }
132 
133 static int hog_cpus_timed(const char *cgroup, void *arg)
134 {
135 	const struct cpu_hog_func_param *param =
136 		(struct cpu_hog_func_param *)arg;
137 	struct timespec ts_run = param->ts;
138 	struct timespec ts_remaining = ts_run;
139 	struct timespec ts_start;
140 	int i, ret;
141 
142 	ret = clock_gettime(CLOCK_MONOTONIC, &ts_start);
143 	if (ret != 0)
144 		return ret;
145 
146 	for (i = 0; i < param->nprocs; i++) {
147 		pthread_t tid;
148 
149 		ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
150 		if (ret != 0)
151 			return ret;
152 	}
153 
154 	while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
155 		struct timespec ts_total;
156 
157 		ret = nanosleep(&ts_remaining, NULL);
158 		if (ret && errno != EINTR)
159 			return ret;
160 
161 		if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
162 			ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total);
163 			if (ret != 0)
164 				return ret;
165 		} else {
166 			struct timespec ts_current;
167 
168 			ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
169 			if (ret != 0)
170 				return ret;
171 
172 			ts_total = timespec_sub(&ts_current, &ts_start);
173 		}
174 
175 		ts_remaining = timespec_sub(&ts_run, &ts_total);
176 	}
177 
178 	return 0;
179 }
180 
181 /*
182  * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that
183  * cpu.stat shows the expected output.
184  */
185 static int test_cpucg_stats(const char *root)
186 {
187 	int ret = KSFT_FAIL;
188 	long usage_usec, user_usec, system_usec;
189 	long usage_seconds = 2;
190 	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
191 	char *cpucg;
192 
193 	cpucg = cg_name(root, "cpucg_test");
194 	if (!cpucg)
195 		goto cleanup;
196 
197 	if (cg_create(cpucg))
198 		goto cleanup;
199 
200 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
201 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
202 	system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec");
203 	if (usage_usec != 0 || user_usec != 0 || system_usec != 0)
204 		goto cleanup;
205 
206 	struct cpu_hog_func_param param = {
207 		.nprocs = 1,
208 		.ts = {
209 			.tv_sec = usage_seconds,
210 			.tv_nsec = 0,
211 		},
212 		.clock_type = CPU_HOG_CLOCK_PROCESS,
213 	};
214 	if (cg_run(cpucg, hog_cpus_timed, (void *)&param))
215 		goto cleanup;
216 
217 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
218 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
219 	if (user_usec <= 0)
220 		goto cleanup;
221 
222 	if (!values_close(usage_usec, expected_usage_usec, 1))
223 		goto cleanup;
224 
225 	ret = KSFT_PASS;
226 
227 cleanup:
228 	cg_destroy(cpucg);
229 	free(cpucg);
230 
231 	return ret;
232 }
233 
234 /*
235  * Creates a nice process that consumes CPU and checks that the elapsed
236  * usertime in the cgroup is close to the expected time.
237  */
238 static int test_cpucg_nice(const char *root)
239 {
240 	int ret = KSFT_FAIL;
241 	int status;
242 	long user_usec, nice_usec;
243 	long usage_seconds = 2;
244 	long expected_nice_usec = usage_seconds * USEC_PER_SEC;
245 	char *cpucg;
246 	pid_t pid;
247 
248 	cpucg = cg_name(root, "cpucg_test");
249 	if (!cpucg)
250 		goto cleanup;
251 
252 	if (cg_create(cpucg))
253 		goto cleanup;
254 
255 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
256 	nice_usec = cg_read_key_long(cpucg, "cpu.stat", "nice_usec");
257 	if (nice_usec == -1)
258 		ret = KSFT_SKIP;
259 	if (user_usec != 0 || nice_usec != 0)
260 		goto cleanup;
261 
262 	/*
263 	 * We fork here to create a new process that can be niced without
264 	 * polluting the nice value of other selftests
265 	 */
266 	pid = fork();
267 	if (pid < 0) {
268 		goto cleanup;
269 	} else if (pid == 0) {
270 		struct cpu_hog_func_param param = {
271 			.nprocs = 1,
272 			.ts = {
273 				.tv_sec = usage_seconds,
274 				.tv_nsec = 0,
275 			},
276 			.clock_type = CPU_HOG_CLOCK_PROCESS,
277 		};
278 		char buf[64];
279 		snprintf(buf, sizeof(buf), "%d", getpid());
280 		if (cg_write(cpucg, "cgroup.procs", buf))
281 			goto cleanup;
282 
283 		/* Try to keep niced CPU usage as constrained to hog_cpu as possible */
284 		nice(1);
285 		hog_cpus_timed(cpucg, &param);
286 		exit(0);
287 	} else {
288 		waitpid(pid, &status, 0);
289 		if (!WIFEXITED(status))
290 			goto cleanup;
291 
292 		user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
293 		nice_usec = cg_read_key_long(cpucg, "cpu.stat", "nice_usec");
294 		if (!values_close(nice_usec, expected_nice_usec, 1))
295 			goto cleanup;
296 
297 		ret = KSFT_PASS;
298 	}
299 
300 cleanup:
301 	cg_destroy(cpucg);
302 	free(cpucg);
303 
304 	return ret;
305 }
306 
307 static int
308 run_cpucg_weight_test(
309 		const char *root,
310 		pid_t (*spawn_child)(const struct cpu_hogger *child),
311 		int (*validate)(const struct cpu_hogger *children, int num_children))
312 {
313 	int ret = KSFT_FAIL, i;
314 	char *parent = NULL;
315 	struct cpu_hogger children[3] = {};
316 
317 	parent = cg_name(root, "cpucg_test_0");
318 	if (!parent)
319 		goto cleanup;
320 
321 	if (cg_create(parent))
322 		goto cleanup;
323 
324 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
325 		goto cleanup;
326 
327 	for (i = 0; i < ARRAY_SIZE(children); i++) {
328 		children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i);
329 		if (!children[i].cgroup)
330 			goto cleanup;
331 
332 		if (cg_create(children[i].cgroup))
333 			goto cleanup;
334 
335 		if (cg_write_numeric(children[i].cgroup, "cpu.weight",
336 					50 * (i + 1)))
337 			goto cleanup;
338 	}
339 
340 	for (i = 0; i < ARRAY_SIZE(children); i++) {
341 		pid_t pid = spawn_child(&children[i]);
342 		if (pid <= 0)
343 			goto cleanup;
344 		children[i].pid = pid;
345 	}
346 
347 	for (i = 0; i < ARRAY_SIZE(children); i++) {
348 		int retcode;
349 
350 		waitpid(children[i].pid, &retcode, 0);
351 		if (!WIFEXITED(retcode))
352 			goto cleanup;
353 		if (WEXITSTATUS(retcode))
354 			goto cleanup;
355 	}
356 
357 	for (i = 0; i < ARRAY_SIZE(children); i++)
358 		children[i].usage = cg_read_key_long(children[i].cgroup,
359 				"cpu.stat", "usage_usec");
360 
361 	if (validate(children, ARRAY_SIZE(children)))
362 		goto cleanup;
363 
364 	ret = KSFT_PASS;
365 cleanup:
366 	for (i = 0; i < ARRAY_SIZE(children); i++) {
367 		cg_destroy(children[i].cgroup);
368 		free(children[i].cgroup);
369 	}
370 	cg_destroy(parent);
371 	free(parent);
372 
373 	return ret;
374 }
375 
376 static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)
377 {
378 	long usage_seconds = 10;
379 	struct cpu_hog_func_param param = {
380 		.nprocs = ncpus,
381 		.ts = {
382 			.tv_sec = usage_seconds,
383 			.tv_nsec = 0,
384 		},
385 		.clock_type = CPU_HOG_CLOCK_WALL,
386 	};
387 	return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)&param);
388 }
389 
390 static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)
391 {
392 	return weight_hog_ncpus(child, get_nprocs());
393 }
394 
395 static int
396 overprovision_validate(const struct cpu_hogger *children, int num_children)
397 {
398 	int ret = KSFT_FAIL, i;
399 
400 	for (i = 0; i < num_children - 1; i++) {
401 		long delta;
402 
403 		if (children[i + 1].usage <= children[i].usage)
404 			goto cleanup;
405 
406 		delta = children[i + 1].usage - children[i].usage;
407 		if (!values_close(delta, children[0].usage, 35))
408 			goto cleanup;
409 	}
410 
411 	ret = KSFT_PASS;
412 cleanup:
413 	return ret;
414 }
415 
416 /*
417  * First, this test creates the following hierarchy:
418  * A
419  * A/B     cpu.weight = 50
420  * A/C     cpu.weight = 100
421  * A/D     cpu.weight = 150
422  *
423  * A separate process is then created for each child cgroup which spawns as
424  * many threads as there are cores, and hogs each CPU as much as possible
425  * for some time interval.
426  *
427  * Once all of the children have exited, we verify that each child cgroup
428  * was given proportional runtime as informed by their cpu.weight.
429  */
430 static int test_cpucg_weight_overprovisioned(const char *root)
431 {
432 	return run_cpucg_weight_test(root, weight_hog_all_cpus,
433 			overprovision_validate);
434 }
435 
436 static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)
437 {
438 	return weight_hog_ncpus(child, 1);
439 }
440 
441 static int
442 underprovision_validate(const struct cpu_hogger *children, int num_children)
443 {
444 	int ret = KSFT_FAIL, i;
445 
446 	for (i = 0; i < num_children - 1; i++) {
447 		if (!values_close(children[i + 1].usage, children[0].usage, 15))
448 			goto cleanup;
449 	}
450 
451 	ret = KSFT_PASS;
452 cleanup:
453 	return ret;
454 }
455 
456 /*
457  * First, this test creates the following hierarchy:
458  * A
459  * A/B     cpu.weight = 50
460  * A/C     cpu.weight = 100
461  * A/D     cpu.weight = 150
462  *
463  * A separate process is then created for each child cgroup which spawns a
464  * single thread that hogs a CPU. The testcase is only run on systems that
465  * have at least one core per-thread in the child processes.
466  *
467  * Once all of the children have exited, we verify that each child cgroup
468  * had roughly the same runtime despite having different cpu.weight.
469  */
470 static int test_cpucg_weight_underprovisioned(const char *root)
471 {
472 	// Only run the test if there are enough cores to avoid overprovisioning
473 	// the system.
474 	if (get_nprocs() < 4)
475 		return KSFT_SKIP;
476 
477 	return run_cpucg_weight_test(root, weight_hog_one_cpu,
478 			underprovision_validate);
479 }
480 
481 static int
482 run_cpucg_nested_weight_test(const char *root, bool overprovisioned)
483 {
484 	int ret = KSFT_FAIL, i;
485 	char *parent = NULL, *child = NULL;
486 	struct cpu_hogger leaf[3] = {};
487 	long nested_leaf_usage, child_usage;
488 	int nprocs = get_nprocs();
489 
490 	if (!overprovisioned) {
491 		if (nprocs < 4)
492 			/*
493 			 * Only run the test if there are enough cores to avoid overprovisioning
494 			 * the system.
495 			 */
496 			return KSFT_SKIP;
497 		nprocs /= 4;
498 	}
499 
500 	parent = cg_name(root, "cpucg_test");
501 	child = cg_name(parent, "cpucg_child");
502 	if (!parent || !child)
503 		goto cleanup;
504 
505 	if (cg_create(parent))
506 		goto cleanup;
507 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
508 		goto cleanup;
509 
510 	if (cg_create(child))
511 		goto cleanup;
512 	if (cg_write(child, "cgroup.subtree_control", "+cpu"))
513 		goto cleanup;
514 	if (cg_write(child, "cpu.weight", "1000"))
515 		goto cleanup;
516 
517 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
518 		const char *ancestor;
519 		long weight;
520 
521 		if (i == 0) {
522 			ancestor = parent;
523 			weight = 1000;
524 		} else {
525 			ancestor = child;
526 			weight = 5000;
527 		}
528 		leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i);
529 		if (!leaf[i].cgroup)
530 			goto cleanup;
531 
532 		if (cg_create(leaf[i].cgroup))
533 			goto cleanup;
534 
535 		if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight))
536 			goto cleanup;
537 	}
538 
539 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
540 		pid_t pid;
541 		struct cpu_hog_func_param param = {
542 			.nprocs = nprocs,
543 			.ts = {
544 				.tv_sec = 10,
545 				.tv_nsec = 0,
546 			},
547 			.clock_type = CPU_HOG_CLOCK_WALL,
548 		};
549 
550 		pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed,
551 				(void *)&param);
552 		if (pid <= 0)
553 			goto cleanup;
554 		leaf[i].pid = pid;
555 	}
556 
557 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
558 		int retcode;
559 
560 		waitpid(leaf[i].pid, &retcode, 0);
561 		if (!WIFEXITED(retcode))
562 			goto cleanup;
563 		if (WEXITSTATUS(retcode))
564 			goto cleanup;
565 	}
566 
567 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
568 		leaf[i].usage = cg_read_key_long(leaf[i].cgroup,
569 				"cpu.stat", "usage_usec");
570 		if (leaf[i].usage <= 0)
571 			goto cleanup;
572 	}
573 
574 	nested_leaf_usage = leaf[1].usage + leaf[2].usage;
575 	if (overprovisioned) {
576 		if (!values_close(leaf[0].usage, nested_leaf_usage, 15))
577 			goto cleanup;
578 	} else if (!values_close(leaf[0].usage * 2, nested_leaf_usage, 15))
579 		goto cleanup;
580 
581 
582 	child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec");
583 	if (child_usage <= 0)
584 		goto cleanup;
585 	if (!values_close(child_usage, nested_leaf_usage, 1))
586 		goto cleanup;
587 
588 	ret = KSFT_PASS;
589 cleanup:
590 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
591 		cg_destroy(leaf[i].cgroup);
592 		free(leaf[i].cgroup);
593 	}
594 	cg_destroy(child);
595 	free(child);
596 	cg_destroy(parent);
597 	free(parent);
598 
599 	return ret;
600 }
601 
602 /*
603  * First, this test creates the following hierarchy:
604  * A
605  * A/B     cpu.weight = 1000
606  * A/C     cpu.weight = 1000
607  * A/C/D   cpu.weight = 5000
608  * A/C/E   cpu.weight = 5000
609  *
610  * A separate process is then created for each leaf, which spawn nproc threads
611  * that burn a CPU for a few seconds.
612  *
613  * Once all of those processes have exited, we verify that each of the leaf
614  * cgroups have roughly the same usage from cpu.stat.
615  */
616 static int
617 test_cpucg_nested_weight_overprovisioned(const char *root)
618 {
619 	return run_cpucg_nested_weight_test(root, true);
620 }
621 
622 /*
623  * First, this test creates the following hierarchy:
624  * A
625  * A/B     cpu.weight = 1000
626  * A/C     cpu.weight = 1000
627  * A/C/D   cpu.weight = 5000
628  * A/C/E   cpu.weight = 5000
629  *
630  * A separate process is then created for each leaf, which nproc / 4 threads
631  * that burns a CPU for a few seconds.
632  *
633  * Once all of those processes have exited, we verify that each of the leaf
634  * cgroups have roughly the same usage from cpu.stat.
635  */
636 static int
637 test_cpucg_nested_weight_underprovisioned(const char *root)
638 {
639 	return run_cpucg_nested_weight_test(root, false);
640 }
641 
642 /*
643  * This test creates a cgroup with some maximum value within a period, and
644  * verifies that a process in the cgroup is not overscheduled.
645  */
646 static int test_cpucg_max(const char *root)
647 {
648 	int ret = KSFT_FAIL;
649 	long quota_usec = 1000;
650 	long default_period_usec = 100000; /* cpu.max's default period */
651 	long duration_seconds = 1;
652 
653 	long duration_usec = duration_seconds * USEC_PER_SEC;
654 	long usage_usec, n_periods, remainder_usec, expected_usage_usec;
655 	char *cpucg;
656 	char quota_buf[32];
657 
658 	snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
659 
660 	cpucg = cg_name(root, "cpucg_test");
661 	if (!cpucg)
662 		goto cleanup;
663 
664 	if (cg_create(cpucg))
665 		goto cleanup;
666 
667 	if (cg_write(cpucg, "cpu.max", quota_buf))
668 		goto cleanup;
669 
670 	struct cpu_hog_func_param param = {
671 		.nprocs = 1,
672 		.ts = {
673 			.tv_sec = duration_seconds,
674 			.tv_nsec = 0,
675 		},
676 		.clock_type = CPU_HOG_CLOCK_WALL,
677 	};
678 	if (cg_run(cpucg, hog_cpus_timed, (void *)&param))
679 		goto cleanup;
680 
681 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
682 	if (usage_usec <= 0)
683 		goto cleanup;
684 
685 	/*
686 	 * The following calculation applies only since
687 	 * the cpu hog is set to run as per wall-clock time
688 	 */
689 	n_periods = duration_usec / default_period_usec;
690 	remainder_usec = duration_usec - n_periods * default_period_usec;
691 	expected_usage_usec
692 		= n_periods * quota_usec + MIN(remainder_usec, quota_usec);
693 
694 	if (!values_close(usage_usec, expected_usage_usec, 10))
695 		goto cleanup;
696 
697 	ret = KSFT_PASS;
698 
699 cleanup:
700 	cg_destroy(cpucg);
701 	free(cpucg);
702 
703 	return ret;
704 }
705 
706 /*
707  * This test verifies that a process inside of a nested cgroup whose parent
708  * group has a cpu.max value set, is properly throttled.
709  */
710 static int test_cpucg_max_nested(const char *root)
711 {
712 	int ret = KSFT_FAIL;
713 	long quota_usec = 1000;
714 	long default_period_usec = 100000; /* cpu.max's default period */
715 	long duration_seconds = 1;
716 
717 	long duration_usec = duration_seconds * USEC_PER_SEC;
718 	long usage_usec, n_periods, remainder_usec, expected_usage_usec;
719 	char *parent, *child;
720 	char quota_buf[32];
721 
722 	snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
723 
724 	parent = cg_name(root, "cpucg_parent");
725 	child = cg_name(parent, "cpucg_child");
726 	if (!parent || !child)
727 		goto cleanup;
728 
729 	if (cg_create(parent))
730 		goto cleanup;
731 
732 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
733 		goto cleanup;
734 
735 	if (cg_create(child))
736 		goto cleanup;
737 
738 	if (cg_write(parent, "cpu.max", quota_buf))
739 		goto cleanup;
740 
741 	struct cpu_hog_func_param param = {
742 		.nprocs = 1,
743 		.ts = {
744 			.tv_sec = duration_seconds,
745 			.tv_nsec = 0,
746 		},
747 		.clock_type = CPU_HOG_CLOCK_WALL,
748 	};
749 	if (cg_run(child, hog_cpus_timed, (void *)&param))
750 		goto cleanup;
751 
752 	usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec");
753 	if (usage_usec <= 0)
754 		goto cleanup;
755 
756 	/*
757 	 * The following calculation applies only since
758 	 * the cpu hog is set to run as per wall-clock time
759 	 */
760 	n_periods = duration_usec / default_period_usec;
761 	remainder_usec = duration_usec - n_periods * default_period_usec;
762 	expected_usage_usec
763 		= n_periods * quota_usec + MIN(remainder_usec, quota_usec);
764 
765 	if (!values_close(usage_usec, expected_usage_usec, 10))
766 		goto cleanup;
767 
768 	ret = KSFT_PASS;
769 
770 cleanup:
771 	cg_destroy(child);
772 	free(child);
773 	cg_destroy(parent);
774 	free(parent);
775 
776 	return ret;
777 }
778 
779 #define T(x) { x, #x }
780 struct cpucg_test {
781 	int (*fn)(const char *root);
782 	const char *name;
783 } tests[] = {
784 	T(test_cpucg_subtree_control),
785 	T(test_cpucg_stats),
786 	T(test_cpucg_nice),
787 	T(test_cpucg_weight_overprovisioned),
788 	T(test_cpucg_weight_underprovisioned),
789 	T(test_cpucg_nested_weight_overprovisioned),
790 	T(test_cpucg_nested_weight_underprovisioned),
791 	T(test_cpucg_max),
792 	T(test_cpucg_max_nested),
793 };
794 #undef T
795 
796 int main(int argc, char *argv[])
797 {
798 	char root[PATH_MAX];
799 	int i, ret = EXIT_SUCCESS;
800 
801 	if (cg_find_unified_root(root, sizeof(root), NULL))
802 		ksft_exit_skip("cgroup v2 isn't mounted\n");
803 
804 	if (cg_read_strstr(root, "cgroup.subtree_control", "cpu"))
805 		if (cg_write(root, "cgroup.subtree_control", "+cpu"))
806 			ksft_exit_skip("Failed to set cpu controller\n");
807 
808 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
809 		switch (tests[i].fn(root)) {
810 		case KSFT_PASS:
811 			ksft_test_result_pass("%s\n", tests[i].name);
812 			break;
813 		case KSFT_SKIP:
814 			ksft_test_result_skip("%s\n", tests[i].name);
815 			break;
816 		default:
817 			ret = EXIT_FAILURE;
818 			ksft_test_result_fail("%s\n", tests[i].name);
819 			break;
820 		}
821 	}
822 
823 	return ret;
824 }
825