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 *)¶m))
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, ¶m);
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 *)¶m);
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 *)¶m);
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 *)¶m))
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 *)¶m))
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