diff --git a/tools/testing/selftests/cgroup/cgroup_util.h b/tools/testing/selftests/cgroup/cgroup_util.h index 628738532ac9..973774fa7bee 100644 --- a/tools/testing/selftests/cgroup/cgroup_util.h +++ b/tools/testing/selftests/cgroup/cgroup_util.h @@ -8,6 +8,9 @@ #define MB(x) (x << 20) +#define USEC_PER_SEC 1000000L +#define NSEC_PER_SEC 1000000000L + /* * Checks if two given values differ by less than err% of their sum. */ diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c index a724bff00d07..3bd61964a262 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -2,11 +2,19 @@ #define _GNU_SOURCE #include +#include +#include #include +#include #include "../kselftest.h" #include "cgroup_util.h" +struct cpu_hog_func_param { + int nprocs; + struct timespec ts; +}; + /* * This test creates two nested cgroups with and without enabling * the cpu controller. @@ -70,12 +78,132 @@ cleanup: return ret; } +static void *hog_cpu_thread_func(void *arg) +{ + while (1) + ; + + return NULL; +} + +static struct timespec +timespec_sub(const struct timespec *lhs, const struct timespec *rhs) +{ + struct timespec zero = { + .tv_sec = 0, + .tv_nsec = 0, + }; + struct timespec ret; + + if (lhs->tv_sec < rhs->tv_sec) + return zero; + + ret.tv_sec = lhs->tv_sec - rhs->tv_sec; + + if (lhs->tv_nsec < rhs->tv_nsec) { + if (ret.tv_sec == 0) + return zero; + + ret.tv_sec--; + ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec; + } else + ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec; + + return ret; +} + +static int hog_cpus_timed(const char *cgroup, void *arg) +{ + const struct cpu_hog_func_param *param = + (struct cpu_hog_func_param *)arg; + struct timespec ts_run = param->ts; + struct timespec ts_remaining = ts_run; + int i, ret; + + for (i = 0; i < param->nprocs; i++) { + pthread_t tid; + + ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL); + if (ret != 0) + return ret; + } + + while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) { + struct timespec ts_total; + + ret = nanosleep(&ts_remaining, NULL); + if (ret && errno != EINTR) + return ret; + + ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total); + if (ret != 0) + return ret; + + ts_remaining = timespec_sub(&ts_run, &ts_total); + } + + return 0; +} + +/* + * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that + * cpu.stat shows the expected output. + */ +static int test_cpucg_stats(const char *root) +{ + int ret = KSFT_FAIL; + long usage_usec, user_usec, system_usec; + long usage_seconds = 2; + long expected_usage_usec = usage_seconds * USEC_PER_SEC; + char *cpucg; + + cpucg = cg_name(root, "cpucg_test"); + if (!cpucg) + goto cleanup; + + if (cg_create(cpucg)) + goto cleanup; + + usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec"); + user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec"); + system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec"); + if (usage_usec != 0 || user_usec != 0 || system_usec != 0) + goto cleanup; + + struct cpu_hog_func_param param = { + .nprocs = 1, + .ts = { + .tv_sec = usage_seconds, + .tv_nsec = 0, + }, + }; + if (cg_run(cpucg, hog_cpus_timed, (void *)¶m)) + goto cleanup; + + usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec"); + user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec"); + if (user_usec <= 0) + goto cleanup; + + if (!values_close(usage_usec, expected_usage_usec, 1)) + goto cleanup; + + ret = KSFT_PASS; + +cleanup: + cg_destroy(cpucg); + free(cpucg); + + return ret; +} + #define T(x) { x, #x } struct cpucg_test { int (*fn)(const char *root); const char *name; } tests[] = { T(test_cpucg_subtree_control), + T(test_cpucg_stats), }; #undef T