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 *)¶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 */ 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 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 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 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 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 */ 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 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 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 */ 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 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 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 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 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 */ 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 */ 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 829 int main(int argc, char *argv[]) 830 { 831 char root[PATH_MAX]; 832 int i; 833 834 ksft_print_header(); 835 if (cg_find_unified_root(root, sizeof(root), NULL)) 836 ksft_exit_skip("cgroup v2 isn't mounted\n"); 837 838 if (cg_read_strstr(root, "cgroup.subtree_control", "cpu")) 839 if (cg_write(root, "cgroup.subtree_control", "+cpu")) 840 ksft_exit_skip("Failed to set cpu controller\n"); 841 842 ksft_set_plan(ARRAY_SIZE(tests)); 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