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 *)&param))
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 *)&param);
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 *)&param);
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 *)&param))
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 *)&param))
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