xref: /linux/tools/testing/selftests/cgroup/test_cpu.c (revision 40d8c81577db09b71ee5402ba336b642d32d6a82)
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  */
test_cpucg_subtree_control(const char * root)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 
hog_cpu_thread_func(void * arg)99 static void *hog_cpu_thread_func(void *arg)
100 {
101 	while (1)
102 		;
103 
104 	return NULL;
105 }
106 
107 static struct timespec
timespec_sub(const struct timespec * lhs,const struct timespec * rhs)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 
hog_cpus_timed(const char * cgroup,void * arg)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  */
test_cpucg_stats(const char * root)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_report(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  */
test_cpucg_nice(const char * root)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 			exit(EXIT_FAILURE);
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 (user_usec <= 0)
295 			goto cleanup;
296 		if (!values_close_report(nice_usec, expected_nice_usec, 1))
297 			goto cleanup;
298 
299 		ret = KSFT_PASS;
300 	}
301 
302 cleanup:
303 	cg_destroy(cpucg);
304 	free(cpucg);
305 
306 	return ret;
307 }
308 
309 static int
run_cpucg_weight_test(const char * root,pid_t (* spawn_child)(const struct cpu_hogger * child),int (* validate)(const struct cpu_hogger * children,int num_children))310 run_cpucg_weight_test(
311 		const char *root,
312 		pid_t (*spawn_child)(const struct cpu_hogger *child),
313 		int (*validate)(const struct cpu_hogger *children, int num_children))
314 {
315 	int ret = KSFT_FAIL, i;
316 	char *parent = NULL;
317 	struct cpu_hogger children[3] = {};
318 
319 	parent = cg_name(root, "cpucg_test_0");
320 	if (!parent)
321 		goto cleanup;
322 
323 	if (cg_create(parent))
324 		goto cleanup;
325 
326 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
327 		goto cleanup;
328 
329 	for (i = 0; i < ARRAY_SIZE(children); i++) {
330 		children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i);
331 		if (!children[i].cgroup)
332 			goto cleanup;
333 
334 		if (cg_create(children[i].cgroup))
335 			goto cleanup;
336 
337 		if (cg_write_numeric(children[i].cgroup, "cpu.weight",
338 					50 * (i + 1)))
339 			goto cleanup;
340 	}
341 
342 	for (i = 0; i < ARRAY_SIZE(children); i++) {
343 		pid_t pid = spawn_child(&children[i]);
344 		if (pid <= 0)
345 			goto cleanup;
346 		children[i].pid = pid;
347 	}
348 
349 	for (i = 0; i < ARRAY_SIZE(children); i++) {
350 		int retcode;
351 
352 		waitpid(children[i].pid, &retcode, 0);
353 		if (!WIFEXITED(retcode))
354 			goto cleanup;
355 		if (WEXITSTATUS(retcode))
356 			goto cleanup;
357 	}
358 
359 	for (i = 0; i < ARRAY_SIZE(children); i++)
360 		children[i].usage = cg_read_key_long(children[i].cgroup,
361 				"cpu.stat", "usage_usec");
362 
363 	if (validate(children, ARRAY_SIZE(children)))
364 		goto cleanup;
365 
366 	ret = KSFT_PASS;
367 cleanup:
368 	for (i = 0; i < ARRAY_SIZE(children); i++) {
369 		cg_destroy(children[i].cgroup);
370 		free(children[i].cgroup);
371 	}
372 	cg_destroy(parent);
373 	free(parent);
374 
375 	return ret;
376 }
377 
weight_hog_ncpus(const struct cpu_hogger * child,int ncpus)378 static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)
379 {
380 	long usage_seconds = 10;
381 	struct cpu_hog_func_param param = {
382 		.nprocs = ncpus,
383 		.ts = {
384 			.tv_sec = usage_seconds,
385 			.tv_nsec = 0,
386 		},
387 		.clock_type = CPU_HOG_CLOCK_WALL,
388 	};
389 	return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)&param);
390 }
391 
weight_hog_all_cpus(const struct cpu_hogger * child)392 static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)
393 {
394 	return weight_hog_ncpus(child, get_nprocs());
395 }
396 
397 static int
overprovision_validate(const struct cpu_hogger * children,int num_children)398 overprovision_validate(const struct cpu_hogger *children, int num_children)
399 {
400 	int ret = KSFT_FAIL, i;
401 
402 	for (i = 0; i < num_children - 1; i++) {
403 		long delta;
404 
405 		if (children[i + 1].usage <= children[i].usage)
406 			goto cleanup;
407 
408 		delta = children[i + 1].usage - children[i].usage;
409 		if (!values_close_report(delta, children[0].usage, 35))
410 			goto cleanup;
411 	}
412 
413 	ret = KSFT_PASS;
414 cleanup:
415 	return ret;
416 }
417 
418 /*
419  * First, this test creates the following hierarchy:
420  * A
421  * A/B     cpu.weight = 50
422  * A/C     cpu.weight = 100
423  * A/D     cpu.weight = 150
424  *
425  * A separate process is then created for each child cgroup which spawns as
426  * many threads as there are cores, and hogs each CPU as much as possible
427  * for some time interval.
428  *
429  * Once all of the children have exited, we verify that each child cgroup
430  * was given proportional runtime as informed by their cpu.weight.
431  */
test_cpucg_weight_overprovisioned(const char * root)432 static int test_cpucg_weight_overprovisioned(const char *root)
433 {
434 	return run_cpucg_weight_test(root, weight_hog_all_cpus,
435 			overprovision_validate);
436 }
437 
weight_hog_one_cpu(const struct cpu_hogger * child)438 static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)
439 {
440 	return weight_hog_ncpus(child, 1);
441 }
442 
443 static int
underprovision_validate(const struct cpu_hogger * children,int num_children)444 underprovision_validate(const struct cpu_hogger *children, int num_children)
445 {
446 	int ret = KSFT_FAIL, i;
447 
448 	for (i = 0; i < num_children - 1; i++) {
449 		if (!values_close_report(children[i + 1].usage, children[0].usage, 15))
450 			goto cleanup;
451 	}
452 
453 	ret = KSFT_PASS;
454 cleanup:
455 	return ret;
456 }
457 
458 /*
459  * First, this test creates the following hierarchy:
460  * A
461  * A/B     cpu.weight = 50
462  * A/C     cpu.weight = 100
463  * A/D     cpu.weight = 150
464  *
465  * A separate process is then created for each child cgroup which spawns a
466  * single thread that hogs a CPU. The testcase is only run on systems that
467  * have at least one core per-thread in the child processes.
468  *
469  * Once all of the children have exited, we verify that each child cgroup
470  * had roughly the same runtime despite having different cpu.weight.
471  */
test_cpucg_weight_underprovisioned(const char * root)472 static int test_cpucg_weight_underprovisioned(const char *root)
473 {
474 	// Only run the test if there are enough cores to avoid overprovisioning
475 	// the system.
476 	if (get_nprocs() < 4)
477 		return KSFT_SKIP;
478 
479 	return run_cpucg_weight_test(root, weight_hog_one_cpu,
480 			underprovision_validate);
481 }
482 
483 static int
run_cpucg_nested_weight_test(const char * root,bool overprovisioned)484 run_cpucg_nested_weight_test(const char *root, bool overprovisioned)
485 {
486 	int ret = KSFT_FAIL, i;
487 	char *parent = NULL, *child = NULL;
488 	struct cpu_hogger leaf[3] = {};
489 	long nested_leaf_usage, child_usage;
490 	int nprocs = get_nprocs();
491 
492 	if (!overprovisioned) {
493 		if (nprocs < 4)
494 			/*
495 			 * Only run the test if there are enough cores to avoid overprovisioning
496 			 * the system.
497 			 */
498 			return KSFT_SKIP;
499 		nprocs /= 4;
500 	}
501 
502 	parent = cg_name(root, "cpucg_test");
503 	child = cg_name(parent, "cpucg_child");
504 	if (!parent || !child)
505 		goto cleanup;
506 
507 	if (cg_create(parent))
508 		goto cleanup;
509 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
510 		goto cleanup;
511 
512 	if (cg_create(child))
513 		goto cleanup;
514 	if (cg_write(child, "cgroup.subtree_control", "+cpu"))
515 		goto cleanup;
516 	if (cg_write(child, "cpu.weight", "1000"))
517 		goto cleanup;
518 
519 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
520 		const char *ancestor;
521 		long weight;
522 
523 		if (i == 0) {
524 			ancestor = parent;
525 			weight = 1000;
526 		} else {
527 			ancestor = child;
528 			weight = 5000;
529 		}
530 		leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i);
531 		if (!leaf[i].cgroup)
532 			goto cleanup;
533 
534 		if (cg_create(leaf[i].cgroup))
535 			goto cleanup;
536 
537 		if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight))
538 			goto cleanup;
539 	}
540 
541 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
542 		pid_t pid;
543 		struct cpu_hog_func_param param = {
544 			.nprocs = nprocs,
545 			.ts = {
546 				.tv_sec = 10,
547 				.tv_nsec = 0,
548 			},
549 			.clock_type = CPU_HOG_CLOCK_WALL,
550 		};
551 
552 		pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed,
553 				(void *)&param);
554 		if (pid <= 0)
555 			goto cleanup;
556 		leaf[i].pid = pid;
557 	}
558 
559 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
560 		int retcode;
561 
562 		waitpid(leaf[i].pid, &retcode, 0);
563 		if (!WIFEXITED(retcode))
564 			goto cleanup;
565 		if (WEXITSTATUS(retcode))
566 			goto cleanup;
567 	}
568 
569 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
570 		leaf[i].usage = cg_read_key_long(leaf[i].cgroup,
571 				"cpu.stat", "usage_usec");
572 		if (leaf[i].usage <= 0)
573 			goto cleanup;
574 	}
575 
576 	nested_leaf_usage = leaf[1].usage + leaf[2].usage;
577 	if (overprovisioned) {
578 		if (!values_close_report(leaf[0].usage, nested_leaf_usage, 15))
579 			goto cleanup;
580 	} else if (!values_close_report(leaf[0].usage * 2, nested_leaf_usage, 15))
581 		goto cleanup;
582 
583 
584 	child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec");
585 	if (child_usage <= 0)
586 		goto cleanup;
587 	if (!values_close_report(child_usage, nested_leaf_usage, 1))
588 		goto cleanup;
589 
590 	ret = KSFT_PASS;
591 cleanup:
592 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
593 		cg_destroy(leaf[i].cgroup);
594 		free(leaf[i].cgroup);
595 	}
596 	cg_destroy(child);
597 	free(child);
598 	cg_destroy(parent);
599 	free(parent);
600 
601 	return ret;
602 }
603 
604 /*
605  * First, this test creates the following hierarchy:
606  * A
607  * A/B     cpu.weight = 1000
608  * A/C     cpu.weight = 1000
609  * A/C/D   cpu.weight = 5000
610  * A/C/E   cpu.weight = 5000
611  *
612  * A separate process is then created for each leaf, which spawn nproc threads
613  * that burn a CPU for a few seconds.
614  *
615  * Once all of those processes have exited, we verify that each of the leaf
616  * cgroups have roughly the same usage from cpu.stat.
617  */
618 static int
test_cpucg_nested_weight_overprovisioned(const char * root)619 test_cpucg_nested_weight_overprovisioned(const char *root)
620 {
621 	return run_cpucg_nested_weight_test(root, true);
622 }
623 
624 /*
625  * First, this test creates the following hierarchy:
626  * A
627  * A/B     cpu.weight = 1000
628  * A/C     cpu.weight = 1000
629  * A/C/D   cpu.weight = 5000
630  * A/C/E   cpu.weight = 5000
631  *
632  * A separate process is then created for each leaf, which nproc / 4 threads
633  * that burns a CPU for a few seconds.
634  *
635  * Once all of those processes have exited, we verify that each of the leaf
636  * cgroups have roughly the same usage from cpu.stat.
637  */
638 static int
test_cpucg_nested_weight_underprovisioned(const char * root)639 test_cpucg_nested_weight_underprovisioned(const char *root)
640 {
641 	return run_cpucg_nested_weight_test(root, false);
642 }
643 
644 /*
645  * Best effort attempt to get the kernel's HZ value from the config.
646  * Return the HZ value if found otherwise return 1000 (the default) to
647  * indicate failure.
648  */
649 static long
get_config_hz(void)650 get_config_hz(void)
651 {
652 	long hz = 1000;
653 	FILE *f;
654 	char cmd[256] = "zcat /proc/config.gz 2>/dev/null | grep '^CONFIG_HZ='";
655 
656 	f = popen(cmd, "r");
657 
658 	if (!f)
659 		return hz;
660 
661 	if (fscanf(f, "CONFIG_HZ=%ld", &hz) == EOF)
662 		goto out;
663 
664 out:
665 	pclose(f);
666 	return hz;
667 }
668 
669 /*
670  * This test creates a cgroup with some maximum value within a period, and
671  * verifies that a process in the cgroup is not overscheduled.
672  */
test_cpucg_max(const char * root)673 static int test_cpucg_max(const char *root)
674 {
675 	int ret = KSFT_FAIL;
676 	long hz = get_config_hz();
677 	long quota_usec = 1000;
678 	long default_period_usec = 100000; /* cpu.max's default period */
679 	long duration_seconds = 1;
680 
681 	long duration_usec;
682 	long usage_usec, n_periods, remainder_usec, expected_usage_usec;
683 	char *cpucg;
684 	char quota_buf[32];
685 
686 	duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz;
687 
688 	snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
689 
690 	cpucg = cg_name(root, "cpucg_test");
691 	if (!cpucg)
692 		goto cleanup;
693 
694 	if (cg_create(cpucg))
695 		goto cleanup;
696 
697 	if (cg_write(cpucg, "cpu.max", quota_buf))
698 		goto cleanup;
699 
700 	struct cpu_hog_func_param param = {
701 		.nprocs = 1,
702 		.ts = {
703 			.tv_sec = duration_usec / USEC_PER_SEC,
704 			.tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC,
705 		},
706 		.clock_type = CPU_HOG_CLOCK_WALL,
707 	};
708 	if (cg_run(cpucg, hog_cpus_timed, (void *)&param))
709 		goto cleanup;
710 
711 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
712 	if (usage_usec <= 0)
713 		goto cleanup;
714 
715 	/*
716 	 * The following calculation applies only since
717 	 * the cpu hog is set to run as per wall-clock time
718 	 */
719 	n_periods = duration_usec / default_period_usec;
720 	remainder_usec = duration_usec - n_periods * default_period_usec;
721 	expected_usage_usec
722 		= n_periods * quota_usec + MIN(remainder_usec, quota_usec);
723 
724 	if (!values_close_report(usage_usec, expected_usage_usec, 10))
725 		goto cleanup;
726 
727 	ret = KSFT_PASS;
728 
729 cleanup:
730 	cg_destroy(cpucg);
731 	free(cpucg);
732 
733 	return ret;
734 }
735 
736 /*
737  * This test verifies that a process inside of a nested cgroup whose parent
738  * group has a cpu.max value set, is properly throttled.
739  */
test_cpucg_max_nested(const char * root)740 static int test_cpucg_max_nested(const char *root)
741 {
742 	int ret = KSFT_FAIL;
743 	long hz = get_config_hz();
744 	long quota_usec = 1000;
745 	long default_period_usec = 100000; /* cpu.max's default period */
746 	long duration_seconds = 1;
747 
748 	long duration_usec;
749 	long usage_usec, n_periods, remainder_usec, expected_usage_usec;
750 	char *parent, *child;
751 	char quota_buf[32];
752 
753 	duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz;
754 
755 	snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
756 
757 	parent = cg_name(root, "cpucg_parent");
758 	child = cg_name(parent, "cpucg_child");
759 	if (!parent || !child)
760 		goto cleanup;
761 
762 	if (cg_create(parent))
763 		goto cleanup;
764 
765 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
766 		goto cleanup;
767 
768 	if (cg_create(child))
769 		goto cleanup;
770 
771 	if (cg_write(parent, "cpu.max", quota_buf))
772 		goto cleanup;
773 
774 	struct cpu_hog_func_param param = {
775 		.nprocs = 1,
776 		.ts = {
777 			.tv_sec = duration_usec / USEC_PER_SEC,
778 			.tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC,
779 		},
780 		.clock_type = CPU_HOG_CLOCK_WALL,
781 	};
782 	if (cg_run(child, hog_cpus_timed, (void *)&param))
783 		goto cleanup;
784 
785 	usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec");
786 	if (usage_usec <= 0)
787 		goto cleanup;
788 
789 	/*
790 	 * The following calculation applies only since
791 	 * the cpu hog is set to run as per wall-clock time
792 	 */
793 	n_periods = duration_usec / default_period_usec;
794 	remainder_usec = duration_usec - n_periods * default_period_usec;
795 	expected_usage_usec
796 		= n_periods * quota_usec + MIN(remainder_usec, quota_usec);
797 
798 	if (!values_close_report(usage_usec, expected_usage_usec, 10))
799 		goto cleanup;
800 
801 	ret = KSFT_PASS;
802 
803 cleanup:
804 	cg_destroy(child);
805 	free(child);
806 	cg_destroy(parent);
807 	free(parent);
808 
809 	return ret;
810 }
811 
812 #define T(x) { x, #x }
813 struct cpucg_test {
814 	int (*fn)(const char *root);
815 	const char *name;
816 } tests[] = {
817 	T(test_cpucg_subtree_control),
818 	T(test_cpucg_stats),
819 	T(test_cpucg_nice),
820 	T(test_cpucg_weight_overprovisioned),
821 	T(test_cpucg_weight_underprovisioned),
822 	T(test_cpucg_nested_weight_overprovisioned),
823 	T(test_cpucg_nested_weight_underprovisioned),
824 	T(test_cpucg_max),
825 	T(test_cpucg_max_nested),
826 };
827 #undef T
828 
main(int argc,char * argv[])829 int main(int argc, char *argv[])
830 {
831 	char root[PATH_MAX];
832 	int i;
833 
834 	ksft_print_header();
835 	ksft_set_plan(ARRAY_SIZE(tests));
836 	if (cg_find_unified_root(root, sizeof(root), NULL))
837 		ksft_exit_skip("cgroup v2 isn't mounted\n");
838 
839 	if (cg_read_strstr(root, "cgroup.subtree_control", "cpu"))
840 		if (cg_write(root, "cgroup.subtree_control", "+cpu"))
841 			ksft_exit_skip("Failed to set cpu controller\n");
842 
843 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
844 		switch (tests[i].fn(root)) {
845 		case KSFT_PASS:
846 			ksft_test_result_pass("%s\n", tests[i].name);
847 			break;
848 		case KSFT_SKIP:
849 			ksft_test_result_skip("%s\n", tests[i].name);
850 			break;
851 		default:
852 			ksft_test_result_fail("%s\n", tests[i].name);
853 			break;
854 		}
855 	}
856 
857 	ksft_finished();
858 }
859