All of lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [PATCH 0/3] Add CPU cgroup v2 tests ported from kselftest
@ 2026-07-30  3:28 Shaojie Sun
  2026-07-30  3:28 ` [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Shaojie Sun @ 2026-07-30  3:28 UTC (permalink / raw)
  To: ltp

This series ports the CPU cgroup v2 tests from the kernel selftests
(tools/testing/selftests/cgroup/test_cpu.c) to LTP, including tests
for subtree control, cpu.stat, nice, weight, and cpu.max.

Patch 1 adds the necessary cgroup file entries (cpu.stat, cpu.weight)
to the LTP cgroup library.
Patch 2 adds the test source files and build infrastructure.
Patch 3 registers the new tests in runtest/controllers.

Shaojie Sun (3):
  tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  cgroup: add CPU cgroup v2 tests ported from kselftest
  runtest/controllers: add CPU cgroup v2 test entries

 lib/tst_cgroup.c                              |   2 +
 runtest/controllers                           |   5 +
 testcases/kernel/controllers/cpu/.gitignore   |   5 +
 testcases/kernel/controllers/cpu/Makefile     |   9 +
 .../kernel/controllers/cpu/cgroup_cpu01.c     |  65 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 104 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 116 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 346 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 175 +++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 130 +++++++
 10 files changed, 957 insertions(+)
 create mode 100644 testcases/kernel/controllers/cpu/.gitignore
 create mode 100644 testcases/kernel/controllers/cpu/Makefile
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu01.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu02.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu03.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu04.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu05.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu_common.h

--
2.25.1

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  2026-07-30  3:28 [LTP] [PATCH 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-30  3:28 ` Shaojie Sun
  2026-07-30  7:04   ` [LTP] " linuxtestproject.agent
  2026-07-30  3:28 ` [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-30  3:28 ` [LTP] [PATCH 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  2 siblings, 1 reply; 7+ messages in thread
From: Shaojie Sun @ 2026-07-30  3:28 UTC (permalink / raw)
  To: ltp

These entries are needed by the upcoming CPU cgroup v2 tests
(cgroup_cpu01 through cgroup_cpu05) to read cpu.stat and set
cpu.weight on cgroups.

Signed-off-by: Shaojie Sun <sunshaojie@kylinos.cn>
---
 lib/tst_cgroup.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/tst_cgroup.c b/lib/tst_cgroup.c
index 7c6ecd4d2..8a27ce287 100644
--- a/lib/tst_cgroup.c
+++ b/lib/tst_cgroup.c
@@ -199,6 +199,8 @@ static const struct cgroup_file cpu_ctrl_files[] = {
 	 */
 	{ "cpu.max", "cpu.cfs_quota_us", CTRL_CPU },
 	{ "cpu.cfs_period_us", "cpu.cfs_period_us", CTRL_CPU },
+	{ "cpu.stat", NULL, CTRL_CPU },
+	{ "cpu.weight", NULL, CTRL_CPU },
 	{ }
 };
 
-- 
2.25.1


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest
  2026-07-30  3:28 [LTP] [PATCH 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-30  3:28 ` [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
@ 2026-07-30  3:28 ` Shaojie Sun
  2026-07-30  6:38   ` Andrea Cervesato via ltp
  2026-07-30  3:28 ` [LTP] [PATCH 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  2 siblings, 1 reply; 7+ messages in thread
From: Shaojie Sun @ 2026-07-30  3:28 UTC (permalink / raw)
  To: ltp

Port the following kselftest CPU cgroup v2 tests to LTP:
  - test_cpucg_subtree_control (cgroup_cpu01)
  - test_cpucg_stats (cgroup_cpu02)
  - test_cpucg_nice (cgroup_cpu03)
  - test_cpucg_weight_overprovisioned (cgroup_cpu04)
  - test_cpucg_weight_underprovisioned (cgroup_cpu04)
  - test_cpucg_nested_weight_overprovisioned (cgroup_cpu04)
  - test_cpucg_nested_weight_underprovisioned (cgroup_cpu04)
  - test_cpucg_max (cgroup_cpu05)
  - test_cpucg_max_nested (cgroup_cpu05)

Converted from tools/testing/selftests/cgroup/test_cpu.c.

Signed-off-by: Shaojie Sun <sunshaojie@kylinos.cn>
---
 testcases/kernel/controllers/cpu/.gitignore   |   5 +
 testcases/kernel/controllers/cpu/Makefile     |   9 +
 .../kernel/controllers/cpu/cgroup_cpu01.c     |  65 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 104 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 116 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 346 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 175 +++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 130 +++++++
 8 files changed, 950 insertions(+)
 create mode 100644 testcases/kernel/controllers/cpu/.gitignore
 create mode 100644 testcases/kernel/controllers/cpu/Makefile
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu01.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu02.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu03.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu04.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu05.c
 create mode 100644 testcases/kernel/controllers/cpu/cgroup_cpu_common.h

diff --git a/testcases/kernel/controllers/cpu/.gitignore b/testcases/kernel/controllers/cpu/.gitignore
new file mode 100644
index 000000000..1a81d332a
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/.gitignore
@@ -0,0 +1,5 @@
+/cgroup_cpu01
+/cgroup_cpu02
+/cgroup_cpu03
+/cgroup_cpu04
+/cgroup_cpu05
diff --git a/testcases/kernel/controllers/cpu/Makefile b/testcases/kernel/controllers/cpu/Makefile
new file mode 100644
index 000000000..d1fbcba3e
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+LDLIBS			+= -lpthread
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu01.c b/testcases/kernel/controllers/cpu/cgroup_cpu01.c
new file mode 100644
index 000000000..b8fd1d770
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu01.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_subtree_control from
+ * tools/testing/selftests/cgroup/test_cpu.c.
+ *
+ * This test creates two nested cgroups with and without enabling
+ * the cpu controller.
+ *
+ * The LTP API automatically adds controllers to subtree_control when
+ * a child cgroup is added. So unlike the kselftest we remove the
+ * controller after it being added automatically.
+ */
+
+#define _GNU_SOURCE
+#include "tst_test.h"
+
+static struct tst_cg_group *cg_parent_0, *cg_child_0;
+static struct tst_cg_group *cg_parent_1, *cg_child_1;
+
+static void test_cpucg_subtree_control(void)
+{
+	cg_parent_0 = tst_cg_group_mk(tst_cg, "cpucg_test_0");
+	cg_child_0 = tst_cg_group_mk(cg_parent_0, "cpucg_child_0");
+
+	cg_parent_1 = tst_cg_group_mk(tst_cg, "cpucg_test_1");
+	cg_child_1 = tst_cg_group_mk(cg_parent_1, "cpucg_child_1");
+	SAFE_CG_PRINT(cg_parent_1, "cgroup.subtree_control", "-cpu");
+
+	TST_EXP_POSITIVE(
+		SAFE_CG_OCCURSIN(cg_child_0, "cgroup.controllers", "cpu"),
+		"child with cpu enabled should have cpu controller");
+
+	TST_EXP_POSITIVE(
+		!SAFE_CG_OCCURSIN(cg_child_1, "cgroup.controllers", "cpu"),
+		"child without cpu enabled should not have cpu controller");
+
+	cg_child_1 = tst_cg_group_rm(cg_child_1);
+	cg_parent_1 = tst_cg_group_rm(cg_parent_1);
+	cg_child_0 = tst_cg_group_rm(cg_child_0);
+	cg_parent_0 = tst_cg_group_rm(cg_parent_0);
+}
+
+static void cleanup(void)
+{
+	if (cg_child_1)
+		cg_child_1 = tst_cg_group_rm(cg_child_1);
+	if (cg_parent_1)
+		cg_parent_1 = tst_cg_group_rm(cg_parent_1);
+	if (cg_child_0)
+		cg_child_0 = tst_cg_group_rm(cg_child_0);
+	if (cg_parent_0)
+		cg_parent_0 = tst_cg_group_rm(cg_parent_0);
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test_all = test_cpucg_subtree_control,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+	.needs_cgroup_nsdelegate = 0,
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu02.c b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
new file mode 100644
index 000000000..55b076ddd
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_stats from
+ * tools/testing/selftests/cgroup/test_cpu.c.
+ *
+ * Creates a cpu cgroup, burns a CPU for a few quanta, and verifies that
+ * cpu.stat shows the expected output.
+ */
+
+#define _GNU_SOURCE
+#include <time.h>
+#include <sys/wait.h>
+
+#include "tst_test.h"
+#include "cgroup_cpu_common.h"
+
+#define USEC_PER_SEC 1000000L
+
+static struct tst_cg_group *cg_test;
+
+static void test_cpucg_stats(void)
+{
+	long usage_usec, user_usec, system_usec;
+	long usage_seconds = 2;
+	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
+	int status;
+
+	cg_test = tst_cg_group_mk(tst_cg, "cpucg_test");
+
+	SAFE_CG_LINES_SCANF(cg_test, "cpu.stat",
+			    "usage_usec %ld", &usage_usec);
+	SAFE_CG_LINES_SCANF(cg_test, "cpu.stat",
+			    "user_usec %ld", &user_usec);
+	SAFE_CG_LINES_SCANF(cg_test, "cpu.stat",
+			    "system_usec %ld", &system_usec);
+
+	if (usage_usec != 0 || user_usec != 0 || system_usec != 0) {
+		tst_res(TFAIL,
+			"Initial cpu.stat values not zero: usage=%ld user=%ld system=%ld",
+			usage_usec, user_usec, system_usec);
+		goto cleanup;
+	}
+
+	if (!SAFE_FORK()) {
+		struct cpu_hog_func_param param = {
+			.nprocs = 1,
+			.ts = {
+				.tv_sec = usage_seconds,
+				.tv_nsec = 0,
+			},
+			.clock_type = CPU_HOG_CLOCK_PROCESS,
+		};
+
+		SAFE_CG_PRINTF(cg_test, "cgroup.procs", "%d", getpid());
+		exit(hog_cpus_timed(&param));
+	}
+
+	SAFE_WAITPID(-1, &status, 0);
+
+	SAFE_CG_LINES_SCANF(cg_test, "cpu.stat",
+			    "usage_usec %ld", &usage_usec);
+	SAFE_CG_LINES_SCANF(cg_test, "cpu.stat",
+			    "user_usec %ld", &user_usec);
+
+	if (user_usec <= 0) {
+		tst_res(TFAIL, "user_usec=%ld should be > 0 after CPU hog",
+			user_usec);
+		goto cleanup;
+	}
+
+	if (!values_close(usage_usec, expected_usage_usec, 1)) {
+		tst_res(TFAIL,
+			"usage_usec=%ld should be close to expected=%ld (diff > 1%%)",
+			usage_usec, expected_usage_usec);
+		goto cleanup;
+	}
+
+	tst_res(TPASS, "cpu.stat values valid: usage=%ld user=%ld",
+		usage_usec, user_usec);
+
+cleanup:
+	cg_test = tst_cg_group_rm(cg_test);
+}
+
+static void cleanup(void)
+{
+	if (cg_test) {
+		SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+		cg_test = tst_cg_group_rm(cg_test);
+	}
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test_all = test_cpucg_stats,
+	.forks_child = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+	.needs_cgroup_nsdelegate = 0,
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu03.c b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
new file mode 100644
index 000000000..64175842a
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_nice from
+ * tools/testing/selftests/cgroup/test_cpu.c.
+ *
+ * Creates a nice process that consumes CPU and checks that the elapsed
+ * nice time in the cgroup is close to the expected time.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "cgroup_cpu_common.h"
+
+#define USEC_PER_SEC 1000000L
+
+static struct tst_cg_group *cg_child;
+
+static long read_nice_usec(void)
+{
+	char buf[256];
+	long val = -1;
+	char *line, *saveptr;
+
+	SAFE_CG_READ(cg_child, "cpu.stat", buf, sizeof(buf));
+
+	line = strtok_r(buf, "\n", &saveptr);
+	while (line) {
+		if (sscanf(line, "nice_usec %ld", &val) == 1)
+			return val;
+		line = strtok_r(NULL, "\n", &saveptr);
+	}
+
+	return -1;
+}
+
+static void test_cpucg_nice(void)
+{
+	long user_usec, nice_usec;
+	long usage_seconds = 2;
+	long expected_nice_usec = usage_seconds * USEC_PER_SEC;
+	int status;
+
+	cg_child = tst_cg_group_mk(tst_cg, "cpucg_test");
+
+	SAFE_CG_LINES_SCANF(cg_child, "cpu.stat",
+			    "user_usec %ld", &user_usec);
+
+	nice_usec = read_nice_usec();
+	if (nice_usec < 0)
+		tst_brk(TCONF, "nice_usec not available in cpu.stat");
+
+	if (user_usec != 0 || nice_usec != 0) {
+		tst_res(TFAIL,
+			"Initial cpu.stat values not zero: user=%ld nice=%ld", user_usec, nice_usec);
+		goto cleanup;
+	}
+
+	if (!SAFE_FORK()) {
+		struct cpu_hog_func_param param = {
+			.nprocs = 1,
+			.ts = {
+				.tv_sec = usage_seconds,
+				.tv_nsec = 0,
+			},
+			.clock_type = CPU_HOG_CLOCK_PROCESS,
+		};
+		SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
+
+		if (nice(1) == -1 && errno != 0)
+			exit(1);
+		exit(hog_cpus_timed(&param));
+	}
+
+	SAFE_WAITPID(-1, &status, 0);
+
+	nice_usec = read_nice_usec();
+
+	if (!values_close(nice_usec, expected_nice_usec, 1)) {
+		tst_res(TFAIL,
+			"nice_usec=%ld should be close to expected=%ld",
+			nice_usec, expected_nice_usec);
+		goto cleanup;
+	}
+
+	tst_res(TPASS, "nice_usec=%ld is close to expected=%ld",
+		nice_usec, expected_nice_usec);
+
+cleanup:
+	cg_child = tst_cg_group_rm(cg_child);
+}
+
+static void cleanup(void)
+{
+	if (cg_child) {
+		SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+		cg_child = tst_cg_group_rm(cg_child);
+	}
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test_all = test_cpucg_nice,
+	.forks_child = 1,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+	.needs_cgroup_nsdelegate = 0,
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu04.c b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
new file mode 100644
index 000000000..c45097f86
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest cpu weight tests from
+ * tools/testing/selftests/cgroup/test_cpu.c.
+ *
+ * Includes:
+ * - test_cpucg_weight_overprovisioned
+ * - test_cpucg_weight_underprovisioned
+ * - test_cpucg_nested_weight_overprovisioned
+ * - test_cpucg_nested_weight_underprovisioned
+ */
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "cgroup_cpu_common.h"
+
+#define USEC_PER_SEC 1000000L
+#define HOG_SECONDS 10
+
+static struct tst_cg_group *cg_parent;
+static struct tst_cg_group *cg_child;
+static struct tst_cg_group *cg_leaf[3];
+
+static pid_t fork_hog_child(struct tst_cg_group *cg, int nprocs,
+			     enum hog_clock_type clock_type)
+{
+	pid_t pid;
+
+	pid = SAFE_FORK();
+	if (pid == 0) {
+		struct cpu_hog_func_param param = {
+			.nprocs = nprocs,
+			.ts = {
+				.tv_sec = HOG_SECONDS,
+				.tv_nsec = 0,
+			},
+			.clock_type = clock_type,
+		};
+
+		SAFE_CG_PRINTF(cg, "cgroup.procs", "%d", getpid());
+		exit(hog_cpus_timed(&param));
+	}
+
+	return pid;
+}
+
+static void wait_children(pid_t *pids, int n)
+{
+	int i, status;
+
+	for (i = 0; i < n; i++)
+		SAFE_WAITPID(pids[i], &status, 0);
+}
+
+static void test_cpucg_weight_overprovisioned(void)
+{
+	int i, nprocs;
+	pid_t pids[3];
+	long usage[3];
+	long delta;
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_test_0");
+
+	for (i = 0; i < 3; i++) {
+		cg_leaf[i] = tst_cg_group_mk(cg_parent, "cpucg_child_%d", i);
+		SAFE_CG_PRINTF(cg_leaf[i], "cpu.weight", "%d", 50 * (i + 1));
+	}
+
+	nprocs = tst_ncpus_available();
+	for (i = 0; i < 3; i++)
+		pids[i] = fork_hog_child(cg_leaf[i], nprocs,
+					 CPU_HOG_CLOCK_WALL);
+
+	wait_children(pids, 3);
+
+	for (i = 0; i < 3; i++) {
+		SAFE_CG_LINES_SCANF(cg_leaf[i], "cpu.stat",
+				    "usage_usec %ld", &usage[i]);
+	}
+
+	if (usage[1] <= usage[0] || usage[2] <= usage[1]) {
+		tst_res(TFAIL,
+			"CPU usage not monotonic with weight: %ld %ld %ld", usage[0], usage[1], usage[2]);
+		goto cleanup;
+	}
+
+	for (i = 0; i < 2; i++) {
+		delta = usage[i + 1] - usage[i];
+		if (!values_close(delta, usage[0], 35)) {
+			tst_res(TFAIL,
+				"weight delta %ld not close to base %ld (idx=%d)",
+				delta, usage[0], i);
+			goto cleanup;
+		}
+	}
+
+	tst_res(TPASS, "weight overprovisioned: usage %ld %ld %ld",
+		usage[0], usage[1], usage[2]);
+
+cleanup:
+	for (i = 2; i >= 0; i--) {
+		if (cg_leaf[i]) {
+			SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs",
+				       "%d", getpid());
+			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
+		}
+	}
+	cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void test_cpucg_weight_underprovisioned(void)
+{
+	int i;
+	pid_t pids[3];
+	long usage[3];
+
+	if (tst_ncpus_available() < 4) {
+		tst_res(TCONF, "Need at least 4 CPUs for underprovisioned test");
+		return;
+	}
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_test_1");
+
+	for (i = 0; i < 3; i++) {
+		cg_leaf[i] = tst_cg_group_mk(cg_parent, "cpucg_child_%d", i);
+		SAFE_CG_PRINTF(cg_leaf[i], "cpu.weight", "%d", 50 * (i + 1));
+	}
+
+	for (i = 0; i < 3; i++)
+		pids[i] = fork_hog_child(cg_leaf[i], 1, CPU_HOG_CLOCK_WALL);
+
+	wait_children(pids, 3);
+
+	for (i = 0; i < 3; i++) {
+		SAFE_CG_LINES_SCANF(cg_leaf[i], "cpu.stat",
+				    "usage_usec %ld", &usage[i]);
+	}
+
+	if (!values_close(usage[0], usage[1], 15) ||
+	    !values_close(usage[0], usage[2], 15)) {
+		tst_res(TFAIL,
+			"Underprovisioned usages not equal: %ld %ld %ld", usage[0], usage[1], usage[2]);
+		goto cleanup;
+	}
+
+	tst_res(TPASS, "weight underprovisioned: usage %ld %ld %ld",
+		usage[0], usage[1], usage[2]);
+
+cleanup:
+	for (i = 2; i >= 0; i--) {
+		if (cg_leaf[i]) {
+			SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs",
+				       "%d", getpid());
+			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
+		}
+	}
+	cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void test_cpucg_nested_weight_overprovisioned(void)
+{
+	int i, nprocs;
+	pid_t pids[3];
+	long usage[3], nested_leaf_usage, child_usage;
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_nested_test");
+	cg_child = tst_cg_group_mk(cg_parent, "cpucg_child");
+
+	SAFE_CG_PRINT(cg_child, "cpu.weight", "1000");
+
+	cg_leaf[0] = tst_cg_group_mk(cg_parent, "cpucg_leaf_0");
+	SAFE_CG_PRINT(cg_leaf[0], "cpu.weight", "1000");
+
+	for (i = 1; i < 3; i++) {
+		cg_leaf[i] = tst_cg_group_mk(cg_child, "cpucg_leaf_%d", i);
+		SAFE_CG_PRINT(cg_leaf[i], "cpu.weight", "5000");
+	}
+
+	nprocs = tst_ncpus_available();
+	for (i = 0; i < 3; i++)
+		pids[i] = fork_hog_child(cg_leaf[i], nprocs,
+					 CPU_HOG_CLOCK_WALL);
+
+	wait_children(pids, 3);
+
+	for (i = 0; i < 3; i++) {
+		SAFE_CG_LINES_SCANF(cg_leaf[i], "cpu.stat",
+				    "usage_usec %ld", &usage[i]);
+	}
+
+	nested_leaf_usage = usage[1] + usage[2];
+	if (!values_close(usage[0], nested_leaf_usage, 15)) {
+		tst_res(TFAIL,
+			"Nested weight over: leaf0=%ld not close to leaf1+leaf2=%ld", usage[0], nested_leaf_usage);
+		goto cleanup;
+	}
+
+	SAFE_CG_LINES_SCANF(cg_child, "cpu.stat",
+			    "usage_usec %ld", &child_usage);
+	if (!values_close(child_usage, nested_leaf_usage, 1)) {
+		tst_res(TFAIL,
+			"Child usage=%ld not close to nested leaf usage=%ld",
+			child_usage, nested_leaf_usage);
+		goto cleanup;
+	}
+
+	tst_res(TPASS,
+		"nested weight overprovisioned: leaf0=%ld leaf1+leaf2=%ld",
+		usage[0], nested_leaf_usage);
+
+cleanup:
+	SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+	for (i = 2; i >= 0; i--) {
+		if (cg_leaf[i])
+			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
+	}
+	if (cg_child)
+		cg_child = tst_cg_group_rm(cg_child);
+	if (cg_parent)
+		cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void test_cpucg_nested_weight_underprovisioned(void)
+{
+	int i, nprocs;
+	pid_t pids[3];
+	long usage[3], nested_leaf_usage, child_usage;
+
+	if (tst_ncpus_available() < 4) {
+		tst_res(TCONF,
+			"Need at least 4 CPUs for nested underprovisioned test");
+		return;
+	}
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_nested_test_2");
+	cg_child = tst_cg_group_mk(cg_parent, "cpucg_child");
+
+	SAFE_CG_PRINT(cg_child, "cpu.weight", "1000");
+
+	cg_leaf[0] = tst_cg_group_mk(cg_parent, "cpucg_leaf_0");
+	SAFE_CG_PRINT(cg_leaf[0], "cpu.weight", "1000");
+
+	for (i = 1; i < 3; i++) {
+		cg_leaf[i] = tst_cg_group_mk(cg_child, "cpucg_leaf_%d", i);
+		SAFE_CG_PRINT(cg_leaf[i], "cpu.weight", "5000");
+	}
+
+	nprocs = tst_ncpus_available() / 4;
+	if (nprocs < 1)
+		nprocs = 1;
+
+	for (i = 0; i < 3; i++)
+		pids[i] = fork_hog_child(cg_leaf[i], nprocs,
+					 CPU_HOG_CLOCK_WALL);
+
+	wait_children(pids, 3);
+
+	for (i = 0; i < 3; i++) {
+		SAFE_CG_LINES_SCANF(cg_leaf[i], "cpu.stat",
+				    "usage_usec %ld", &usage[i]);
+	}
+
+	nested_leaf_usage = usage[1] + usage[2];
+	if (!values_close(usage[0] * 2, nested_leaf_usage, 15)) {
+		tst_res(TFAIL,
+			"Nested weight under: leaf0=%ld leaf1+leaf2=%ld, leaf0*2=%ld not close to leaf1+leaf2",
+			usage[0], nested_leaf_usage, usage[0] * 2);
+		goto cleanup;
+	}
+
+	SAFE_CG_LINES_SCANF(cg_child, "cpu.stat",
+			    "usage_usec %ld", &child_usage);
+	if (!values_close(child_usage, nested_leaf_usage, 1)) {
+		tst_res(TFAIL,
+			"Child usage=%ld not close to nested leaf usage=%ld",
+			child_usage, nested_leaf_usage);
+		goto cleanup;
+	}
+
+	tst_res(TPASS,
+		"nested weight underprovisioned: leaf0=%ld leaf1+leaf2=%ld",
+		usage[0], nested_leaf_usage);
+
+cleanup:
+	SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+	for (i = 2; i >= 0; i--) {
+		if (cg_leaf[i])
+			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
+	}
+	if (cg_child)
+		cg_child = tst_cg_group_rm(cg_child);
+	if (cg_parent)
+		cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void run(unsigned int n)
+{
+	switch (n) {
+	case 0:
+		test_cpucg_weight_overprovisioned();
+		break;
+	case 1:
+		test_cpucg_weight_underprovisioned();
+		break;
+	case 2:
+		test_cpucg_nested_weight_overprovisioned();
+		break;
+	case 3:
+		test_cpucg_nested_weight_underprovisioned();
+		break;
+	}
+}
+
+static void cleanup(void)
+{
+	int i;
+
+	SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+	for (i = 2; i >= 0; i--) {
+		if (cg_leaf[i])
+			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
+	}
+	if (cg_child)
+		cg_child = tst_cg_group_rm(cg_child);
+	if (cg_parent)
+		cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test = run,
+	.tcnt = 4,
+	.forks_child = 1,
+	.timeout = 60,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+	.needs_cgroup_nsdelegate = 0,
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu05.c b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
new file mode 100644
index 000000000..8da943777
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest cpu.max tests from
+ * tools/testing/selftests/cgroup/test_cpu.c.
+ *
+ * Includes:
+ * - test_cpucg_max: creates a cgroup with cpu.max quota and verifies
+ *   a process is properly throttled.
+ * - test_cpucg_max_nested: verifies a process inside a nested cgroup
+ *   whose parent has cpu.max set is properly throttled.
+ */
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "cgroup_cpu_common.h"
+
+#define USEC_PER_SEC 1000000L
+
+static struct tst_cg_group *cg_parent, *cg_child;
+
+static void test_cpucg_max(void)
+{
+	long quota_usec = 1000;
+	long period_usec = 100000;
+	long duration_seconds = 1;
+	long usage_usec;
+	long n_periods, remainder_usec, expected_usage_usec;
+	long duration_usec = duration_seconds * USEC_PER_SEC;
+	int status;
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_max_test");
+
+	SAFE_CG_PRINTF(cg_parent, "cpu.max", "%ld %ld", quota_usec, period_usec);
+
+	if (!SAFE_FORK()) {
+		struct cpu_hog_func_param param = {
+			.nprocs = 1,
+			.ts = {
+				.tv_sec = duration_seconds,
+				.tv_nsec = 0,
+			},
+			.clock_type = CPU_HOG_CLOCK_WALL,
+		};
+
+		SAFE_CG_PRINTF(cg_parent, "cgroup.procs", "%d", getpid());
+		exit(hog_cpus_timed(&param));
+	}
+
+	SAFE_WAITPID(-1, &status, 0);
+
+	SAFE_CG_LINES_SCANF(cg_parent, "cpu.stat",
+			    "usage_usec %ld", &usage_usec);
+
+	/*
+	 * The following calculation applies only since
+	 * the cpu hog is set to run as per wall-clock time
+	 */
+	n_periods = duration_usec / period_usec;
+	remainder_usec = duration_usec - n_periods * period_usec;
+	expected_usage_usec = n_periods * quota_usec +
+			      MIN(remainder_usec, quota_usec);
+
+	if (!values_close(usage_usec, expected_usage_usec, 10)) {
+		tst_res(TFAIL,
+			"cpu.max: usage=%ld expected=%ld",
+			usage_usec, expected_usage_usec);
+		goto cleanup;
+	}
+
+	tst_res(TPASS, "cpu.max: usage=%ld close to expected=%ld",
+		usage_usec, expected_usage_usec);
+
+cleanup:
+	cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void test_cpucg_max_nested(void)
+{
+	long quota_usec = 1000;
+	long period_usec = 100000;
+	long duration_seconds = 1;
+	long usage_usec;
+	long n_periods, remainder_usec, expected_usage_usec;
+	long duration_usec = duration_seconds * USEC_PER_SEC;
+	int status;
+
+	cg_parent = tst_cg_group_mk(tst_cg, "cpucg_max_parent");
+	cg_child = tst_cg_group_mk(cg_parent, "cpucg_max_child");
+
+	SAFE_CG_PRINTF(cg_parent, "cpu.max", "%ld %ld", quota_usec, period_usec);
+
+	if (!SAFE_FORK()) {
+		struct cpu_hog_func_param param = {
+			.nprocs = 1,
+			.ts = {
+				.tv_sec = duration_seconds,
+				.tv_nsec = 0,
+			},
+			.clock_type = CPU_HOG_CLOCK_WALL,
+		};
+
+		SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
+		exit(hog_cpus_timed(&param));
+	}
+
+	SAFE_WAITPID(-1, &status, 0);
+
+	SAFE_CG_LINES_SCANF(cg_child, "cpu.stat",
+			    "usage_usec %ld", &usage_usec);
+
+	/*
+	 * The following calculation applies only since
+	 * the cpu hog is set to run as per wall-clock time
+	 */
+	n_periods = duration_usec / period_usec;
+	remainder_usec = duration_usec - n_periods * period_usec;
+	expected_usage_usec = n_periods * quota_usec +
+			      MIN(remainder_usec, quota_usec);
+
+	if (!values_close(usage_usec, expected_usage_usec, 10)) {
+		tst_res(TFAIL,
+			"cpu.max nested: usage=%ld expected=%ld",
+			usage_usec, expected_usage_usec);
+		goto cleanup;
+	}
+
+	tst_res(TPASS, "cpu.max nested: usage=%ld close to expected=%ld",
+		usage_usec, expected_usage_usec);
+
+cleanup:
+	if (cg_child)
+		cg_child = tst_cg_group_rm(cg_child);
+	if (cg_parent)
+		cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static void run(unsigned int n)
+{
+	switch (n) {
+	case 0:
+		test_cpucg_max();
+		break;
+	case 1:
+		test_cpucg_max_nested();
+		break;
+	}
+}
+
+static void cleanup(void)
+{
+	SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
+	if (cg_child)
+		cg_child = tst_cg_group_rm(cg_child);
+	if (cg_parent)
+		cg_parent = tst_cg_group_rm(cg_parent);
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test = run,
+	.tcnt = 2,
+	.forks_child = 1,
+	.timeout = 30,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+	.needs_cgroup_nsdelegate = 0,
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu_common.h b/testcases/kernel/controllers/cpu/cgroup_cpu_common.h
new file mode 100644
index 000000000..a957aa409
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu_common.h
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+/*\
+ * Common helpers for cgroup cpu controller tests.
+ *
+ * Converted from kselftest
+ * tools/testing/selftests/cgroup/test_cpu.c
+ */
+
+#ifndef CGROUP_CPU_COMMON_H__
+#define CGROUP_CPU_COMMON_H__
+
+#include <pthread.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include "tst_test.h"
+
+#ifndef NSEC_PER_SEC
+#define NSEC_PER_SEC 1000000000L
+#endif
+
+enum hog_clock_type {
+	CPU_HOG_CLOCK_PROCESS,
+	CPU_HOG_CLOCK_WALL,
+};
+
+struct cpu_hog_func_param {
+	int nprocs;
+	struct timespec ts;
+	enum hog_clock_type clock_type;
+};
+
+static inline void *hog_cpu_thread_func(void *arg)
+{
+	(void)arg;
+
+	while (1)
+		;
+
+	return NULL;
+}
+
+static inline 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;
+}
+
+/*
+ * CPU hog function that creates nprocs spinning threads.
+ * To be called in a forked child process already in the target cgroup.
+ * Returns 0 on success, -1 on error.
+ */
+static inline int hog_cpus_timed(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;
+	struct timespec ts_start;
+	struct timespec ts_total;
+	int ret, i;
+	pthread_t tid;
+
+	ret = clock_gettime(CLOCK_MONOTONIC, &ts_start);
+	if (ret != 0)
+		return -1;
+
+	for (i = 0; i < param->nprocs; i++) {
+		ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
+		if (ret != 0)
+			return -1;
+	}
+
+	while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
+		ret = nanosleep(&ts_remaining, NULL);
+		if (ret && errno != EINTR)
+			return -1;
+
+		if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
+			ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID,
+					    &ts_total);
+			if (ret != 0)
+				return -1;
+		} else {
+			struct timespec ts_current;
+
+			ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
+			if (ret != 0)
+				return -1;
+
+			ts_total = timespec_sub(&ts_current, &ts_start);
+		}
+
+		ts_remaining = timespec_sub(&ts_run, &ts_total);
+	}
+
+	return 0;
+}
+
+/*
+ * Check if two values differ by less than err percent.
+ */
+static inline int values_close(long a, long b, int err)
+{
+	return labs(a - b) <= ((a + b) / 100) * err;
+}
+
+#endif /* CGROUP_CPU_COMMON_H__ */
-- 
2.25.1


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [LTP] [PATCH 3/3] runtest/controllers: add CPU cgroup v2 test entries
  2026-07-30  3:28 [LTP] [PATCH 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-30  3:28 ` [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
  2026-07-30  3:28 ` [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-30  3:28 ` Shaojie Sun
  2 siblings, 0 replies; 7+ messages in thread
From: Shaojie Sun @ 2026-07-30  3:28 UTC (permalink / raw)
  To: ltp

Register the new cgroup_cpu01 through cgroup_cpu05 tests in the
controllers runtest file so they are included in test runs.

Signed-off-by: Shaojie Sun <sunshaojie@kylinos.cn>
---
 runtest/controllers | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/runtest/controllers b/runtest/controllers
index 93c52c439..e98ad98cc 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -20,6 +20,11 @@ memcg_stress		memcg_stress_test.sh
 memcg_control		memcg_control_test.sh
 
 # kselftest ports
+cgroup_cpu01 cgroup_cpu01
+cgroup_cpu02 cgroup_cpu02
+cgroup_cpu03 cgroup_cpu03
+cgroup_cpu04 cgroup_cpu04
+cgroup_cpu05 cgroup_cpu05
 memcontrol01 memcontrol01
 memcontrol02 memcontrol02
 memcontrol03 memcontrol03
-- 
2.25.1


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest
  2026-07-30  3:28 ` [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-30  6:38   ` Andrea Cervesato via ltp
  0 siblings, 0 replies; 7+ messages in thread
From: Andrea Cervesato via ltp @ 2026-07-30  6:38 UTC (permalink / raw)
  To: Shaojie Sun; +Cc: ltp

Hi Shaojie,

> + * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>

even before starting the review, why Richie is mentioned as the
developer of these tests?

Also, I would be really careful to convert tests from kselftests
to LTP API. We did that in the past and often it might require
more maintenance than we really need while using cgroups tests
from kselftests.

Is there a reason for this?

Regards,
--
Andrea Cervesato
SUSE QE Automation Engineer Linux
andrea.cervesato@suse.com

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [LTP] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  2026-07-30  3:28 ` [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
@ 2026-07-30  7:04   ` linuxtestproject.agent
  0 siblings, 0 replies; 7+ messages in thread
From: linuxtestproject.agent @ 2026-07-30  7:04 UTC (permalink / raw)
  To: Shaojie Sun; +Cc: ltp

Hi Shaojie,

On Thu, Jul 30, 2026 at 11:28:48 AM +0800, Shaojie Sun wrote:
> tst_cgroup: add cpu.stat and cpu.weight to cgroup file table

--- [PATCH 1/3] ---

>  	{ "cpu.max", "cpu.cfs_quota_us", CTRL_CPU },
>  	{ "cpu.cfs_period_us", "cpu.cfs_period_us", CTRL_CPU },
> +	{ "cpu.stat", NULL, CTRL_CPU },
> +	{ "cpu.weight", NULL, CTRL_CPU },

A NULL file_name_v1 means "item is V2 only", and cgroup_file_alias()
returns that NULL for TST_CG_V1 roots, so safe_cg_has()/safe_cg_read()
will report the file as absent on a v1 hierarchy.

cpu.stat does exist on v1 though: kernel/sched/core.c has
{ .name = "stat", .seq_show = cpu_cfs_stat_show } in cpu_legacy_files[]
under CONFIG_CFS_BANDWIDTH.

Should this be { "cpu.stat", "cpu.stat", CTRL_CPU }, following the
memory.stat entry?

cpu.weight -> NULL looks right, since the v1 counterpart cpu.shares uses
a different scale and cannot be aliased.

--- [PATCH 2/3] ---

> +	if (!SAFE_FORK()) {
> +		struct cpu_hog_func_param param = {
> +			.nprocs = 1,
> +			.ts = {
> +				.tv_sec = usage_seconds,
> +				.tv_nsec = 0,
> +			},
> +			.clock_type = CPU_HOG_CLOCK_PROCESS,
> +		};
> +
> +		SAFE_CG_PRINTF(cg_test, "cgroup.procs", "%d", getpid());
> +		exit(hog_cpus_timed(&param));
> +	}
> +
> +	SAFE_WAITPID(-1, &status, 0);

The child encodes its outcome in the exit code, but "status" is never
examined here (same pattern in cgroup_cpu03.c, cgroup_cpu04.c
wait_children() and cgroup_cpu05.c).

hog_cpus_timed() returns -1 when clock_gettime() or pthread_create()
fails. With the status ignored, a child that never started a single hog
thread is indistinguishable from a successful run, and the parent then
reads cpu.stat and reports a misleading result. For cgroup_cpu05 this
can even produce a TPASS, since a dead hog and a correctly throttled hog
both yield a near-zero usage_usec.

The LTP way is for the child to report with tst_res()/tst_brk() directly
and exit(0) - the library propagates results from children to the
parent. Could hog_cpus_timed() call tst_brk(TBROK | TERRNO, ...) on
failure instead of returning -1? That also removes the unused "status"
variables and lets the framework reap the children.

> +	SAFE_CG_PRINTF(cg_parent, "cpu.max", "%ld %ld", quota_usec, period_usec);

cpu.max and cpu.max.burst are compiled into cpu_files[] only under
CONFIG_GROUP_SCHED_BANDWIDTH, and CONFIG_CFS_BANDWIDTH which selects it
is "default n". Similarly cpu.weight in cgroup_cpu04.c depends on
CONFIG_GROUP_SCHED_WEIGHT.

On a kernel with the cpu controller but without bandwidth support,
safe_cg_printf() cannot find the file and the test aborts with TBROK,
while a missing feature has to be reported as TCONF.

Would .needs_kconfigs work here, e.g.

	.needs_kconfigs = (const char *[]) {
		"CONFIG_CFS_BANDWIDTH=y",
		NULL
	},

or alternatively a SAFE_CG_HAS() check in setup() followed by
tst_brk(TCONF, ...)?

> +		if (nice(1) == -1 && errno != 0)
> +			exit(1);

nice() may legitimately return -1 as the new nice value, so errno has to
be set to 0 immediately before the call for this check to be meaningful
(see nice(2)). At this point errno can still carry a stale value from
the SAFE_CG_PRINTF() path just above, which would turn a successful
nice(1) returning -1 into a spurious failure.

Adding "errno = 0;" before the call would fix that. The silent exit(1)
is covered by the comment on child result reporting above.

> +/*
> + * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
> + */

All six new files carry only this line while the patch author is someone
else. cgroup_cpu01.c is clearly derived from memcontrol01.c so keeping
the original attribution makes sense there, but the remaining files are
new work - should they carry the author's own copyright line as well,
with the year matching the commit date?

> + * Includes:
> + * - test_cpucg_weight_overprovisioned
> + * - test_cpucg_weight_underprovisioned
> + * - test_cpucg_nested_weight_overprovisioned
> + * - test_cpucg_nested_weight_underprovisioned

The doc block is rendered as-is in the test catalog, and reST requires a
blank line before a bullet list. A " *" line after "Includes:" is needed
here and in cgroup_cpu05.c.

> + * Conversion of kselftest test_cpucg_subtree_control from
> + * tools/testing/selftests/cgroup/test_cpu.c.

The doc has a role for this, which turns the reference into a link in
the catalog. memcontrol01.c uses it as

	:kselftest:`cgroup/test_memcontrol.c`

Could the five doc blocks use :kselftest:`cgroup/test_cpu.c` instead of
the plain path?

> +	.needs_cgroup_ver = TST_CG_V2,
> +	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
> +	.needs_cgroup_nsdelegate = 0,

needs_cgroup_nsdelegate is a bitfield that already defaults to 0, so the
explicit initialiser can be dropped from all five tests. The same
applies to .timeout = 30 in cgroup_cpu05.c, which is the framework
default.

> +#define USEC_PER_SEC 1000000L
> +#define HOG_SECONDS 10

USEC_PER_SEC is not used anywhere in cgroup_cpu04.c.

> +cleanup:
> +	for (i = 2; i >= 0; i--) {
> +		if (cg_leaf[i]) {
> +			SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs",
> +				       "%d", getpid());
> +			cg_leaf[i] = tst_cg_group_rm(cg_leaf[i]);
> +		}
> +	}

The drain write is inside the loop here, so it runs three times, while
the two nested variants in the same file do it once before the loop.

Beyond the inconsistency, the test process itself is never added to any
cg_leaf group in this file, so is the drain write needed at all here?

> +static void run(unsigned int n)
> +{
> +	switch (n) {
> +	case 0:
> +		test_cpucg_weight_overprovisioned();
> +		break;
...
> +	.test = run,
> +	.tcnt = 4,

LTP dispatches multiple test cases through a table rather than a switch,
either a struct tcase[] or a static array of function pointers, with
.tcnt = ARRAY_SIZE(...). As written, .tcnt = 4 (and .tcnt = 2 in
cgroup_cpu05.c) is a literal that can silently drift out of sync with
the cases.

> +	.forks_child = 1,
> +	.needs_cgroup_ver = TST_CG_V2,

cgroup_cpu02.c does not set .needs_root = 1 while cgroup_cpu03.c,
cgroup_cpu04.c and cgroup_cpu05.c do, even though all four write to
cgroup.procs and cpu.* from the test process. Could this be made
consistent across the series?

> +/*\
> + * Common helpers for cgroup cpu controller tests.
> + *
> + * Converted from kselftest
> + * tools/testing/selftests/cgroup/test_cpu.c
> + */

The "/*\" marker opens a test high-level description that docparse
extracts for the catalog. cgroup_cpu_common.h is a helper header rather
than a test, so a plain "/* */" comment would be more appropriate.

Verdict - Needs revision

---
Note:

The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.

Regards,
LTP AI Reviewer

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [LTP] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  2026-07-30 12:25 [LTP] [PATCH v2 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
@ 2026-07-30 14:06 ` linuxtestproject.agent
  0 siblings, 0 replies; 7+ messages in thread
From: linuxtestproject.agent @ 2026-07-30 14:06 UTC (permalink / raw)
  To: Shaojie Sun; +Cc: ltp

Hi Shaojie,

On Thu, 30 Jul 2026, Shaojie Sun wrote:
> tst_cgroup: add cpu.stat and cpu.weight to cgroup file table

--- [PATCH 2/3] ---

> +static void cleanup(void)
> +{
> +	if (cg_test) {
> +		cg_test = tst_cg_group_rm(cg_test);
> +	}
> +}

The braces around the single statement here (and the equivalent cleanup()
in cgroup_cpu03.c) trip checkpatch: "braces {} are not necessary for
single statement blocks". This is also inconsistent with cgroup_cpu01/04/05
which omit them. Could the braces be dropped?

> +	long usage_seconds = 2;
> +	long expected_usage_usec = usage_seconds * USEC_PER_SEC;
> +	cg_test = tst_cg_group_mk(tst_cg, "cpucg_test");

checkpatch reports "Missing a blank line after declarations" here (and at
the analogous spot in cgroup_cpu03.c). Should a blank line separate the
declarations from the first statement?

Verdict - Needs revision

---
Note:

The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.

Regards,
LTP AI Reviewer

-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-07-30 14:07 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30  3:28 [LTP] [PATCH 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
2026-07-30  3:28 ` [LTP] [PATCH 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
2026-07-30  7:04   ` [LTP] " linuxtestproject.agent
2026-07-30  3:28 ` [LTP] [PATCH 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
2026-07-30  6:38   ` Andrea Cervesato via ltp
2026-07-30  3:28 ` [LTP] [PATCH 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  -- strict thread matches above, loose matches on Subject: below --
2026-07-30 12:25 [LTP] [PATCH v2 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
2026-07-30 14:06 ` [LTP] " linuxtestproject.agent

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.