162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#define _GNU_SOURCE 462306a36Sopenharmony_ci#include <linux/limits.h> 562306a36Sopenharmony_ci#include <sys/sysinfo.h> 662306a36Sopenharmony_ci#include <sys/wait.h> 762306a36Sopenharmony_ci#include <errno.h> 862306a36Sopenharmony_ci#include <pthread.h> 962306a36Sopenharmony_ci#include <stdio.h> 1062306a36Sopenharmony_ci#include <time.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "../kselftest.h" 1362306a36Sopenharmony_ci#include "cgroup_util.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cienum hog_clock_type { 1662306a36Sopenharmony_ci // Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock. 1762306a36Sopenharmony_ci CPU_HOG_CLOCK_PROCESS, 1862306a36Sopenharmony_ci // Count elapsed time using system wallclock time. 1962306a36Sopenharmony_ci CPU_HOG_CLOCK_WALL, 2062306a36Sopenharmony_ci}; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct cpu_hogger { 2362306a36Sopenharmony_ci char *cgroup; 2462306a36Sopenharmony_ci pid_t pid; 2562306a36Sopenharmony_ci long usage; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct cpu_hog_func_param { 2962306a36Sopenharmony_ci int nprocs; 3062306a36Sopenharmony_ci struct timespec ts; 3162306a36Sopenharmony_ci enum hog_clock_type clock_type; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* 3562306a36Sopenharmony_ci * This test creates two nested cgroups with and without enabling 3662306a36Sopenharmony_ci * the cpu controller. 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_cistatic int test_cpucg_subtree_control(const char *root) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci char *parent = NULL, *child = NULL, *parent2 = NULL, *child2 = NULL; 4162306a36Sopenharmony_ci int ret = KSFT_FAIL; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci // Create two nested cgroups with the cpu controller enabled. 4462306a36Sopenharmony_ci parent = cg_name(root, "cpucg_test_0"); 4562306a36Sopenharmony_ci if (!parent) 4662306a36Sopenharmony_ci goto cleanup; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (cg_create(parent)) 4962306a36Sopenharmony_ci goto cleanup; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (cg_write(parent, "cgroup.subtree_control", "+cpu")) 5262306a36Sopenharmony_ci goto cleanup; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci child = cg_name(parent, "cpucg_test_child"); 5562306a36Sopenharmony_ci if (!child) 5662306a36Sopenharmony_ci goto cleanup; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci if (cg_create(child)) 5962306a36Sopenharmony_ci goto cleanup; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (cg_read_strstr(child, "cgroup.controllers", "cpu")) 6262306a36Sopenharmony_ci goto cleanup; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci // Create two nested cgroups without enabling the cpu controller. 6562306a36Sopenharmony_ci parent2 = cg_name(root, "cpucg_test_1"); 6662306a36Sopenharmony_ci if (!parent2) 6762306a36Sopenharmony_ci goto cleanup; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (cg_create(parent2)) 7062306a36Sopenharmony_ci goto cleanup; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci child2 = cg_name(parent2, "cpucg_test_child"); 7362306a36Sopenharmony_ci if (!child2) 7462306a36Sopenharmony_ci goto cleanup; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (cg_create(child2)) 7762306a36Sopenharmony_ci goto cleanup; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (!cg_read_strstr(child2, "cgroup.controllers", "cpu")) 8062306a36Sopenharmony_ci goto cleanup; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci ret = KSFT_PASS; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cicleanup: 8562306a36Sopenharmony_ci cg_destroy(child); 8662306a36Sopenharmony_ci free(child); 8762306a36Sopenharmony_ci cg_destroy(child2); 8862306a36Sopenharmony_ci free(child2); 8962306a36Sopenharmony_ci cg_destroy(parent); 9062306a36Sopenharmony_ci free(parent); 9162306a36Sopenharmony_ci cg_destroy(parent2); 9262306a36Sopenharmony_ci free(parent2); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return ret; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void *hog_cpu_thread_func(void *arg) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci while (1) 10062306a36Sopenharmony_ci ; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci return NULL; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic struct timespec 10662306a36Sopenharmony_citimespec_sub(const struct timespec *lhs, const struct timespec *rhs) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct timespec zero = { 10962306a36Sopenharmony_ci .tv_sec = 0, 11062306a36Sopenharmony_ci .tv_nsec = 0, 11162306a36Sopenharmony_ci }; 11262306a36Sopenharmony_ci struct timespec ret; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (lhs->tv_sec < rhs->tv_sec) 11562306a36Sopenharmony_ci return zero; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci ret.tv_sec = lhs->tv_sec - rhs->tv_sec; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (lhs->tv_nsec < rhs->tv_nsec) { 12062306a36Sopenharmony_ci if (ret.tv_sec == 0) 12162306a36Sopenharmony_ci return zero; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci ret.tv_sec--; 12462306a36Sopenharmony_ci ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec; 12562306a36Sopenharmony_ci } else 12662306a36Sopenharmony_ci ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return ret; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic int hog_cpus_timed(const char *cgroup, void *arg) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci const struct cpu_hog_func_param *param = 13462306a36Sopenharmony_ci (struct cpu_hog_func_param *)arg; 13562306a36Sopenharmony_ci struct timespec ts_run = param->ts; 13662306a36Sopenharmony_ci struct timespec ts_remaining = ts_run; 13762306a36Sopenharmony_ci struct timespec ts_start; 13862306a36Sopenharmony_ci int i, ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci ret = clock_gettime(CLOCK_MONOTONIC, &ts_start); 14162306a36Sopenharmony_ci if (ret != 0) 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci for (i = 0; i < param->nprocs; i++) { 14562306a36Sopenharmony_ci pthread_t tid; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL); 14862306a36Sopenharmony_ci if (ret != 0) 14962306a36Sopenharmony_ci return ret; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) { 15362306a36Sopenharmony_ci struct timespec ts_total; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci ret = nanosleep(&ts_remaining, NULL); 15662306a36Sopenharmony_ci if (ret && errno != EINTR) 15762306a36Sopenharmony_ci return ret; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (param->clock_type == CPU_HOG_CLOCK_PROCESS) { 16062306a36Sopenharmony_ci ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total); 16162306a36Sopenharmony_ci if (ret != 0) 16262306a36Sopenharmony_ci return ret; 16362306a36Sopenharmony_ci } else { 16462306a36Sopenharmony_ci struct timespec ts_current; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ret = clock_gettime(CLOCK_MONOTONIC, &ts_current); 16762306a36Sopenharmony_ci if (ret != 0) 16862306a36Sopenharmony_ci return ret; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci ts_total = timespec_sub(&ts_current, &ts_start); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci ts_remaining = timespec_sub(&ts_run, &ts_total); 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci/* 18062306a36Sopenharmony_ci * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that 18162306a36Sopenharmony_ci * cpu.stat shows the expected output. 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_cistatic int test_cpucg_stats(const char *root) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci int ret = KSFT_FAIL; 18662306a36Sopenharmony_ci long usage_usec, user_usec, system_usec; 18762306a36Sopenharmony_ci long usage_seconds = 2; 18862306a36Sopenharmony_ci long expected_usage_usec = usage_seconds * USEC_PER_SEC; 18962306a36Sopenharmony_ci char *cpucg; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci cpucg = cg_name(root, "cpucg_test"); 19262306a36Sopenharmony_ci if (!cpucg) 19362306a36Sopenharmony_ci goto cleanup; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (cg_create(cpucg)) 19662306a36Sopenharmony_ci goto cleanup; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec"); 19962306a36Sopenharmony_ci user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec"); 20062306a36Sopenharmony_ci system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec"); 20162306a36Sopenharmony_ci if (usage_usec != 0 || user_usec != 0 || system_usec != 0) 20262306a36Sopenharmony_ci goto cleanup; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci struct cpu_hog_func_param param = { 20562306a36Sopenharmony_ci .nprocs = 1, 20662306a36Sopenharmony_ci .ts = { 20762306a36Sopenharmony_ci .tv_sec = usage_seconds, 20862306a36Sopenharmony_ci .tv_nsec = 0, 20962306a36Sopenharmony_ci }, 21062306a36Sopenharmony_ci .clock_type = CPU_HOG_CLOCK_PROCESS, 21162306a36Sopenharmony_ci }; 21262306a36Sopenharmony_ci if (cg_run(cpucg, hog_cpus_timed, (void *)¶m)) 21362306a36Sopenharmony_ci goto cleanup; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec"); 21662306a36Sopenharmony_ci user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec"); 21762306a36Sopenharmony_ci if (user_usec <= 0) 21862306a36Sopenharmony_ci goto cleanup; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci if (!values_close(usage_usec, expected_usage_usec, 1)) 22162306a36Sopenharmony_ci goto cleanup; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci ret = KSFT_PASS; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cicleanup: 22662306a36Sopenharmony_ci cg_destroy(cpucg); 22762306a36Sopenharmony_ci free(cpucg); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return ret; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int 23362306a36Sopenharmony_cirun_cpucg_weight_test( 23462306a36Sopenharmony_ci const char *root, 23562306a36Sopenharmony_ci pid_t (*spawn_child)(const struct cpu_hogger *child), 23662306a36Sopenharmony_ci int (*validate)(const struct cpu_hogger *children, int num_children)) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci int ret = KSFT_FAIL, i; 23962306a36Sopenharmony_ci char *parent = NULL; 24062306a36Sopenharmony_ci struct cpu_hogger children[3] = {NULL}; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci parent = cg_name(root, "cpucg_test_0"); 24362306a36Sopenharmony_ci if (!parent) 24462306a36Sopenharmony_ci goto cleanup; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci if (cg_create(parent)) 24762306a36Sopenharmony_ci goto cleanup; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (cg_write(parent, "cgroup.subtree_control", "+cpu")) 25062306a36Sopenharmony_ci goto cleanup; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(children); i++) { 25362306a36Sopenharmony_ci children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i); 25462306a36Sopenharmony_ci if (!children[i].cgroup) 25562306a36Sopenharmony_ci goto cleanup; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (cg_create(children[i].cgroup)) 25862306a36Sopenharmony_ci goto cleanup; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci if (cg_write_numeric(children[i].cgroup, "cpu.weight", 26162306a36Sopenharmony_ci 50 * (i + 1))) 26262306a36Sopenharmony_ci goto cleanup; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(children); i++) { 26662306a36Sopenharmony_ci pid_t pid = spawn_child(&children[i]); 26762306a36Sopenharmony_ci if (pid <= 0) 26862306a36Sopenharmony_ci goto cleanup; 26962306a36Sopenharmony_ci children[i].pid = pid; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(children); i++) { 27362306a36Sopenharmony_ci int retcode; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci waitpid(children[i].pid, &retcode, 0); 27662306a36Sopenharmony_ci if (!WIFEXITED(retcode)) 27762306a36Sopenharmony_ci goto cleanup; 27862306a36Sopenharmony_ci if (WEXITSTATUS(retcode)) 27962306a36Sopenharmony_ci goto cleanup; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(children); i++) 28362306a36Sopenharmony_ci children[i].usage = cg_read_key_long(children[i].cgroup, 28462306a36Sopenharmony_ci "cpu.stat", "usage_usec"); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (validate(children, ARRAY_SIZE(children))) 28762306a36Sopenharmony_ci goto cleanup; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci ret = KSFT_PASS; 29062306a36Sopenharmony_cicleanup: 29162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(children); i++) { 29262306a36Sopenharmony_ci cg_destroy(children[i].cgroup); 29362306a36Sopenharmony_ci free(children[i].cgroup); 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci cg_destroy(parent); 29662306a36Sopenharmony_ci free(parent); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci return ret; 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci long usage_seconds = 10; 30462306a36Sopenharmony_ci struct cpu_hog_func_param param = { 30562306a36Sopenharmony_ci .nprocs = ncpus, 30662306a36Sopenharmony_ci .ts = { 30762306a36Sopenharmony_ci .tv_sec = usage_seconds, 30862306a36Sopenharmony_ci .tv_nsec = 0, 30962306a36Sopenharmony_ci }, 31062306a36Sopenharmony_ci .clock_type = CPU_HOG_CLOCK_WALL, 31162306a36Sopenharmony_ci }; 31262306a36Sopenharmony_ci return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)¶m); 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic pid_t weight_hog_all_cpus(const struct cpu_hogger *child) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci return weight_hog_ncpus(child, get_nprocs()); 31862306a36Sopenharmony_ci} 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cistatic int 32162306a36Sopenharmony_cioverprovision_validate(const struct cpu_hogger *children, int num_children) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci int ret = KSFT_FAIL, i; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci for (i = 0; i < num_children - 1; i++) { 32662306a36Sopenharmony_ci long delta; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (children[i + 1].usage <= children[i].usage) 32962306a36Sopenharmony_ci goto cleanup; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci delta = children[i + 1].usage - children[i].usage; 33262306a36Sopenharmony_ci if (!values_close(delta, children[0].usage, 35)) 33362306a36Sopenharmony_ci goto cleanup; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci ret = KSFT_PASS; 33762306a36Sopenharmony_cicleanup: 33862306a36Sopenharmony_ci return ret; 33962306a36Sopenharmony_ci} 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci/* 34262306a36Sopenharmony_ci * First, this test creates the following hierarchy: 34362306a36Sopenharmony_ci * A 34462306a36Sopenharmony_ci * A/B cpu.weight = 50 34562306a36Sopenharmony_ci * A/C cpu.weight = 100 34662306a36Sopenharmony_ci * A/D cpu.weight = 150 34762306a36Sopenharmony_ci * 34862306a36Sopenharmony_ci * A separate process is then created for each child cgroup which spawns as 34962306a36Sopenharmony_ci * many threads as there are cores, and hogs each CPU as much as possible 35062306a36Sopenharmony_ci * for some time interval. 35162306a36Sopenharmony_ci * 35262306a36Sopenharmony_ci * Once all of the children have exited, we verify that each child cgroup 35362306a36Sopenharmony_ci * was given proportional runtime as informed by their cpu.weight. 35462306a36Sopenharmony_ci */ 35562306a36Sopenharmony_cistatic int test_cpucg_weight_overprovisioned(const char *root) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci return run_cpucg_weight_test(root, weight_hog_all_cpus, 35862306a36Sopenharmony_ci overprovision_validate); 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_cistatic pid_t weight_hog_one_cpu(const struct cpu_hogger *child) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci return weight_hog_ncpus(child, 1); 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_cistatic int 36762306a36Sopenharmony_ciunderprovision_validate(const struct cpu_hogger *children, int num_children) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci int ret = KSFT_FAIL, i; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci for (i = 0; i < num_children - 1; i++) { 37262306a36Sopenharmony_ci if (!values_close(children[i + 1].usage, children[0].usage, 15)) 37362306a36Sopenharmony_ci goto cleanup; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci ret = KSFT_PASS; 37762306a36Sopenharmony_cicleanup: 37862306a36Sopenharmony_ci return ret; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci/* 38262306a36Sopenharmony_ci * First, this test creates the following hierarchy: 38362306a36Sopenharmony_ci * A 38462306a36Sopenharmony_ci * A/B cpu.weight = 50 38562306a36Sopenharmony_ci * A/C cpu.weight = 100 38662306a36Sopenharmony_ci * A/D cpu.weight = 150 38762306a36Sopenharmony_ci * 38862306a36Sopenharmony_ci * A separate process is then created for each child cgroup which spawns a 38962306a36Sopenharmony_ci * single thread that hogs a CPU. The testcase is only run on systems that 39062306a36Sopenharmony_ci * have at least one core per-thread in the child processes. 39162306a36Sopenharmony_ci * 39262306a36Sopenharmony_ci * Once all of the children have exited, we verify that each child cgroup 39362306a36Sopenharmony_ci * had roughly the same runtime despite having different cpu.weight. 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_cistatic int test_cpucg_weight_underprovisioned(const char *root) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci // Only run the test if there are enough cores to avoid overprovisioning 39862306a36Sopenharmony_ci // the system. 39962306a36Sopenharmony_ci if (get_nprocs() < 4) 40062306a36Sopenharmony_ci return KSFT_SKIP; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci return run_cpucg_weight_test(root, weight_hog_one_cpu, 40362306a36Sopenharmony_ci underprovision_validate); 40462306a36Sopenharmony_ci} 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic int 40762306a36Sopenharmony_cirun_cpucg_nested_weight_test(const char *root, bool overprovisioned) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci int ret = KSFT_FAIL, i; 41062306a36Sopenharmony_ci char *parent = NULL, *child = NULL; 41162306a36Sopenharmony_ci struct cpu_hogger leaf[3] = {NULL}; 41262306a36Sopenharmony_ci long nested_leaf_usage, child_usage; 41362306a36Sopenharmony_ci int nprocs = get_nprocs(); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (!overprovisioned) { 41662306a36Sopenharmony_ci if (nprocs < 4) 41762306a36Sopenharmony_ci /* 41862306a36Sopenharmony_ci * Only run the test if there are enough cores to avoid overprovisioning 41962306a36Sopenharmony_ci * the system. 42062306a36Sopenharmony_ci */ 42162306a36Sopenharmony_ci return KSFT_SKIP; 42262306a36Sopenharmony_ci nprocs /= 4; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci parent = cg_name(root, "cpucg_test"); 42662306a36Sopenharmony_ci child = cg_name(parent, "cpucg_child"); 42762306a36Sopenharmony_ci if (!parent || !child) 42862306a36Sopenharmony_ci goto cleanup; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci if (cg_create(parent)) 43162306a36Sopenharmony_ci goto cleanup; 43262306a36Sopenharmony_ci if (cg_write(parent, "cgroup.subtree_control", "+cpu")) 43362306a36Sopenharmony_ci goto cleanup; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (cg_create(child)) 43662306a36Sopenharmony_ci goto cleanup; 43762306a36Sopenharmony_ci if (cg_write(child, "cgroup.subtree_control", "+cpu")) 43862306a36Sopenharmony_ci goto cleanup; 43962306a36Sopenharmony_ci if (cg_write(child, "cpu.weight", "1000")) 44062306a36Sopenharmony_ci goto cleanup; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(leaf); i++) { 44362306a36Sopenharmony_ci const char *ancestor; 44462306a36Sopenharmony_ci long weight; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci if (i == 0) { 44762306a36Sopenharmony_ci ancestor = parent; 44862306a36Sopenharmony_ci weight = 1000; 44962306a36Sopenharmony_ci } else { 45062306a36Sopenharmony_ci ancestor = child; 45162306a36Sopenharmony_ci weight = 5000; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i); 45462306a36Sopenharmony_ci if (!leaf[i].cgroup) 45562306a36Sopenharmony_ci goto cleanup; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci if (cg_create(leaf[i].cgroup)) 45862306a36Sopenharmony_ci goto cleanup; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight)) 46162306a36Sopenharmony_ci goto cleanup; 46262306a36Sopenharmony_ci } 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(leaf); i++) { 46562306a36Sopenharmony_ci pid_t pid; 46662306a36Sopenharmony_ci struct cpu_hog_func_param param = { 46762306a36Sopenharmony_ci .nprocs = nprocs, 46862306a36Sopenharmony_ci .ts = { 46962306a36Sopenharmony_ci .tv_sec = 10, 47062306a36Sopenharmony_ci .tv_nsec = 0, 47162306a36Sopenharmony_ci }, 47262306a36Sopenharmony_ci .clock_type = CPU_HOG_CLOCK_WALL, 47362306a36Sopenharmony_ci }; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed, 47662306a36Sopenharmony_ci (void *)¶m); 47762306a36Sopenharmony_ci if (pid <= 0) 47862306a36Sopenharmony_ci goto cleanup; 47962306a36Sopenharmony_ci leaf[i].pid = pid; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(leaf); i++) { 48362306a36Sopenharmony_ci int retcode; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci waitpid(leaf[i].pid, &retcode, 0); 48662306a36Sopenharmony_ci if (!WIFEXITED(retcode)) 48762306a36Sopenharmony_ci goto cleanup; 48862306a36Sopenharmony_ci if (WEXITSTATUS(retcode)) 48962306a36Sopenharmony_ci goto cleanup; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(leaf); i++) { 49362306a36Sopenharmony_ci leaf[i].usage = cg_read_key_long(leaf[i].cgroup, 49462306a36Sopenharmony_ci "cpu.stat", "usage_usec"); 49562306a36Sopenharmony_ci if (leaf[i].usage <= 0) 49662306a36Sopenharmony_ci goto cleanup; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci nested_leaf_usage = leaf[1].usage + leaf[2].usage; 50062306a36Sopenharmony_ci if (overprovisioned) { 50162306a36Sopenharmony_ci if (!values_close(leaf[0].usage, nested_leaf_usage, 15)) 50262306a36Sopenharmony_ci goto cleanup; 50362306a36Sopenharmony_ci } else if (!values_close(leaf[0].usage * 2, nested_leaf_usage, 15)) 50462306a36Sopenharmony_ci goto cleanup; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec"); 50862306a36Sopenharmony_ci if (child_usage <= 0) 50962306a36Sopenharmony_ci goto cleanup; 51062306a36Sopenharmony_ci if (!values_close(child_usage, nested_leaf_usage, 1)) 51162306a36Sopenharmony_ci goto cleanup; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci ret = KSFT_PASS; 51462306a36Sopenharmony_cicleanup: 51562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(leaf); i++) { 51662306a36Sopenharmony_ci cg_destroy(leaf[i].cgroup); 51762306a36Sopenharmony_ci free(leaf[i].cgroup); 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci cg_destroy(child); 52062306a36Sopenharmony_ci free(child); 52162306a36Sopenharmony_ci cg_destroy(parent); 52262306a36Sopenharmony_ci free(parent); 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci return ret; 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci/* 52862306a36Sopenharmony_ci * First, this test creates the following hierarchy: 52962306a36Sopenharmony_ci * A 53062306a36Sopenharmony_ci * A/B cpu.weight = 1000 53162306a36Sopenharmony_ci * A/C cpu.weight = 1000 53262306a36Sopenharmony_ci * A/C/D cpu.weight = 5000 53362306a36Sopenharmony_ci * A/C/E cpu.weight = 5000 53462306a36Sopenharmony_ci * 53562306a36Sopenharmony_ci * A separate process is then created for each leaf, which spawn nproc threads 53662306a36Sopenharmony_ci * that burn a CPU for a few seconds. 53762306a36Sopenharmony_ci * 53862306a36Sopenharmony_ci * Once all of those processes have exited, we verify that each of the leaf 53962306a36Sopenharmony_ci * cgroups have roughly the same usage from cpu.stat. 54062306a36Sopenharmony_ci */ 54162306a36Sopenharmony_cistatic int 54262306a36Sopenharmony_citest_cpucg_nested_weight_overprovisioned(const char *root) 54362306a36Sopenharmony_ci{ 54462306a36Sopenharmony_ci return run_cpucg_nested_weight_test(root, true); 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci/* 54862306a36Sopenharmony_ci * First, this test creates the following hierarchy: 54962306a36Sopenharmony_ci * A 55062306a36Sopenharmony_ci * A/B cpu.weight = 1000 55162306a36Sopenharmony_ci * A/C cpu.weight = 1000 55262306a36Sopenharmony_ci * A/C/D cpu.weight = 5000 55362306a36Sopenharmony_ci * A/C/E cpu.weight = 5000 55462306a36Sopenharmony_ci * 55562306a36Sopenharmony_ci * A separate process is then created for each leaf, which nproc / 4 threads 55662306a36Sopenharmony_ci * that burns a CPU for a few seconds. 55762306a36Sopenharmony_ci * 55862306a36Sopenharmony_ci * Once all of those processes have exited, we verify that each of the leaf 55962306a36Sopenharmony_ci * cgroups have roughly the same usage from cpu.stat. 56062306a36Sopenharmony_ci */ 56162306a36Sopenharmony_cistatic int 56262306a36Sopenharmony_citest_cpucg_nested_weight_underprovisioned(const char *root) 56362306a36Sopenharmony_ci{ 56462306a36Sopenharmony_ci return run_cpucg_nested_weight_test(root, false); 56562306a36Sopenharmony_ci} 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci/* 56862306a36Sopenharmony_ci * This test creates a cgroup with some maximum value within a period, and 56962306a36Sopenharmony_ci * verifies that a process in the cgroup is not overscheduled. 57062306a36Sopenharmony_ci */ 57162306a36Sopenharmony_cistatic int test_cpucg_max(const char *root) 57262306a36Sopenharmony_ci{ 57362306a36Sopenharmony_ci int ret = KSFT_FAIL; 57462306a36Sopenharmony_ci long usage_usec, user_usec; 57562306a36Sopenharmony_ci long usage_seconds = 1; 57662306a36Sopenharmony_ci long expected_usage_usec = usage_seconds * USEC_PER_SEC; 57762306a36Sopenharmony_ci char *cpucg; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci cpucg = cg_name(root, "cpucg_test"); 58062306a36Sopenharmony_ci if (!cpucg) 58162306a36Sopenharmony_ci goto cleanup; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (cg_create(cpucg)) 58462306a36Sopenharmony_ci goto cleanup; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci if (cg_write(cpucg, "cpu.max", "1000")) 58762306a36Sopenharmony_ci goto cleanup; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci struct cpu_hog_func_param param = { 59062306a36Sopenharmony_ci .nprocs = 1, 59162306a36Sopenharmony_ci .ts = { 59262306a36Sopenharmony_ci .tv_sec = usage_seconds, 59362306a36Sopenharmony_ci .tv_nsec = 0, 59462306a36Sopenharmony_ci }, 59562306a36Sopenharmony_ci .clock_type = CPU_HOG_CLOCK_WALL, 59662306a36Sopenharmony_ci }; 59762306a36Sopenharmony_ci if (cg_run(cpucg, hog_cpus_timed, (void *)¶m)) 59862306a36Sopenharmony_ci goto cleanup; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec"); 60162306a36Sopenharmony_ci user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec"); 60262306a36Sopenharmony_ci if (user_usec <= 0) 60362306a36Sopenharmony_ci goto cleanup; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci if (user_usec >= expected_usage_usec) 60662306a36Sopenharmony_ci goto cleanup; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci if (values_close(usage_usec, expected_usage_usec, 95)) 60962306a36Sopenharmony_ci goto cleanup; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci ret = KSFT_PASS; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_cicleanup: 61462306a36Sopenharmony_ci cg_destroy(cpucg); 61562306a36Sopenharmony_ci free(cpucg); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci return ret; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci/* 62162306a36Sopenharmony_ci * This test verifies that a process inside of a nested cgroup whose parent 62262306a36Sopenharmony_ci * group has a cpu.max value set, is properly throttled. 62362306a36Sopenharmony_ci */ 62462306a36Sopenharmony_cistatic int test_cpucg_max_nested(const char *root) 62562306a36Sopenharmony_ci{ 62662306a36Sopenharmony_ci int ret = KSFT_FAIL; 62762306a36Sopenharmony_ci long usage_usec, user_usec; 62862306a36Sopenharmony_ci long usage_seconds = 1; 62962306a36Sopenharmony_ci long expected_usage_usec = usage_seconds * USEC_PER_SEC; 63062306a36Sopenharmony_ci char *parent, *child; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci parent = cg_name(root, "cpucg_parent"); 63362306a36Sopenharmony_ci child = cg_name(parent, "cpucg_child"); 63462306a36Sopenharmony_ci if (!parent || !child) 63562306a36Sopenharmony_ci goto cleanup; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci if (cg_create(parent)) 63862306a36Sopenharmony_ci goto cleanup; 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci if (cg_write(parent, "cgroup.subtree_control", "+cpu")) 64162306a36Sopenharmony_ci goto cleanup; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci if (cg_create(child)) 64462306a36Sopenharmony_ci goto cleanup; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci if (cg_write(parent, "cpu.max", "1000")) 64762306a36Sopenharmony_ci goto cleanup; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci struct cpu_hog_func_param param = { 65062306a36Sopenharmony_ci .nprocs = 1, 65162306a36Sopenharmony_ci .ts = { 65262306a36Sopenharmony_ci .tv_sec = usage_seconds, 65362306a36Sopenharmony_ci .tv_nsec = 0, 65462306a36Sopenharmony_ci }, 65562306a36Sopenharmony_ci .clock_type = CPU_HOG_CLOCK_WALL, 65662306a36Sopenharmony_ci }; 65762306a36Sopenharmony_ci if (cg_run(child, hog_cpus_timed, (void *)¶m)) 65862306a36Sopenharmony_ci goto cleanup; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec"); 66162306a36Sopenharmony_ci user_usec = cg_read_key_long(child, "cpu.stat", "user_usec"); 66262306a36Sopenharmony_ci if (user_usec <= 0) 66362306a36Sopenharmony_ci goto cleanup; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci if (user_usec >= expected_usage_usec) 66662306a36Sopenharmony_ci goto cleanup; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (values_close(usage_usec, expected_usage_usec, 95)) 66962306a36Sopenharmony_ci goto cleanup; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci ret = KSFT_PASS; 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_cicleanup: 67462306a36Sopenharmony_ci cg_destroy(child); 67562306a36Sopenharmony_ci free(child); 67662306a36Sopenharmony_ci cg_destroy(parent); 67762306a36Sopenharmony_ci free(parent); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci return ret; 68062306a36Sopenharmony_ci} 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci#define T(x) { x, #x } 68362306a36Sopenharmony_cistruct cpucg_test { 68462306a36Sopenharmony_ci int (*fn)(const char *root); 68562306a36Sopenharmony_ci const char *name; 68662306a36Sopenharmony_ci} tests[] = { 68762306a36Sopenharmony_ci T(test_cpucg_subtree_control), 68862306a36Sopenharmony_ci T(test_cpucg_stats), 68962306a36Sopenharmony_ci T(test_cpucg_weight_overprovisioned), 69062306a36Sopenharmony_ci T(test_cpucg_weight_underprovisioned), 69162306a36Sopenharmony_ci T(test_cpucg_nested_weight_overprovisioned), 69262306a36Sopenharmony_ci T(test_cpucg_nested_weight_underprovisioned), 69362306a36Sopenharmony_ci T(test_cpucg_max), 69462306a36Sopenharmony_ci T(test_cpucg_max_nested), 69562306a36Sopenharmony_ci}; 69662306a36Sopenharmony_ci#undef T 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ciint main(int argc, char *argv[]) 69962306a36Sopenharmony_ci{ 70062306a36Sopenharmony_ci char root[PATH_MAX]; 70162306a36Sopenharmony_ci int i, ret = EXIT_SUCCESS; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci if (cg_find_unified_root(root, sizeof(root))) 70462306a36Sopenharmony_ci ksft_exit_skip("cgroup v2 isn't mounted\n"); 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci if (cg_read_strstr(root, "cgroup.subtree_control", "cpu")) 70762306a36Sopenharmony_ci if (cg_write(root, "cgroup.subtree_control", "+cpu")) 70862306a36Sopenharmony_ci ksft_exit_skip("Failed to set cpu controller\n"); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(tests); i++) { 71162306a36Sopenharmony_ci switch (tests[i].fn(root)) { 71262306a36Sopenharmony_ci case KSFT_PASS: 71362306a36Sopenharmony_ci ksft_test_result_pass("%s\n", tests[i].name); 71462306a36Sopenharmony_ci break; 71562306a36Sopenharmony_ci case KSFT_SKIP: 71662306a36Sopenharmony_ci ksft_test_result_skip("%s\n", tests[i].name); 71762306a36Sopenharmony_ci break; 71862306a36Sopenharmony_ci default: 71962306a36Sopenharmony_ci ret = EXIT_FAILURE; 72062306a36Sopenharmony_ci ksft_test_result_fail("%s\n", tests[i].name); 72162306a36Sopenharmony_ci break; 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci } 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci return ret; 72662306a36Sopenharmony_ci} 727