Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/cgroup/test_cpu.c
26285 views
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
826