2022-04-22 18:01:42

by David Vernet

[permalink] [raw]
Subject: [PATCH 0/4] cgroup: Introduce cpu controller test suite

This patchset introduces a new test_cpu.c test suite as part of
tools/testing/selftests/cgroup. test_cpu.c will contain testcases that
validate the cgroup v2 cpu controller.

This patchset only contains testcases that validate cpu.stat and
cpu.weight, but I'm expecting to send further patchsets after this that
also include testcases that validate other knobs such as cpu.max.

Note that checkpatch complains about a missing MAINTAINERS file entry for
[PATCH 1/4], but Roman Gushchin added that entry in a separate patchset:
https://lore.kernel.org/all/[email protected]/.

David Vernet (4):
cgroup: Add new test_cpu.c test suite in cgroup selftests
cgroup: Add test_cgcpu_stats() testcase to cgroup cpu selftests
cgroup: Add test_cgcpu_weight_overprovisioned() testcase
cgroup: Add new test_cgcpu_weight_underprovisioned() testcase

tools/testing/selftests/cgroup/.gitignore | 1 +
tools/testing/selftests/cgroup/Makefile | 2 +
tools/testing/selftests/cgroup/cgroup_util.c | 12 +
tools/testing/selftests/cgroup/cgroup_util.h | 4 +
tools/testing/selftests/cgroup/test_cpu.c | 416 +++++++++++++++++++
5 files changed, 435 insertions(+)
create mode 100644 tools/testing/selftests/cgroup/test_cpu.c

--
2.30.2


2022-04-22 19:40:14

by David Vernet

[permalink] [raw]
Subject: Re: [PATCH 0/4] cgroup: Introduce cpu controller test suite

Hi Tejun,

On Thu, Apr 21, 2022 at 12:21:18PM -1000, Tejun Heo wrote:
> * Can you please repost w/ Ingo and Peterz cc'd?

Will do, I'll cc them on v2.

> * Maybe cpucg instead of cgcpu?

Agreed, that seems more intuitive. I'll change it in v2.

> * Single level testing is great but extending the case to cover deeper
> nesting level would be great. ie. a test case with multi level tree w/
> both under and over provisioned parts in the tree.

This sounds like a great idea. I have another patch set that I was planning
to send out which adds a few more testcases. I can include some testcases
in that set which validate more complicated nesting setups.

Thanks,
David

2022-04-22 19:45:33

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH 0/4] cgroup: Introduce cpu controller test suite

Hello,

On Tue, Apr 19, 2022 at 02:32:40PM -0700, David Vernet wrote:
> This patchset introduces a new test_cpu.c test suite as part of
> tools/testing/selftests/cgroup. test_cpu.c will contain testcases that
> validate the cgroup v2 cpu controller.
>
> This patchset only contains testcases that validate cpu.stat and
> cpu.weight, but I'm expecting to send further patchsets after this that
> also include testcases that validate other knobs such as cpu.max.
>
> Note that checkpatch complains about a missing MAINTAINERS file entry for
> [PATCH 1/4], but Roman Gushchin added that entry in a separate patchset:
> https://lore.kernel.org/all/[email protected]/.

This looks great to me. A few small things:

* Can you please repost w/ Ingo and Peterz cc'd?

* Maybe cpucg instead of cgcpu?

* Single level testing is great but extending the case to cover deeper
nesting level would be great. ie. a test case with multi level tree w/
both under and over provisioned parts in the tree.

Thanks.

--
tejun

2022-04-22 21:40:05

by David Vernet

[permalink] [raw]
Subject: [PATCH 4/4] cgroup: Add test_cgcpu_weight_underprovisioned() testcase

test_cpu.c includes testcases that validate the cgroup cpu controller.
This patch adds a new testcase called test_cgcpu_weight_underprovisioned()
that verifies that processes with different cpu.weight that are all running
on an underprovisioned system, still get roughly the same amount of cpu
time.

Because test_cgcpu_weight_underprovisioned() is very similar to
test_cgcpu_weight_overprovisioned(), this patch also pulls the common logic
into a separate helper function that is invoked from both testcases, and
which uses function pointers to invoke the unique portions of the
testcases.

Signed-off-by: David Vernet <[email protected]>
---
tools/testing/selftests/cgroup/test_cpu.c | 149 +++++++++++++++++-----
1 file changed, 114 insertions(+), 35 deletions(-)

diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c
index 2afac9f9e1e2..7adeadba88c4 100644
--- a/tools/testing/selftests/cgroup/test_cpu.c
+++ b/tools/testing/selftests/cgroup/test_cpu.c
@@ -19,6 +19,12 @@ enum hog_clock_type {
CPU_HOG_CLOCK_WALL,
};

+struct cpu_hogger {
+ char *cgroup;
+ pid_t pid;
+ long usage;
+};
+
struct cpu_hog_func_param {
int nprocs;
long runtime_nsec;
@@ -198,31 +204,15 @@ static int test_cgcpu_stats(const char *root)
return ret;
}

-/*
- * First, this test creates the following hierarchy:
- * A
- * A/B cpu.weight = 50
- * A/C cpu.weight = 100
- * A/D cpu.weight = 150
- *
- * A separate process is then created for each child cgroup which spawns as
- * many threads as there are cores, and hogs each CPU as much as possible
- * for some time interval.
- *
- * Once all of the children have exited, we verify that each child cgroup
- * was given proportional runtime as informed by their cpu.weight.
- */
-static int test_cgcpu_weight_overprovisioned(const char *root)
+static int
+run_cgcpu_weight_test(
+ const char *root,
+ pid_t (*spawn_child)(const struct cpu_hogger *child),
+ int (*validate)(const struct cpu_hogger *children, int num_children))
{
- struct child {
- char *cgroup;
- pid_t pid;
- long usage;
- };
int ret = KSFT_FAIL, i;
char *parent = NULL;
- struct child children[3] = {NULL};
- long usage_seconds = 10;
+ struct cpu_hogger children[3] = {NULL};

parent = cg_name(root, "cgcpu_test_0");
if (!parent)
@@ -248,13 +238,7 @@ static int test_cgcpu_weight_overprovisioned(const char *root)
}

for (i = 0; i < ARRAY_SIZE(children); i++) {
- struct cpu_hog_func_param param = {
- .nprocs = get_nprocs(),
- .runtime_nsec = usage_seconds * NSEC_PER_SEC,
- .clock_type = CPU_HOG_CLOCK_WALL,
- };
- pid_t pid = cg_run_nowait(children[i].cgroup, hog_cpus_timed,
- (void *)&param);
+ pid_t pid = spawn_child(&children[i]);
if (pid <= 0)
goto cleanup;
children[i].pid = pid;
@@ -274,7 +258,43 @@ static int test_cgcpu_weight_overprovisioned(const char *root)
children[i].usage = cg_read_key_long(children[i].cgroup,
"cpu.stat", "usage_usec");

- for (i = 0; i < ARRAY_SIZE(children) - 1; i++) {
+ if (validate(children, ARRAY_SIZE(children)))
+ goto cleanup;
+
+ ret = KSFT_PASS;
+cleanup:
+ for (i = 0; i < ARRAY_SIZE(children); i++) {
+ cg_destroy(children[i].cgroup);
+ free(children[i].cgroup);
+ }
+ cg_destroy(parent);
+ free(parent);
+
+ return ret;
+}
+
+static pid_t weight_hog_ncpus(const struct cpu_hogger *child, int ncpus)
+{
+ long usage_seconds = 10;
+ struct cpu_hog_func_param param = {
+ .nprocs = ncpus,
+ .runtime_nsec = usage_seconds * NSEC_PER_SEC,
+ .clock_type = CPU_HOG_CLOCK_WALL,
+ };
+ return cg_run_nowait(child->cgroup, hog_cpus_timed, (void *)&param);
+}
+
+static pid_t weight_hog_all_cpus(const struct cpu_hogger *child)
+{
+ return weight_hog_ncpus(child, get_nprocs());
+}
+
+static int
+overprovision_validate(const struct cpu_hogger *children, int num_children)
+{
+ int ret = KSFT_FAIL, i;
+
+ for (i = 0; i < num_children - 1; i++) {
long delta;

if (children[i + 1].usage <= children[i].usage)
@@ -287,16 +307,74 @@ static int test_cgcpu_weight_overprovisioned(const char *root)

ret = KSFT_PASS;
cleanup:
- for (i = 0; i < ARRAY_SIZE(children); i++) {
- cg_destroy(children[i].cgroup);
- free(children[i].cgroup);
+ return ret;
+}
+
+/*
+ * First, this test creates the following hierarchy:
+ * A
+ * A/B cpu.weight = 50
+ * A/C cpu.weight = 100
+ * A/D cpu.weight = 150
+ *
+ * A separate process is then created for each child cgroup which spawns as
+ * many threads as there are cores, and hogs each CPU as much as possible
+ * for some time interval.
+ *
+ * Once all of the children have exited, we verify that each child cgroup
+ * was given proportional runtime as informed by their cpu.weight.
+ */
+static int test_cgcpu_weight_overprovisioned(const char *root)
+{
+ return run_cgcpu_weight_test(root, weight_hog_all_cpus,
+ overprovision_validate);
+}
+
+static pid_t weight_hog_one_cpu(const struct cpu_hogger *child)
+{
+ return weight_hog_ncpus(child, 1);
+}
+
+static int
+underprovision_validate(const struct cpu_hogger *children, int num_children)
+{
+ int ret = KSFT_FAIL, i;
+
+ for (i = 0; i < num_children - 1; i++) {
+ if (!values_close(children[i + 1].usage, children[0].usage, 15))
+ goto cleanup;
}
- cg_destroy(parent);
- free(parent);

+ ret = KSFT_PASS;
+cleanup:
return ret;
}

+/*
+ * First, this test creates the following hierarchy:
+ * A
+ * A/B cpu.weight = 50
+ * A/C cpu.weight = 100
+ * A/D cpu.weight = 150
+ *
+ * A separate process is then created for each child cgroup which spawns a
+ * single thread that hogs a CPU. The testcase is only run on systems that
+ * have at least one core per-thread in the child processes.
+ *
+ * Once all of the children have exited, we verify that each child cgroup
+ * had roughly the same runtime despite having different cpu.weight.
+ */
+static int test_cgcpu_weight_underprovisioned(const char *root)
+{
+ // Only run the test if there are enough cores to avoid overprovisioning
+ // the system.
+ if (get_nprocs() < 4)
+ return KSFT_SKIP;
+
+ return run_cgcpu_weight_test(root, weight_hog_one_cpu,
+ underprovision_validate);
+}
+
#define T(x) { x, #x }
struct cgcpu_test {
int (*fn)(const char *root);
@@ -305,6 +383,7 @@ struct cgcpu_test {
T(test_cgcpu_subtree_control),
T(test_cgcpu_stats),
T(test_cgcpu_weight_overprovisioned),
+ T(test_cgcpu_weight_underprovisioned),
};
#undef T

--
2.30.2