Path: blob/master/tools/testing/selftests/cgroup/test_cpu.c
26285 views
// SPDX-License-Identifier: GPL-2.012#define _GNU_SOURCE3#include <linux/limits.h>4#include <sys/param.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>1213#include "../kselftest.h"14#include "cgroup_util.h"1516enum hog_clock_type {17// Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock.18CPU_HOG_CLOCK_PROCESS,19// Count elapsed time using system wallclock time.20CPU_HOG_CLOCK_WALL,21};2223struct cpu_hogger {24char *cgroup;25pid_t pid;26long usage;27};2829struct cpu_hog_func_param {30int nprocs;31struct timespec ts;32enum hog_clock_type clock_type;33};3435/*36* This test creates two nested cgroups with and without enabling37* the cpu controller.38*/39static int test_cpucg_subtree_control(const char *root)40{41char *parent = NULL, *child = NULL, *parent2 = NULL, *child2 = NULL;42int ret = KSFT_FAIL;4344// Create two nested cgroups with the cpu controller enabled.45parent = cg_name(root, "cpucg_test_0");46if (!parent)47goto cleanup;4849if (cg_create(parent))50goto cleanup;5152if (cg_write(parent, "cgroup.subtree_control", "+cpu"))53goto cleanup;5455child = cg_name(parent, "cpucg_test_child");56if (!child)57goto cleanup;5859if (cg_create(child))60goto cleanup;6162if (cg_read_strstr(child, "cgroup.controllers", "cpu"))63goto cleanup;6465// Create two nested cgroups without enabling the cpu controller.66parent2 = cg_name(root, "cpucg_test_1");67if (!parent2)68goto cleanup;6970if (cg_create(parent2))71goto cleanup;7273child2 = cg_name(parent2, "cpucg_test_child");74if (!child2)75goto cleanup;7677if (cg_create(child2))78goto cleanup;7980if (!cg_read_strstr(child2, "cgroup.controllers", "cpu"))81goto cleanup;8283ret = KSFT_PASS;8485cleanup:86cg_destroy(child);87free(child);88cg_destroy(child2);89free(child2);90cg_destroy(parent);91free(parent);92cg_destroy(parent2);93free(parent2);9495return ret;96}9798static void *hog_cpu_thread_func(void *arg)99{100while (1)101;102103return NULL;104}105106static struct timespec107timespec_sub(const struct timespec *lhs, const struct timespec *rhs)108{109struct timespec zero = {110.tv_sec = 0,111.tv_nsec = 0,112};113struct timespec ret;114115if (lhs->tv_sec < rhs->tv_sec)116return zero;117118ret.tv_sec = lhs->tv_sec - rhs->tv_sec;119120if (lhs->tv_nsec < rhs->tv_nsec) {121if (ret.tv_sec == 0)122return zero;123124ret.tv_sec--;125ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec;126} else127ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec;128129return ret;130}131132static int hog_cpus_timed(const char *cgroup, void *arg)133{134const struct cpu_hog_func_param *param =135(struct cpu_hog_func_param *)arg;136struct timespec ts_run = param->ts;137struct timespec ts_remaining = ts_run;138struct timespec ts_start;139int i, ret;140141ret = clock_gettime(CLOCK_MONOTONIC, &ts_start);142if (ret != 0)143return ret;144145for (i = 0; i < param->nprocs; i++) {146pthread_t tid;147148ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);149if (ret != 0)150return ret;151}152153while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {154struct timespec ts_total;155156ret = nanosleep(&ts_remaining, NULL);157if (ret && errno != EINTR)158return ret;159160if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {161ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total);162if (ret != 0)163return ret;164} else {165struct timespec ts_current;166167ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);168if (ret != 0)169return ret;170171ts_total = timespec_sub(&ts_current, &ts_start);172}173174ts_remaining = timespec_sub(&ts_run, &ts_total);175}176177return 0;178}179180/*181* Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that182* cpu.stat shows the expected output.183*/184static int test_cpucg_stats(const char *root)185{186int ret = KSFT_FAIL;187long usage_usec, user_usec, system_usec;188long usage_seconds = 2;189long expected_usage_usec = usage_seconds * USEC_PER_SEC;190char *cpucg;191192cpucg = cg_name(root, "cpucg_test");193if (!cpucg)194goto cleanup;195196if (cg_create(cpucg))197goto cleanup;198199usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");200user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");201system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec");202if (usage_usec != 0 || user_usec != 0 || system_usec != 0)203goto cleanup;204205struct 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};213if (cg_run(cpucg, hog_cpus_timed, (void *)¶m))214goto cleanup;215216usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");217user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");218if (user_usec <= 0)219goto cleanup;220221if (!values_close(usage_usec, expected_usage_usec, 1))222goto cleanup;223224ret = KSFT_PASS;225226cleanup:227cg_destroy(cpucg);228free(cpucg);229230return ret;231}232233/*234* Creates a nice process that consumes CPU and checks that the elapsed235* usertime in the cgroup is close to the expected time.236*/237static int test_cpucg_nice(const char *root)238{239int ret = KSFT_FAIL;240int status;241long user_usec, nice_usec;242long usage_seconds = 2;243long expected_nice_usec = usage_seconds * USEC_PER_SEC;244char *cpucg;245pid_t pid;246247cpucg = cg_name(root, "cpucg_test");248if (!cpucg)249goto cleanup;250251if (cg_create(cpucg))252goto cleanup;253254user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");255nice_usec = cg_read_key_long(cpucg, "cpu.stat", "nice_usec");256if (nice_usec == -1)257ret = KSFT_SKIP;258if (user_usec != 0 || nice_usec != 0)259goto cleanup;260261/*262* We fork here to create a new process that can be niced without263* polluting the nice value of other selftests264*/265pid = fork();266if (pid < 0) {267goto cleanup;268} else if (pid == 0) {269struct 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};277char buf[64];278snprintf(buf, sizeof(buf), "%d", getpid());279if (cg_write(cpucg, "cgroup.procs", buf))280goto cleanup;281282/* Try to keep niced CPU usage as constrained to hog_cpu as possible */283nice(1);284hog_cpus_timed(cpucg, ¶m);285exit(0);286} else {287waitpid(pid, &status, 0);288if (!WIFEXITED(status))289goto cleanup;290291user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");292nice_usec = cg_read_key_long(cpucg, "cpu.stat", "nice_usec");293if (!values_close(nice_usec, expected_nice_usec, 1))294goto cleanup;295296ret = KSFT_PASS;297}298299cleanup:300cg_destroy(cpucg);301free(cpucg);302303return ret;304}305306static int307run_cpucg_weight_test(308const char *root,309pid_t (*spawn_child)(const struct cpu_hogger *child),310int (*validate)(const struct cpu_hogger *children, int num_children))311{312int ret = KSFT_FAIL, i;313char *parent = NULL;314struct cpu_hogger children[3] = {};315316parent = cg_name(root, "cpucg_test_0");317if (!parent)318goto cleanup;319320if (cg_create(parent))321goto cleanup;322323if (cg_write(parent, "cgroup.subtree_control", "+cpu"))324goto cleanup;325326for (i = 0; i < ARRAY_SIZE(children); i++) {327children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i);328if (!children[i].cgroup)329goto cleanup;330331if (cg_create(children[i].cgroup))332goto cleanup;333334if (cg_write_numeric(children[i].cgroup, "cpu.weight",33550 * (i + 1)))336goto cleanup;337}338339for (i = 0; i < ARRAY_SIZE(children); i++) {340pid_t pid = spawn_child(&children[i]);341if (pid <= 0)342goto cleanup;343children[i].pid = pid;344}345346for (i = 0; i < ARRAY_SIZE(children); i++) {347int retcode;348349waitpid(children[i].pid, &retcode, 0);350if (!WIFEXITED(retcode))351goto cleanup;352if (WEXITSTATUS(retcode))353goto cleanup;354}355356for (i = 0; i < ARRAY_SIZE(children); i++)357children[i].usage = cg_read_key_long(children[i].cgroup,358"cpu.stat", "usage_usec");359360if (validate(children, ARRAY_SIZE(children)))361goto cleanup;362363ret = KSFT_PASS;364cleanup:365for (i = 0; i < ARRAY_SIZE(children); i++) {366cg_destroy(children[i].cgroup);367free(children[i].cgroup);368}369cg_destroy(parent);370free(parent);371372return ret;373}374375static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)376{377long usage_seconds = 10;378struct 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};386return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)¶m);387}388389static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)390{391return weight_hog_ncpus(child, get_nprocs());392}393394static int395overprovision_validate(const struct cpu_hogger *children, int num_children)396{397int ret = KSFT_FAIL, i;398399for (i = 0; i < num_children - 1; i++) {400long delta;401402if (children[i + 1].usage <= children[i].usage)403goto cleanup;404405delta = children[i + 1].usage - children[i].usage;406if (!values_close(delta, children[0].usage, 35))407goto cleanup;408}409410ret = KSFT_PASS;411cleanup:412return ret;413}414415/*416* First, this test creates the following hierarchy:417* A418* A/B cpu.weight = 50419* A/C cpu.weight = 100420* A/D cpu.weight = 150421*422* A separate process is then created for each child cgroup which spawns as423* many threads as there are cores, and hogs each CPU as much as possible424* for some time interval.425*426* Once all of the children have exited, we verify that each child cgroup427* was given proportional runtime as informed by their cpu.weight.428*/429static int test_cpucg_weight_overprovisioned(const char *root)430{431return run_cpucg_weight_test(root, weight_hog_all_cpus,432overprovision_validate);433}434435static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)436{437return weight_hog_ncpus(child, 1);438}439440static int441underprovision_validate(const struct cpu_hogger *children, int num_children)442{443int ret = KSFT_FAIL, i;444445for (i = 0; i < num_children - 1; i++) {446if (!values_close(children[i + 1].usage, children[0].usage, 15))447goto cleanup;448}449450ret = KSFT_PASS;451cleanup:452return ret;453}454455/*456* First, this test creates the following hierarchy:457* A458* A/B cpu.weight = 50459* A/C cpu.weight = 100460* A/D cpu.weight = 150461*462* A separate process is then created for each child cgroup which spawns a463* single thread that hogs a CPU. The testcase is only run on systems that464* 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 cgroup467* had roughly the same runtime despite having different cpu.weight.468*/469static int test_cpucg_weight_underprovisioned(const char *root)470{471// Only run the test if there are enough cores to avoid overprovisioning472// the system.473if (get_nprocs() < 4)474return KSFT_SKIP;475476return run_cpucg_weight_test(root, weight_hog_one_cpu,477underprovision_validate);478}479480static int481run_cpucg_nested_weight_test(const char *root, bool overprovisioned)482{483int ret = KSFT_FAIL, i;484char *parent = NULL, *child = NULL;485struct cpu_hogger leaf[3] = {};486long nested_leaf_usage, child_usage;487int nprocs = get_nprocs();488489if (!overprovisioned) {490if (nprocs < 4)491/*492* Only run the test if there are enough cores to avoid overprovisioning493* the system.494*/495return KSFT_SKIP;496nprocs /= 4;497}498499parent = cg_name(root, "cpucg_test");500child = cg_name(parent, "cpucg_child");501if (!parent || !child)502goto cleanup;503504if (cg_create(parent))505goto cleanup;506if (cg_write(parent, "cgroup.subtree_control", "+cpu"))507goto cleanup;508509if (cg_create(child))510goto cleanup;511if (cg_write(child, "cgroup.subtree_control", "+cpu"))512goto cleanup;513if (cg_write(child, "cpu.weight", "1000"))514goto cleanup;515516for (i = 0; i < ARRAY_SIZE(leaf); i++) {517const char *ancestor;518long weight;519520if (i == 0) {521ancestor = parent;522weight = 1000;523} else {524ancestor = child;525weight = 5000;526}527leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i);528if (!leaf[i].cgroup)529goto cleanup;530531if (cg_create(leaf[i].cgroup))532goto cleanup;533534if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight))535goto cleanup;536}537538for (i = 0; i < ARRAY_SIZE(leaf); i++) {539pid_t pid;540struct 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};548549pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed,550(void *)¶m);551if (pid <= 0)552goto cleanup;553leaf[i].pid = pid;554}555556for (i = 0; i < ARRAY_SIZE(leaf); i++) {557int retcode;558559waitpid(leaf[i].pid, &retcode, 0);560if (!WIFEXITED(retcode))561goto cleanup;562if (WEXITSTATUS(retcode))563goto cleanup;564}565566for (i = 0; i < ARRAY_SIZE(leaf); i++) {567leaf[i].usage = cg_read_key_long(leaf[i].cgroup,568"cpu.stat", "usage_usec");569if (leaf[i].usage <= 0)570goto cleanup;571}572573nested_leaf_usage = leaf[1].usage + leaf[2].usage;574if (overprovisioned) {575if (!values_close(leaf[0].usage, nested_leaf_usage, 15))576goto cleanup;577} else if (!values_close(leaf[0].usage * 2, nested_leaf_usage, 15))578goto cleanup;579580581child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec");582if (child_usage <= 0)583goto cleanup;584if (!values_close(child_usage, nested_leaf_usage, 1))585goto cleanup;586587ret = KSFT_PASS;588cleanup:589for (i = 0; i < ARRAY_SIZE(leaf); i++) {590cg_destroy(leaf[i].cgroup);591free(leaf[i].cgroup);592}593cg_destroy(child);594free(child);595cg_destroy(parent);596free(parent);597598return ret;599}600601/*602* First, this test creates the following hierarchy:603* A604* A/B cpu.weight = 1000605* A/C cpu.weight = 1000606* A/C/D cpu.weight = 5000607* A/C/E cpu.weight = 5000608*609* A separate process is then created for each leaf, which spawn nproc threads610* that burn a CPU for a few seconds.611*612* Once all of those processes have exited, we verify that each of the leaf613* cgroups have roughly the same usage from cpu.stat.614*/615static int616test_cpucg_nested_weight_overprovisioned(const char *root)617{618return run_cpucg_nested_weight_test(root, true);619}620621/*622* First, this test creates the following hierarchy:623* A624* A/B cpu.weight = 1000625* A/C cpu.weight = 1000626* A/C/D cpu.weight = 5000627* A/C/E cpu.weight = 5000628*629* A separate process is then created for each leaf, which nproc / 4 threads630* that burns a CPU for a few seconds.631*632* Once all of those processes have exited, we verify that each of the leaf633* cgroups have roughly the same usage from cpu.stat.634*/635static int636test_cpucg_nested_weight_underprovisioned(const char *root)637{638return run_cpucg_nested_weight_test(root, false);639}640641/*642* This test creates a cgroup with some maximum value within a period, and643* verifies that a process in the cgroup is not overscheduled.644*/645static int test_cpucg_max(const char *root)646{647int ret = KSFT_FAIL;648long quota_usec = 1000;649long default_period_usec = 100000; /* cpu.max's default period */650long duration_seconds = 1;651652long duration_usec = duration_seconds * USEC_PER_SEC;653long usage_usec, n_periods, remainder_usec, expected_usage_usec;654char *cpucg;655char quota_buf[32];656657snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);658659cpucg = cg_name(root, "cpucg_test");660if (!cpucg)661goto cleanup;662663if (cg_create(cpucg))664goto cleanup;665666if (cg_write(cpucg, "cpu.max", quota_buf))667goto cleanup;668669struct cpu_hog_func_param param = {670.nprocs = 1,671.ts = {672.tv_sec = duration_seconds,673.tv_nsec = 0,674},675.clock_type = CPU_HOG_CLOCK_WALL,676};677if (cg_run(cpucg, hog_cpus_timed, (void *)¶m))678goto cleanup;679680usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");681if (usage_usec <= 0)682goto cleanup;683684/*685* The following calculation applies only since686* the cpu hog is set to run as per wall-clock time687*/688n_periods = duration_usec / default_period_usec;689remainder_usec = duration_usec - n_periods * default_period_usec;690expected_usage_usec691= n_periods * quota_usec + MIN(remainder_usec, quota_usec);692693if (!values_close(usage_usec, expected_usage_usec, 10))694goto cleanup;695696ret = KSFT_PASS;697698cleanup:699cg_destroy(cpucg);700free(cpucg);701702return ret;703}704705/*706* This test verifies that a process inside of a nested cgroup whose parent707* group has a cpu.max value set, is properly throttled.708*/709static int test_cpucg_max_nested(const char *root)710{711int ret = KSFT_FAIL;712long quota_usec = 1000;713long default_period_usec = 100000; /* cpu.max's default period */714long duration_seconds = 1;715716long duration_usec = duration_seconds * USEC_PER_SEC;717long usage_usec, n_periods, remainder_usec, expected_usage_usec;718char *parent, *child;719char quota_buf[32];720721snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);722723parent = cg_name(root, "cpucg_parent");724child = cg_name(parent, "cpucg_child");725if (!parent || !child)726goto cleanup;727728if (cg_create(parent))729goto cleanup;730731if (cg_write(parent, "cgroup.subtree_control", "+cpu"))732goto cleanup;733734if (cg_create(child))735goto cleanup;736737if (cg_write(parent, "cpu.max", quota_buf))738goto cleanup;739740struct cpu_hog_func_param param = {741.nprocs = 1,742.ts = {743.tv_sec = duration_seconds,744.tv_nsec = 0,745},746.clock_type = CPU_HOG_CLOCK_WALL,747};748if (cg_run(child, hog_cpus_timed, (void *)¶m))749goto cleanup;750751usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec");752if (usage_usec <= 0)753goto cleanup;754755/*756* The following calculation applies only since757* the cpu hog is set to run as per wall-clock time758*/759n_periods = duration_usec / default_period_usec;760remainder_usec = duration_usec - n_periods * default_period_usec;761expected_usage_usec762= n_periods * quota_usec + MIN(remainder_usec, quota_usec);763764if (!values_close(usage_usec, expected_usage_usec, 10))765goto cleanup;766767ret = KSFT_PASS;768769cleanup:770cg_destroy(child);771free(child);772cg_destroy(parent);773free(parent);774775return ret;776}777778#define T(x) { x, #x }779struct cpucg_test {780int (*fn)(const char *root);781const char *name;782} tests[] = {783T(test_cpucg_subtree_control),784T(test_cpucg_stats),785T(test_cpucg_nice),786T(test_cpucg_weight_overprovisioned),787T(test_cpucg_weight_underprovisioned),788T(test_cpucg_nested_weight_overprovisioned),789T(test_cpucg_nested_weight_underprovisioned),790T(test_cpucg_max),791T(test_cpucg_max_nested),792};793#undef T794795int main(int argc, char *argv[])796{797char root[PATH_MAX];798int i, ret = EXIT_SUCCESS;799800if (cg_find_unified_root(root, sizeof(root), NULL))801ksft_exit_skip("cgroup v2 isn't mounted\n");802803if (cg_read_strstr(root, "cgroup.subtree_control", "cpu"))804if (cg_write(root, "cgroup.subtree_control", "+cpu"))805ksft_exit_skip("Failed to set cpu controller\n");806807for (i = 0; i < ARRAY_SIZE(tests); i++) {808switch (tests[i].fn(root)) {809case KSFT_PASS:810ksft_test_result_pass("%s\n", tests[i].name);811break;812case KSFT_SKIP:813ksft_test_result_skip("%s\n", tests[i].name);814break;815default:816ret = EXIT_FAILURE;817ksft_test_result_fail("%s\n", tests[i].name);818break;819}820}821822return ret;823}824825826