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