1820a4f88SDavid Vernet // SPDX-License-Identifier: GPL-2.0
2820a4f88SDavid Vernet 
3820a4f88SDavid Vernet #define _GNU_SOURCE
4820a4f88SDavid Vernet #include <linux/limits.h>
56376b22cSDavid Vernet #include <sys/sysinfo.h>
66376b22cSDavid Vernet #include <sys/wait.h>
73c879a1bSDavid Vernet #include <errno.h>
83c879a1bSDavid Vernet #include <pthread.h>
9820a4f88SDavid Vernet #include <stdio.h>
103c879a1bSDavid Vernet #include <time.h>
11820a4f88SDavid Vernet 
12820a4f88SDavid Vernet #include "../kselftest.h"
13820a4f88SDavid Vernet #include "cgroup_util.h"
14820a4f88SDavid Vernet 
156376b22cSDavid Vernet enum hog_clock_type {
166376b22cSDavid Vernet 	// Count elapsed time using the CLOCK_PROCESS_CPUTIME_ID clock.
176376b22cSDavid Vernet 	CPU_HOG_CLOCK_PROCESS,
186376b22cSDavid Vernet 	// Count elapsed time using system wallclock time.
196376b22cSDavid Vernet 	CPU_HOG_CLOCK_WALL,
206376b22cSDavid Vernet };
216376b22cSDavid Vernet 
224ab93063SDavid Vernet struct cpu_hogger {
234ab93063SDavid Vernet 	char *cgroup;
244ab93063SDavid Vernet 	pid_t pid;
254ab93063SDavid Vernet 	long usage;
264ab93063SDavid Vernet };
274ab93063SDavid Vernet 
283c879a1bSDavid Vernet struct cpu_hog_func_param {
293c879a1bSDavid Vernet 	int nprocs;
303c879a1bSDavid Vernet 	struct timespec ts;
316376b22cSDavid Vernet 	enum hog_clock_type clock_type;
323c879a1bSDavid Vernet };
333c879a1bSDavid Vernet 
34820a4f88SDavid Vernet /*
35820a4f88SDavid Vernet  * This test creates two nested cgroups with and without enabling
36820a4f88SDavid Vernet  * the cpu controller.
37820a4f88SDavid Vernet  */
test_cpucg_subtree_control(const char * root)38820a4f88SDavid Vernet static int test_cpucg_subtree_control(const char *root)
39820a4f88SDavid Vernet {
40820a4f88SDavid Vernet 	char *parent = NULL, *child = NULL, *parent2 = NULL, *child2 = NULL;
41820a4f88SDavid Vernet 	int ret = KSFT_FAIL;
42820a4f88SDavid Vernet 
43820a4f88SDavid Vernet 	// Create two nested cgroups with the cpu controller enabled.
44820a4f88SDavid Vernet 	parent = cg_name(root, "cpucg_test_0");
45820a4f88SDavid Vernet 	if (!parent)
46820a4f88SDavid Vernet 		goto cleanup;
47820a4f88SDavid Vernet 
48820a4f88SDavid Vernet 	if (cg_create(parent))
49820a4f88SDavid Vernet 		goto cleanup;
50820a4f88SDavid Vernet 
51820a4f88SDavid Vernet 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
52820a4f88SDavid Vernet 		goto cleanup;
53820a4f88SDavid Vernet 
54820a4f88SDavid Vernet 	child = cg_name(parent, "cpucg_test_child");
55820a4f88SDavid Vernet 	if (!child)
56820a4f88SDavid Vernet 		goto cleanup;
57820a4f88SDavid Vernet 
58820a4f88SDavid Vernet 	if (cg_create(child))
59820a4f88SDavid Vernet 		goto cleanup;
60820a4f88SDavid Vernet 
61820a4f88SDavid Vernet 	if (cg_read_strstr(child, "cgroup.controllers", "cpu"))
62820a4f88SDavid Vernet 		goto cleanup;
63820a4f88SDavid Vernet 
64820a4f88SDavid Vernet 	// Create two nested cgroups without enabling the cpu controller.
65820a4f88SDavid Vernet 	parent2 = cg_name(root, "cpucg_test_1");
66820a4f88SDavid Vernet 	if (!parent2)
67820a4f88SDavid Vernet 		goto cleanup;
68820a4f88SDavid Vernet 
69820a4f88SDavid Vernet 	if (cg_create(parent2))
70820a4f88SDavid Vernet 		goto cleanup;
71820a4f88SDavid Vernet 
72820a4f88SDavid Vernet 	child2 = cg_name(parent2, "cpucg_test_child");
73820a4f88SDavid Vernet 	if (!child2)
74820a4f88SDavid Vernet 		goto cleanup;
75820a4f88SDavid Vernet 
76820a4f88SDavid Vernet 	if (cg_create(child2))
77820a4f88SDavid Vernet 		goto cleanup;
78820a4f88SDavid Vernet 
79820a4f88SDavid Vernet 	if (!cg_read_strstr(child2, "cgroup.controllers", "cpu"))
80820a4f88SDavid Vernet 		goto cleanup;
81820a4f88SDavid Vernet 
82820a4f88SDavid Vernet 	ret = KSFT_PASS;
83820a4f88SDavid Vernet 
84820a4f88SDavid Vernet cleanup:
85820a4f88SDavid Vernet 	cg_destroy(child);
86820a4f88SDavid Vernet 	free(child);
87820a4f88SDavid Vernet 	cg_destroy(child2);
88820a4f88SDavid Vernet 	free(child2);
89820a4f88SDavid Vernet 	cg_destroy(parent);
90820a4f88SDavid Vernet 	free(parent);
91820a4f88SDavid Vernet 	cg_destroy(parent2);
92820a4f88SDavid Vernet 	free(parent2);
93820a4f88SDavid Vernet 
94820a4f88SDavid Vernet 	return ret;
95820a4f88SDavid Vernet }
96820a4f88SDavid Vernet 
hog_cpu_thread_func(void * arg)973c879a1bSDavid Vernet static void *hog_cpu_thread_func(void *arg)
983c879a1bSDavid Vernet {
993c879a1bSDavid Vernet 	while (1)
1003c879a1bSDavid Vernet 		;
1013c879a1bSDavid Vernet 
1023c879a1bSDavid Vernet 	return NULL;
1033c879a1bSDavid Vernet }
1043c879a1bSDavid Vernet 
1053c879a1bSDavid Vernet static struct timespec
timespec_sub(const struct timespec * lhs,const struct timespec * rhs)1063c879a1bSDavid Vernet timespec_sub(const struct timespec *lhs, const struct timespec *rhs)
1073c879a1bSDavid Vernet {
1083c879a1bSDavid Vernet 	struct timespec zero = {
1093c879a1bSDavid Vernet 		.tv_sec = 0,
1103c879a1bSDavid Vernet 		.tv_nsec = 0,
1113c879a1bSDavid Vernet 	};
1123c879a1bSDavid Vernet 	struct timespec ret;
1133c879a1bSDavid Vernet 
1143c879a1bSDavid Vernet 	if (lhs->tv_sec < rhs->tv_sec)
1153c879a1bSDavid Vernet 		return zero;
1163c879a1bSDavid Vernet 
1173c879a1bSDavid Vernet 	ret.tv_sec = lhs->tv_sec - rhs->tv_sec;
1183c879a1bSDavid Vernet 
1193c879a1bSDavid Vernet 	if (lhs->tv_nsec < rhs->tv_nsec) {
1203c879a1bSDavid Vernet 		if (ret.tv_sec == 0)
1213c879a1bSDavid Vernet 			return zero;
1223c879a1bSDavid Vernet 
1233c879a1bSDavid Vernet 		ret.tv_sec--;
1243c879a1bSDavid Vernet 		ret.tv_nsec = NSEC_PER_SEC - rhs->tv_nsec + lhs->tv_nsec;
1253c879a1bSDavid Vernet 	} else
1263c879a1bSDavid Vernet 		ret.tv_nsec = lhs->tv_nsec - rhs->tv_nsec;
1273c879a1bSDavid Vernet 
1283c879a1bSDavid Vernet 	return ret;
1293c879a1bSDavid Vernet }
1303c879a1bSDavid Vernet 
hog_cpus_timed(const char * cgroup,void * arg)1313c879a1bSDavid Vernet static int hog_cpus_timed(const char *cgroup, void *arg)
1323c879a1bSDavid Vernet {
1333c879a1bSDavid Vernet 	const struct cpu_hog_func_param *param =
1343c879a1bSDavid Vernet 		(struct cpu_hog_func_param *)arg;
1353c879a1bSDavid Vernet 	struct timespec ts_run = param->ts;
1363c879a1bSDavid Vernet 	struct timespec ts_remaining = ts_run;
1376376b22cSDavid Vernet 	struct timespec ts_start;
1383c879a1bSDavid Vernet 	int i, ret;
1393c879a1bSDavid Vernet 
1406376b22cSDavid Vernet 	ret = clock_gettime(CLOCK_MONOTONIC, &ts_start);
1416376b22cSDavid Vernet 	if (ret != 0)
1426376b22cSDavid Vernet 		return ret;
1436376b22cSDavid Vernet 
1443c879a1bSDavid Vernet 	for (i = 0; i < param->nprocs; i++) {
1453c879a1bSDavid Vernet 		pthread_t tid;
1463c879a1bSDavid Vernet 
1473c879a1bSDavid Vernet 		ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
1483c879a1bSDavid Vernet 		if (ret != 0)
1493c879a1bSDavid Vernet 			return ret;
1503c879a1bSDavid Vernet 	}
1513c879a1bSDavid Vernet 
1523c879a1bSDavid Vernet 	while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
1533c879a1bSDavid Vernet 		struct timespec ts_total;
1543c879a1bSDavid Vernet 
1553c879a1bSDavid Vernet 		ret = nanosleep(&ts_remaining, NULL);
1563c879a1bSDavid Vernet 		if (ret && errno != EINTR)
1573c879a1bSDavid Vernet 			return ret;
1583c879a1bSDavid Vernet 
1596376b22cSDavid Vernet 		if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
1603c879a1bSDavid Vernet 			ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts_total);
1613c879a1bSDavid Vernet 			if (ret != 0)
1623c879a1bSDavid Vernet 				return ret;
1636376b22cSDavid Vernet 		} else {
1646376b22cSDavid Vernet 			struct timespec ts_current;
1656376b22cSDavid Vernet 
1666376b22cSDavid Vernet 			ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
1676376b22cSDavid Vernet 			if (ret != 0)
1686376b22cSDavid Vernet 				return ret;
1696376b22cSDavid Vernet 
1706376b22cSDavid Vernet 			ts_total = timespec_sub(&ts_current, &ts_start);
1716376b22cSDavid Vernet 		}
1723c879a1bSDavid Vernet 
1733c879a1bSDavid Vernet 		ts_remaining = timespec_sub(&ts_run, &ts_total);
1743c879a1bSDavid Vernet 	}
1753c879a1bSDavid Vernet 
1763c879a1bSDavid Vernet 	return 0;
1773c879a1bSDavid Vernet }
1783c879a1bSDavid Vernet 
1793c879a1bSDavid Vernet /*
1803c879a1bSDavid Vernet  * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that
1813c879a1bSDavid Vernet  * cpu.stat shows the expected output.
1823c879a1bSDavid Vernet  */
test_cpucg_stats(const char * root)1833c879a1bSDavid Vernet static int test_cpucg_stats(const char *root)
1843c879a1bSDavid Vernet {
1853c879a1bSDavid Vernet 	int ret = KSFT_FAIL;
1863c879a1bSDavid Vernet 	long usage_usec, user_usec, system_usec;
1873c879a1bSDavid Vernet 	long usage_seconds = 2;
1883c879a1bSDavid Vernet 	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
1893c879a1bSDavid Vernet 	char *cpucg;
1903c879a1bSDavid Vernet 
1913c879a1bSDavid Vernet 	cpucg = cg_name(root, "cpucg_test");
1923c879a1bSDavid Vernet 	if (!cpucg)
1933c879a1bSDavid Vernet 		goto cleanup;
1943c879a1bSDavid Vernet 
1953c879a1bSDavid Vernet 	if (cg_create(cpucg))
1963c879a1bSDavid Vernet 		goto cleanup;
1973c879a1bSDavid Vernet 
1983c879a1bSDavid Vernet 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
1993c879a1bSDavid Vernet 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
2003c879a1bSDavid Vernet 	system_usec = cg_read_key_long(cpucg, "cpu.stat", "system_usec");
2013c879a1bSDavid Vernet 	if (usage_usec != 0 || user_usec != 0 || system_usec != 0)
2023c879a1bSDavid Vernet 		goto cleanup;
2033c879a1bSDavid Vernet 
2043c879a1bSDavid Vernet 	struct cpu_hog_func_param param = {
2053c879a1bSDavid Vernet 		.nprocs = 1,
2063c879a1bSDavid Vernet 		.ts = {
2073c879a1bSDavid Vernet 			.tv_sec = usage_seconds,
2083c879a1bSDavid Vernet 			.tv_nsec = 0,
2093c879a1bSDavid Vernet 		},
2106376b22cSDavid Vernet 		.clock_type = CPU_HOG_CLOCK_PROCESS,
2113c879a1bSDavid Vernet 	};
2123c879a1bSDavid Vernet 	if (cg_run(cpucg, hog_cpus_timed, (void *)&param))
2133c879a1bSDavid Vernet 		goto cleanup;
2143c879a1bSDavid Vernet 
2153c879a1bSDavid Vernet 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
2163c879a1bSDavid Vernet 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
2173c879a1bSDavid Vernet 	if (user_usec <= 0)
2183c879a1bSDavid Vernet 		goto cleanup;
2193c879a1bSDavid Vernet 
2203c879a1bSDavid Vernet 	if (!values_close(usage_usec, expected_usage_usec, 1))
2213c879a1bSDavid Vernet 		goto cleanup;
2223c879a1bSDavid Vernet 
2233c879a1bSDavid Vernet 	ret = KSFT_PASS;
2243c879a1bSDavid Vernet 
2253c879a1bSDavid Vernet cleanup:
2263c879a1bSDavid Vernet 	cg_destroy(cpucg);
2273c879a1bSDavid Vernet 	free(cpucg);
2283c879a1bSDavid Vernet 
2293c879a1bSDavid Vernet 	return ret;
2303c879a1bSDavid Vernet }
2313c879a1bSDavid Vernet 
2324ab93063SDavid Vernet static int
run_cpucg_weight_test(const char * root,pid_t (* spawn_child)(const struct cpu_hogger * child),int (* validate)(const struct cpu_hogger * children,int num_children))2334ab93063SDavid Vernet run_cpucg_weight_test(
2344ab93063SDavid Vernet 		const char *root,
2354ab93063SDavid Vernet 		pid_t (*spawn_child)(const struct cpu_hogger *child),
2364ab93063SDavid Vernet 		int (*validate)(const struct cpu_hogger *children, int num_children))
2376376b22cSDavid Vernet {
2386376b22cSDavid Vernet 	int ret = KSFT_FAIL, i;
2396376b22cSDavid Vernet 	char *parent = NULL;
2404ab93063SDavid Vernet 	struct cpu_hogger children[3] = {NULL};
2416376b22cSDavid Vernet 
2426376b22cSDavid Vernet 	parent = cg_name(root, "cpucg_test_0");
2436376b22cSDavid Vernet 	if (!parent)
2446376b22cSDavid Vernet 		goto cleanup;
2456376b22cSDavid Vernet 
2466376b22cSDavid Vernet 	if (cg_create(parent))
2476376b22cSDavid Vernet 		goto cleanup;
2486376b22cSDavid Vernet 
2496376b22cSDavid Vernet 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
2506376b22cSDavid Vernet 		goto cleanup;
2516376b22cSDavid Vernet 
2526376b22cSDavid Vernet 	for (i = 0; i < ARRAY_SIZE(children); i++) {
2536376b22cSDavid Vernet 		children[i].cgroup = cg_name_indexed(parent, "cpucg_child", i);
2546376b22cSDavid Vernet 		if (!children[i].cgroup)
2556376b22cSDavid Vernet 			goto cleanup;
2566376b22cSDavid Vernet 
2576376b22cSDavid Vernet 		if (cg_create(children[i].cgroup))
2586376b22cSDavid Vernet 			goto cleanup;
2596376b22cSDavid Vernet 
2606376b22cSDavid Vernet 		if (cg_write_numeric(children[i].cgroup, "cpu.weight",
2616376b22cSDavid Vernet 					50 * (i + 1)))
2626376b22cSDavid Vernet 			goto cleanup;
2636376b22cSDavid Vernet 	}
2646376b22cSDavid Vernet 
2656376b22cSDavid Vernet 	for (i = 0; i < ARRAY_SIZE(children); i++) {
2664ab93063SDavid Vernet 		pid_t pid = spawn_child(&children[i]);
2676376b22cSDavid Vernet 		if (pid <= 0)
2686376b22cSDavid Vernet 			goto cleanup;
2696376b22cSDavid Vernet 		children[i].pid = pid;
2706376b22cSDavid Vernet 	}
2716376b22cSDavid Vernet 
2726376b22cSDavid Vernet 	for (i = 0; i < ARRAY_SIZE(children); i++) {
2736376b22cSDavid Vernet 		int retcode;
2746376b22cSDavid Vernet 
2756376b22cSDavid Vernet 		waitpid(children[i].pid, &retcode, 0);
2766376b22cSDavid Vernet 		if (!WIFEXITED(retcode))
2776376b22cSDavid Vernet 			goto cleanup;
2786376b22cSDavid Vernet 		if (WEXITSTATUS(retcode))
2796376b22cSDavid Vernet 			goto cleanup;
2806376b22cSDavid Vernet 	}
2816376b22cSDavid Vernet 
2826376b22cSDavid Vernet 	for (i = 0; i < ARRAY_SIZE(children); i++)
2836376b22cSDavid Vernet 		children[i].usage = cg_read_key_long(children[i].cgroup,
2846376b22cSDavid Vernet 				"cpu.stat", "usage_usec");
2856376b22cSDavid Vernet 
2864ab93063SDavid Vernet 	if (validate(children, ARRAY_SIZE(children)))
2876376b22cSDavid Vernet 		goto cleanup;
2886376b22cSDavid Vernet 
2896376b22cSDavid Vernet 	ret = KSFT_PASS;
2906376b22cSDavid Vernet cleanup:
2916376b22cSDavid Vernet 	for (i = 0; i < ARRAY_SIZE(children); i++) {
2926376b22cSDavid Vernet 		cg_destroy(children[i].cgroup);
2936376b22cSDavid Vernet 		free(children[i].cgroup);
2946376b22cSDavid Vernet 	}
2956376b22cSDavid Vernet 	cg_destroy(parent);
2966376b22cSDavid Vernet 	free(parent);
2976376b22cSDavid Vernet 
2986376b22cSDavid Vernet 	return ret;
2996376b22cSDavid Vernet }
3006376b22cSDavid Vernet 
weight_hog_ncpus(const struct cpu_hogger * child,int ncpus)3014ab93063SDavid Vernet static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)
3024ab93063SDavid Vernet {
3034ab93063SDavid Vernet 	long usage_seconds = 10;
3044ab93063SDavid Vernet 	struct cpu_hog_func_param param = {
3054ab93063SDavid Vernet 		.nprocs = ncpus,
3064ab93063SDavid Vernet 		.ts = {
3074ab93063SDavid Vernet 			.tv_sec = usage_seconds,
3084ab93063SDavid Vernet 			.tv_nsec = 0,
3094ab93063SDavid Vernet 		},
3104ab93063SDavid Vernet 		.clock_type = CPU_HOG_CLOCK_WALL,
3114ab93063SDavid Vernet 	};
3124ab93063SDavid Vernet 	return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)&param);
3134ab93063SDavid Vernet }
3144ab93063SDavid Vernet 
weight_hog_all_cpus(const struct cpu_hogger * child)3154ab93063SDavid Vernet static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)
3164ab93063SDavid Vernet {
3174ab93063SDavid Vernet 	return weight_hog_ncpus(child, get_nprocs());
3184ab93063SDavid Vernet }
3194ab93063SDavid Vernet 
3204ab93063SDavid Vernet static int
overprovision_validate(const struct cpu_hogger * children,int num_children)3214ab93063SDavid Vernet overprovision_validate(const struct cpu_hogger *children, int num_children)
3224ab93063SDavid Vernet {
3234ab93063SDavid Vernet 	int ret = KSFT_FAIL, i;
3244ab93063SDavid Vernet 
3254ab93063SDavid Vernet 	for (i = 0; i < num_children - 1; i++) {
3264ab93063SDavid Vernet 		long delta;
3274ab93063SDavid Vernet 
3284ab93063SDavid Vernet 		if (children[i + 1].usage <= children[i].usage)
3294ab93063SDavid Vernet 			goto cleanup;
3304ab93063SDavid Vernet 
3314ab93063SDavid Vernet 		delta = children[i + 1].usage - children[i].usage;
3324ab93063SDavid Vernet 		if (!values_close(delta, children[0].usage, 35))
3334ab93063SDavid Vernet 			goto cleanup;
3344ab93063SDavid Vernet 	}
3354ab93063SDavid Vernet 
3364ab93063SDavid Vernet 	ret = KSFT_PASS;
3374ab93063SDavid Vernet cleanup:
3384ab93063SDavid Vernet 	return ret;
3394ab93063SDavid Vernet }
3404ab93063SDavid Vernet 
3414ab93063SDavid Vernet /*
3424ab93063SDavid Vernet  * First, this test creates the following hierarchy:
3434ab93063SDavid Vernet  * A
3444ab93063SDavid Vernet  * A/B     cpu.weight = 50
3454ab93063SDavid Vernet  * A/C     cpu.weight = 100
3464ab93063SDavid Vernet  * A/D     cpu.weight = 150
3474ab93063SDavid Vernet  *
3484ab93063SDavid Vernet  * A separate process is then created for each child cgroup which spawns as
3494ab93063SDavid Vernet  * many threads as there are cores, and hogs each CPU as much as possible
3504ab93063SDavid Vernet  * for some time interval.
3514ab93063SDavid Vernet  *
3524ab93063SDavid Vernet  * Once all of the children have exited, we verify that each child cgroup
3534ab93063SDavid Vernet  * was given proportional runtime as informed by their cpu.weight.
3544ab93063SDavid Vernet  */
test_cpucg_weight_overprovisioned(const char * root)3554ab93063SDavid Vernet static int test_cpucg_weight_overprovisioned(const char *root)
3564ab93063SDavid Vernet {
3574ab93063SDavid Vernet 	return run_cpucg_weight_test(root, weight_hog_all_cpus,
3584ab93063SDavid Vernet 			overprovision_validate);
3594ab93063SDavid Vernet }
3604ab93063SDavid Vernet 
weight_hog_one_cpu(const struct cpu_hogger * child)3614ab93063SDavid Vernet static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)
3624ab93063SDavid Vernet {
3634ab93063SDavid Vernet 	return weight_hog_ncpus(child, 1);
3644ab93063SDavid Vernet }
3654ab93063SDavid Vernet 
3664ab93063SDavid Vernet static int
underprovision_validate(const struct cpu_hogger * children,int num_children)3674ab93063SDavid Vernet underprovision_validate(const struct cpu_hogger *children, int num_children)
3684ab93063SDavid Vernet {
3694ab93063SDavid Vernet 	int ret = KSFT_FAIL, i;
3704ab93063SDavid Vernet 
3714ab93063SDavid Vernet 	for (i = 0; i < num_children - 1; i++) {
3724ab93063SDavid Vernet 		if (!values_close(children[i + 1].usage, children[0].usage, 15))
3734ab93063SDavid Vernet 			goto cleanup;
3744ab93063SDavid Vernet 	}
3754ab93063SDavid Vernet 
3764ab93063SDavid Vernet 	ret = KSFT_PASS;
3774ab93063SDavid Vernet cleanup:
3784ab93063SDavid Vernet 	return ret;
3794ab93063SDavid Vernet }
3804ab93063SDavid Vernet 
3814ab93063SDavid Vernet /*
3824ab93063SDavid Vernet  * First, this test creates the following hierarchy:
3834ab93063SDavid Vernet  * A
3844ab93063SDavid Vernet  * A/B     cpu.weight = 50
3854ab93063SDavid Vernet  * A/C     cpu.weight = 100
3864ab93063SDavid Vernet  * A/D     cpu.weight = 150
3874ab93063SDavid Vernet  *
3884ab93063SDavid Vernet  * A separate process is then created for each child cgroup which spawns a
3894ab93063SDavid Vernet  * single thread that hogs a CPU. The testcase is only run on systems that
3904ab93063SDavid Vernet  * have at least one core per-thread in the child processes.
3914ab93063SDavid Vernet  *
3924ab93063SDavid Vernet  * Once all of the children have exited, we verify that each child cgroup
3934ab93063SDavid Vernet  * had roughly the same runtime despite having different cpu.weight.
3944ab93063SDavid Vernet  */
test_cpucg_weight_underprovisioned(const char * root)3954ab93063SDavid Vernet static int test_cpucg_weight_underprovisioned(const char *root)
3964ab93063SDavid Vernet {
3974ab93063SDavid Vernet 	// Only run the test if there are enough cores to avoid overprovisioning
3984ab93063SDavid Vernet 	// the system.
3994ab93063SDavid Vernet 	if (get_nprocs() < 4)
4004ab93063SDavid Vernet 		return KSFT_SKIP;
4014ab93063SDavid Vernet 
4024ab93063SDavid Vernet 	return run_cpucg_weight_test(root, weight_hog_one_cpu,
4034ab93063SDavid Vernet 			underprovision_validate);
4044ab93063SDavid Vernet }
4054ab93063SDavid Vernet 
406b76ee4f5SDavid Vernet static int
run_cpucg_nested_weight_test(const char * root,bool overprovisioned)40789ca0efaSDavid Vernet run_cpucg_nested_weight_test(const char *root, bool overprovisioned)
408b76ee4f5SDavid Vernet {
409b76ee4f5SDavid Vernet 	int ret = KSFT_FAIL, i;
410b76ee4f5SDavid Vernet 	char *parent = NULL, *child = NULL;
411b76ee4f5SDavid Vernet 	struct cpu_hogger leaf[3] = {NULL};
412b76ee4f5SDavid Vernet 	long nested_leaf_usage, child_usage;
413b76ee4f5SDavid Vernet 	int nprocs = get_nprocs();
414b76ee4f5SDavid Vernet 
41589ca0efaSDavid Vernet 	if (!overprovisioned) {
41689ca0efaSDavid Vernet 		if (nprocs < 4)
41789ca0efaSDavid Vernet 			/*
41889ca0efaSDavid Vernet 			 * Only run the test if there are enough cores to avoid overprovisioning
41989ca0efaSDavid Vernet 			 * the system.
42089ca0efaSDavid Vernet 			 */
42189ca0efaSDavid Vernet 			return KSFT_SKIP;
42289ca0efaSDavid Vernet 		nprocs /= 4;
42389ca0efaSDavid Vernet 	}
42489ca0efaSDavid Vernet 
425b76ee4f5SDavid Vernet 	parent = cg_name(root, "cpucg_test");
426b76ee4f5SDavid Vernet 	child = cg_name(parent, "cpucg_child");
427b76ee4f5SDavid Vernet 	if (!parent || !child)
428b76ee4f5SDavid Vernet 		goto cleanup;
429b76ee4f5SDavid Vernet 
430b76ee4f5SDavid Vernet 	if (cg_create(parent))
431b76ee4f5SDavid Vernet 		goto cleanup;
432b76ee4f5SDavid Vernet 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
433b76ee4f5SDavid Vernet 		goto cleanup;
434b76ee4f5SDavid Vernet 
435b76ee4f5SDavid Vernet 	if (cg_create(child))
436b76ee4f5SDavid Vernet 		goto cleanup;
437b76ee4f5SDavid Vernet 	if (cg_write(child, "cgroup.subtree_control", "+cpu"))
438b76ee4f5SDavid Vernet 		goto cleanup;
439b76ee4f5SDavid Vernet 	if (cg_write(child, "cpu.weight", "1000"))
440b76ee4f5SDavid Vernet 		goto cleanup;
441b76ee4f5SDavid Vernet 
442b76ee4f5SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
443b76ee4f5SDavid Vernet 		const char *ancestor;
444b76ee4f5SDavid Vernet 		long weight;
445b76ee4f5SDavid Vernet 
446b76ee4f5SDavid Vernet 		if (i == 0) {
447b76ee4f5SDavid Vernet 			ancestor = parent;
448b76ee4f5SDavid Vernet 			weight = 1000;
449b76ee4f5SDavid Vernet 		} else {
450b76ee4f5SDavid Vernet 			ancestor = child;
451b76ee4f5SDavid Vernet 			weight = 5000;
452b76ee4f5SDavid Vernet 		}
453b76ee4f5SDavid Vernet 		leaf[i].cgroup = cg_name_indexed(ancestor, "cpucg_leaf", i);
454b76ee4f5SDavid Vernet 		if (!leaf[i].cgroup)
455b76ee4f5SDavid Vernet 			goto cleanup;
456b76ee4f5SDavid Vernet 
457b76ee4f5SDavid Vernet 		if (cg_create(leaf[i].cgroup))
458b76ee4f5SDavid Vernet 			goto cleanup;
459b76ee4f5SDavid Vernet 
460b76ee4f5SDavid Vernet 		if (cg_write_numeric(leaf[i].cgroup, "cpu.weight", weight))
461b76ee4f5SDavid Vernet 			goto cleanup;
462b76ee4f5SDavid Vernet 	}
463b76ee4f5SDavid Vernet 
464b76ee4f5SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
465b76ee4f5SDavid Vernet 		pid_t pid;
466b76ee4f5SDavid Vernet 		struct cpu_hog_func_param param = {
467b76ee4f5SDavid Vernet 			.nprocs = nprocs,
468b76ee4f5SDavid Vernet 			.ts = {
469b76ee4f5SDavid Vernet 				.tv_sec = 10,
470b76ee4f5SDavid Vernet 				.tv_nsec = 0,
471b76ee4f5SDavid Vernet 			},
472b76ee4f5SDavid Vernet 			.clock_type = CPU_HOG_CLOCK_WALL,
473b76ee4f5SDavid Vernet 		};
474b76ee4f5SDavid Vernet 
475b76ee4f5SDavid Vernet 		pid = cg_run_nowait(leaf[i].cgroup, hog_cpus_timed,
476b76ee4f5SDavid Vernet 				(void *)&param);
477b76ee4f5SDavid Vernet 		if (pid <= 0)
478b76ee4f5SDavid Vernet 			goto cleanup;
479b76ee4f5SDavid Vernet 		leaf[i].pid = pid;
480b76ee4f5SDavid Vernet 	}
481b76ee4f5SDavid Vernet 
482b76ee4f5SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
483b76ee4f5SDavid Vernet 		int retcode;
484b76ee4f5SDavid Vernet 
485b76ee4f5SDavid Vernet 		waitpid(leaf[i].pid, &retcode, 0);
486b76ee4f5SDavid Vernet 		if (!WIFEXITED(retcode))
487b76ee4f5SDavid Vernet 			goto cleanup;
488b76ee4f5SDavid Vernet 		if (WEXITSTATUS(retcode))
489b76ee4f5SDavid Vernet 			goto cleanup;
490b76ee4f5SDavid Vernet 	}
491b76ee4f5SDavid Vernet 
492b76ee4f5SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
493b76ee4f5SDavid Vernet 		leaf[i].usage = cg_read_key_long(leaf[i].cgroup,
494b76ee4f5SDavid Vernet 				"cpu.stat", "usage_usec");
495b76ee4f5SDavid Vernet 		if (leaf[i].usage <= 0)
496b76ee4f5SDavid Vernet 			goto cleanup;
497b76ee4f5SDavid Vernet 	}
498b76ee4f5SDavid Vernet 
499b76ee4f5SDavid Vernet 	nested_leaf_usage = leaf[1].usage + leaf[2].usage;
50089ca0efaSDavid Vernet 	if (overprovisioned) {
501b76ee4f5SDavid Vernet 		if (!values_close(leaf[0].usage, nested_leaf_usage, 15))
502b76ee4f5SDavid Vernet 			goto cleanup;
50389ca0efaSDavid Vernet 	} else if (!values_close(leaf[0].usage * 2, nested_leaf_usage, 15))
50489ca0efaSDavid Vernet 		goto cleanup;
50589ca0efaSDavid Vernet 
506b76ee4f5SDavid Vernet 
507b76ee4f5SDavid Vernet 	child_usage = cg_read_key_long(child, "cpu.stat", "usage_usec");
508b76ee4f5SDavid Vernet 	if (child_usage <= 0)
509b76ee4f5SDavid Vernet 		goto cleanup;
510b76ee4f5SDavid Vernet 	if (!values_close(child_usage, nested_leaf_usage, 1))
511b76ee4f5SDavid Vernet 		goto cleanup;
512b76ee4f5SDavid Vernet 
513b76ee4f5SDavid Vernet 	ret = KSFT_PASS;
514b76ee4f5SDavid Vernet cleanup:
515b76ee4f5SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(leaf); i++) {
516b76ee4f5SDavid Vernet 		cg_destroy(leaf[i].cgroup);
517b76ee4f5SDavid Vernet 		free(leaf[i].cgroup);
518b76ee4f5SDavid Vernet 	}
519b76ee4f5SDavid Vernet 	cg_destroy(child);
520b76ee4f5SDavid Vernet 	free(child);
521b76ee4f5SDavid Vernet 	cg_destroy(parent);
522b76ee4f5SDavid Vernet 	free(parent);
523b76ee4f5SDavid Vernet 
524b76ee4f5SDavid Vernet 	return ret;
525b76ee4f5SDavid Vernet }
526b76ee4f5SDavid Vernet 
52789ca0efaSDavid Vernet /*
52889ca0efaSDavid Vernet  * First, this test creates the following hierarchy:
52989ca0efaSDavid Vernet  * A
53089ca0efaSDavid Vernet  * A/B     cpu.weight = 1000
53189ca0efaSDavid Vernet  * A/C     cpu.weight = 1000
53289ca0efaSDavid Vernet  * A/C/D   cpu.weight = 5000
53389ca0efaSDavid Vernet  * A/C/E   cpu.weight = 5000
53489ca0efaSDavid Vernet  *
53589ca0efaSDavid Vernet  * A separate process is then created for each leaf, which spawn nproc threads
53689ca0efaSDavid Vernet  * that burn a CPU for a few seconds.
53789ca0efaSDavid Vernet  *
53889ca0efaSDavid Vernet  * Once all of those processes have exited, we verify that each of the leaf
53989ca0efaSDavid Vernet  * cgroups have roughly the same usage from cpu.stat.
54089ca0efaSDavid Vernet  */
54189ca0efaSDavid Vernet static int
test_cpucg_nested_weight_overprovisioned(const char * root)54289ca0efaSDavid Vernet test_cpucg_nested_weight_overprovisioned(const char *root)
54389ca0efaSDavid Vernet {
54489ca0efaSDavid Vernet 	return run_cpucg_nested_weight_test(root, true);
54589ca0efaSDavid Vernet }
54689ca0efaSDavid Vernet 
54789ca0efaSDavid Vernet /*
54889ca0efaSDavid Vernet  * First, this test creates the following hierarchy:
54989ca0efaSDavid Vernet  * A
55089ca0efaSDavid Vernet  * A/B     cpu.weight = 1000
55189ca0efaSDavid Vernet  * A/C     cpu.weight = 1000
55289ca0efaSDavid Vernet  * A/C/D   cpu.weight = 5000
55389ca0efaSDavid Vernet  * A/C/E   cpu.weight = 5000
55489ca0efaSDavid Vernet  *
55589ca0efaSDavid Vernet  * A separate process is then created for each leaf, which nproc / 4 threads
55689ca0efaSDavid Vernet  * that burns a CPU for a few seconds.
55789ca0efaSDavid Vernet  *
55889ca0efaSDavid Vernet  * Once all of those processes have exited, we verify that each of the leaf
55989ca0efaSDavid Vernet  * cgroups have roughly the same usage from cpu.stat.
56089ca0efaSDavid Vernet  */
56189ca0efaSDavid Vernet static int
test_cpucg_nested_weight_underprovisioned(const char * root)56289ca0efaSDavid Vernet test_cpucg_nested_weight_underprovisioned(const char *root)
56389ca0efaSDavid Vernet {
56489ca0efaSDavid Vernet 	return run_cpucg_nested_weight_test(root, false);
56589ca0efaSDavid Vernet }
56689ca0efaSDavid Vernet 
567889ab811SDavid Vernet /*
568889ab811SDavid Vernet  * This test creates a cgroup with some maximum value within a period, and
569889ab811SDavid Vernet  * verifies that a process in the cgroup is not overscheduled.
570889ab811SDavid Vernet  */
test_cpucg_max(const char * root)571889ab811SDavid Vernet static int test_cpucg_max(const char *root)
572889ab811SDavid Vernet {
573889ab811SDavid Vernet 	int ret = KSFT_FAIL;
574889ab811SDavid Vernet 	long usage_usec, user_usec;
575889ab811SDavid Vernet 	long usage_seconds = 1;
576889ab811SDavid Vernet 	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
577889ab811SDavid Vernet 	char *cpucg;
578889ab811SDavid Vernet 
579889ab811SDavid Vernet 	cpucg = cg_name(root, "cpucg_test");
580889ab811SDavid Vernet 	if (!cpucg)
581889ab811SDavid Vernet 		goto cleanup;
582889ab811SDavid Vernet 
583889ab811SDavid Vernet 	if (cg_create(cpucg))
584889ab811SDavid Vernet 		goto cleanup;
585889ab811SDavid Vernet 
586889ab811SDavid Vernet 	if (cg_write(cpucg, "cpu.max", "1000"))
587889ab811SDavid Vernet 		goto cleanup;
588889ab811SDavid Vernet 
589889ab811SDavid Vernet 	struct cpu_hog_func_param param = {
590889ab811SDavid Vernet 		.nprocs = 1,
591889ab811SDavid Vernet 		.ts = {
592889ab811SDavid Vernet 			.tv_sec = usage_seconds,
593889ab811SDavid Vernet 			.tv_nsec = 0,
594889ab811SDavid Vernet 		},
595889ab811SDavid Vernet 		.clock_type = CPU_HOG_CLOCK_WALL,
596889ab811SDavid Vernet 	};
597889ab811SDavid Vernet 	if (cg_run(cpucg, hog_cpus_timed, (void *)&param))
598889ab811SDavid Vernet 		goto cleanup;
599889ab811SDavid Vernet 
600889ab811SDavid Vernet 	usage_usec = cg_read_key_long(cpucg, "cpu.stat", "usage_usec");
601889ab811SDavid Vernet 	user_usec = cg_read_key_long(cpucg, "cpu.stat", "user_usec");
602889ab811SDavid Vernet 	if (user_usec <= 0)
603889ab811SDavid Vernet 		goto cleanup;
604889ab811SDavid Vernet 
605889ab811SDavid Vernet 	if (user_usec >= expected_usage_usec)
606889ab811SDavid Vernet 		goto cleanup;
607889ab811SDavid Vernet 
608889ab811SDavid Vernet 	if (values_close(usage_usec, expected_usage_usec, 95))
609889ab811SDavid Vernet 		goto cleanup;
610889ab811SDavid Vernet 
611889ab811SDavid Vernet 	ret = KSFT_PASS;
612889ab811SDavid Vernet 
613889ab811SDavid Vernet cleanup:
614889ab811SDavid Vernet 	cg_destroy(cpucg);
615889ab811SDavid Vernet 	free(cpucg);
616889ab811SDavid Vernet 
617889ab811SDavid Vernet 	return ret;
618889ab811SDavid Vernet }
619889ab811SDavid Vernet 
620*a7990657SDavid Vernet /*
621*a7990657SDavid Vernet  * This test verifies that a process inside of a nested cgroup whose parent
622*a7990657SDavid Vernet  * group has a cpu.max value set, is properly throttled.
623*a7990657SDavid Vernet  */
test_cpucg_max_nested(const char * root)624*a7990657SDavid Vernet static int test_cpucg_max_nested(const char *root)
625*a7990657SDavid Vernet {
626*a7990657SDavid Vernet 	int ret = KSFT_FAIL;
627*a7990657SDavid Vernet 	long usage_usec, user_usec;
628*a7990657SDavid Vernet 	long usage_seconds = 1;
629*a7990657SDavid Vernet 	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
630*a7990657SDavid Vernet 	char *parent, *child;
631*a7990657SDavid Vernet 
632*a7990657SDavid Vernet 	parent = cg_name(root, "cpucg_parent");
633*a7990657SDavid Vernet 	child = cg_name(parent, "cpucg_child");
634*a7990657SDavid Vernet 	if (!parent || !child)
635*a7990657SDavid Vernet 		goto cleanup;
636*a7990657SDavid Vernet 
637*a7990657SDavid Vernet 	if (cg_create(parent))
638*a7990657SDavid Vernet 		goto cleanup;
639*a7990657SDavid Vernet 
640*a7990657SDavid Vernet 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
641*a7990657SDavid Vernet 		goto cleanup;
642*a7990657SDavid Vernet 
643*a7990657SDavid Vernet 	if (cg_create(child))
644*a7990657SDavid Vernet 		goto cleanup;
645*a7990657SDavid Vernet 
646*a7990657SDavid Vernet 	if (cg_write(parent, "cpu.max", "1000"))
647*a7990657SDavid Vernet 		goto cleanup;
648*a7990657SDavid Vernet 
649*a7990657SDavid Vernet 	struct cpu_hog_func_param param = {
650*a7990657SDavid Vernet 		.nprocs = 1,
651*a7990657SDavid Vernet 		.ts = {
652*a7990657SDavid Vernet 			.tv_sec = usage_seconds,
653*a7990657SDavid Vernet 			.tv_nsec = 0,
654*a7990657SDavid Vernet 		},
655*a7990657SDavid Vernet 		.clock_type = CPU_HOG_CLOCK_WALL,
656*a7990657SDavid Vernet 	};
657*a7990657SDavid Vernet 	if (cg_run(child, hog_cpus_timed, (void *)&param))
658*a7990657SDavid Vernet 		goto cleanup;
659*a7990657SDavid Vernet 
660*a7990657SDavid Vernet 	usage_usec = cg_read_key_long(child, "cpu.stat", "usage_usec");
661*a7990657SDavid Vernet 	user_usec = cg_read_key_long(child, "cpu.stat", "user_usec");
662*a7990657SDavid Vernet 	if (user_usec <= 0)
663*a7990657SDavid Vernet 		goto cleanup;
664*a7990657SDavid Vernet 
665*a7990657SDavid Vernet 	if (user_usec >= expected_usage_usec)
666*a7990657SDavid Vernet 		goto cleanup;
667*a7990657SDavid Vernet 
668*a7990657SDavid Vernet 	if (values_close(usage_usec, expected_usage_usec, 95))
669*a7990657SDavid Vernet 		goto cleanup;
670*a7990657SDavid Vernet 
671*a7990657SDavid Vernet 	ret = KSFT_PASS;
672*a7990657SDavid Vernet 
673*a7990657SDavid Vernet cleanup:
674*a7990657SDavid Vernet 	cg_destroy(child);
675*a7990657SDavid Vernet 	free(child);
676*a7990657SDavid Vernet 	cg_destroy(parent);
677*a7990657SDavid Vernet 	free(parent);
678*a7990657SDavid Vernet 
679*a7990657SDavid Vernet 	return ret;
680*a7990657SDavid Vernet }
681*a7990657SDavid Vernet 
682820a4f88SDavid Vernet #define T(x) { x, #x }
683820a4f88SDavid Vernet struct cpucg_test {
684820a4f88SDavid Vernet 	int (*fn)(const char *root);
685820a4f88SDavid Vernet 	const char *name;
686820a4f88SDavid Vernet } tests[] = {
687820a4f88SDavid Vernet 	T(test_cpucg_subtree_control),
6883c879a1bSDavid Vernet 	T(test_cpucg_stats),
6896376b22cSDavid Vernet 	T(test_cpucg_weight_overprovisioned),
6904ab93063SDavid Vernet 	T(test_cpucg_weight_underprovisioned),
691b76ee4f5SDavid Vernet 	T(test_cpucg_nested_weight_overprovisioned),
69289ca0efaSDavid Vernet 	T(test_cpucg_nested_weight_underprovisioned),
693889ab811SDavid Vernet 	T(test_cpucg_max),
694*a7990657SDavid Vernet 	T(test_cpucg_max_nested),
695820a4f88SDavid Vernet };
696820a4f88SDavid Vernet #undef T
697820a4f88SDavid Vernet 
main(int argc,char * argv[])698820a4f88SDavid Vernet int main(int argc, char *argv[])
699820a4f88SDavid Vernet {
700820a4f88SDavid Vernet 	char root[PATH_MAX];
701820a4f88SDavid Vernet 	int i, ret = EXIT_SUCCESS;
702820a4f88SDavid Vernet 
703820a4f88SDavid Vernet 	if (cg_find_unified_root(root, sizeof(root)))
704820a4f88SDavid Vernet 		ksft_exit_skip("cgroup v2 isn't mounted\n");
705820a4f88SDavid Vernet 
706820a4f88SDavid Vernet 	if (cg_read_strstr(root, "cgroup.subtree_control", "cpu"))
707820a4f88SDavid Vernet 		if (cg_write(root, "cgroup.subtree_control", "+cpu"))
708820a4f88SDavid Vernet 			ksft_exit_skip("Failed to set cpu controller\n");
709820a4f88SDavid Vernet 
710820a4f88SDavid Vernet 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
711820a4f88SDavid Vernet 		switch (tests[i].fn(root)) {
712820a4f88SDavid Vernet 		case KSFT_PASS:
713820a4f88SDavid Vernet 			ksft_test_result_pass("%s\n", tests[i].name);
714820a4f88SDavid Vernet 			break;
715820a4f88SDavid Vernet 		case KSFT_SKIP:
716820a4f88SDavid Vernet 			ksft_test_result_skip("%s\n", tests[i].name);
717820a4f88SDavid Vernet 			break;
718820a4f88SDavid Vernet 		default:
719820a4f88SDavid Vernet 			ret = EXIT_FAILURE;
720820a4f88SDavid Vernet 			ksft_test_result_fail("%s\n", tests[i].name);
721820a4f88SDavid Vernet 			break;
722820a4f88SDavid Vernet 		}
723820a4f88SDavid Vernet 	}
724820a4f88SDavid Vernet 
725820a4f88SDavid Vernet 	return ret;
726820a4f88SDavid Vernet }
727