All of lore.kernel.org
 help / color / mirror / Atom feed
* 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; 12+ 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] 12+ messages in thread

* [LTP] [PATCH v2 0/3] Add CPU cgroup v2 tests ported from kselftest
@ 2026-07-30 12:25 Shaojie Sun
  2026-07-30 12:25 ` [LTP] [PATCH v2 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-30 12:25 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.

v2:
- Changed hog_cpus_timed() to use tst_brk() instead of returning -1
- Removed unused status variables, use NULL for SAFE_WAITPID
- Added copyright lines for the author in new files
- Fixed doc block formatting: use :kselftest: references and blank
  lines before bullet lists for reST rendering
- Removed redundant .needs_cgroup_nsdelegate = 0 initializers
- Removed unused USEC_PER_SEC from cgroup_cpu04.c
- Removed unnecessary drain writes in cleanup paths
- Replaced literal .tcnt values with ARRAY_SIZE(test_list) and
  function pointer tables
- Removed .timeout = 30 default value from cgroup_cpu05.c
- Added errno = 0 before nice() call in cgroup_cpu03.c
- Added .needs_kconfigs check for CONFIG_CFS_BANDWIDTH=y in
  cgroup_cpu05.c
- Added .needs_root = 1 to cgroup_cpu02.c for consistency
- Changed doc comment from /*\ to /* in cgroup_cpu_common.h
  since it is a helper header, not a test

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     |  64 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 103 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 115 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 332 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 176 ++++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 131 +++++++
 10 files changed, 942 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] 12+ messages in thread

* [LTP] [PATCH v2 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  2026-07-30 12:25 [LTP] [PATCH v2 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-30 12:25 ` Shaojie Sun
  2026-07-30 14:06   ` [LTP] " linuxtestproject.agent
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-30 12:25 ` [LTP] [PATCH v2 2/3] cgroup: add " Shaojie Sun
  2026-07-30 12:25 ` [LTP] [PATCH v2 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  2 siblings, 2 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-30 12:25 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.

Note: cpu.stat also exists in cgroup v1, so the v1 name is
specified as "cpu.stat" (same as v2). cpu.weight is v2-only
(NULL for v1) because its v1 counterpart cpu.shares uses a
different scale and cannot be aliased.

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..0ab072db7 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", "cpu.stat", 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] 12+ messages in thread

* [LTP] [PATCH v2 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest
  2026-07-30 12:25 [LTP] [PATCH v2 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  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 12:25 ` Shaojie Sun
  2026-07-30 12:25 ` [LTP] [PATCH v2 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  2 siblings, 0 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-30 12:25 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     |  64 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 103 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 115 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 332 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 176 ++++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 131 +++++++
 8 files changed, 935 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..64ae8bc80
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu01.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_subtree_control from
+ * :kselftest:`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 },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu02.c b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
new file mode 100644
index 000000000..d2312c63e
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_stats from
+ * :kselftest:`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;
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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) {
+		cg_test = tst_cg_group_rm(cg_test);
+	}
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test_all = test_cpucg_stats,
+	.forks_child = 1,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu03.c b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
new file mode 100644
index 000000000..1543b21a1
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_nice from
+ * :kselftest:`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;
+	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());
+
+		errno = 0;
+		if (nice(1) == -1 && errno != 0)
+			exit(1);
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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) {
+		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 },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu04.c b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
new file mode 100644
index 000000000..7dd24624b
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest cpu weight tests from
+ * :kselftest:`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 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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	return pid;
+}
+
+static void wait_children(pid_t *pids, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		SAFE_WAITPID(pids[i], NULL, 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])
+			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])
+			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:
+	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:
+	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 (*const test_list[])(void) = {
+	test_cpucg_weight_overprovisioned,
+	test_cpucg_weight_underprovisioned,
+	test_cpucg_nested_weight_overprovisioned,
+	test_cpucg_nested_weight_underprovisioned,
+};
+
+static void run(unsigned int n)
+{
+	test_list[n]();
+}
+
+static void cleanup(void)
+{
+	int i;
+
+	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 = ARRAY_SIZE(test_list),
+	.forks_child = 1,
+	.timeout = 60,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu05.c b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
new file mode 100644
index 000000000..4a31b35b9
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest cpu.max tests from
+ * :kselftest:`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;
+
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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;
+
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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 (*const test_list[])(void) = {
+	test_cpucg_max,
+	test_cpucg_max_nested,
+};
+
+static void run(unsigned int n)
+{
+	test_list[n]();
+}
+
+static void cleanup(void)
+{
+	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 = ARRAY_SIZE(test_list),
+	.forks_child = 1,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_CFS_BANDWIDTH=y",
+		NULL
+	},
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
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..dbcfd025f
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu_common.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+/*
+ * 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 only on success; failures terminate the child via tst_brk().
+ */
+static inline void 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)
+		tst_brk(TBROK | TERRNO, "clock_gettime(CLOCK_MONOTONIC) failed");
+
+	for (i = 0; i < param->nprocs; i++) {
+		ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
+		if (ret != 0)
+			tst_brk(TBROK | TERRNO, "pthread_create failed");
+	}
+
+	while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
+		ret = nanosleep(&ts_remaining, NULL);
+		if (ret && errno != EINTR)
+			tst_brk(TBROK | TERRNO, "nanosleep failed");
+
+		if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
+			ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID,
+					    &ts_total);
+			if (ret != 0)
+				tst_brk(TBROK | TERRNO,
+					"clock_gettime(CLOCK_PROCESS_CPUTIME_ID) failed");
+		} else {
+			struct timespec ts_current;
+
+			ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
+			if (ret != 0)
+				tst_brk(TBROK | TERRNO,
+					"clock_gettime(CLOCK_MONOTONIC) failed");
+
+			ts_total = timespec_sub(&ts_current, &ts_start);
+		}
+
+		ts_remaining = timespec_sub(&ts_run, &ts_total);
+	}
+}
+
+/*
+ * 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] 12+ messages in thread

* [LTP] [PATCH v2 3/3] runtest/controllers: add CPU cgroup v2 test entries
  2026-07-30 12:25 [LTP] [PATCH v2 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  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 12:25 ` [LTP] [PATCH v2 2/3] cgroup: add " Shaojie Sun
@ 2026-07-30 12:25 ` Shaojie Sun
  2 siblings, 0 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-30 12:25 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] 12+ 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
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  1 sibling, 0 replies; 12+ 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] 12+ messages in thread

* [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest
  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
@ 2026-07-31  3:13   ` Shaojie Sun
  2026-07-31  3:13     ` [LTP] [PATCH v3 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
                       ` (3 more replies)
  1 sibling, 4 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-31  3:13 UTC (permalink / raw)
  To: ltp

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 3857 bytes --]

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.

The LTP tree already contains kselftest conversions for memcg
(memcontrol01-04), ported by Richard Palethorpe. This series follows
the same pattern to complete the CPU cgroup coverage.

Regarding the concern about kselftest conversion: the CPU cgroup
interfaces tested here (cpu.stat, cpu.weight, cpu.max) are stable
kernel ABIs that change very rarely. LTP's cgroup infrastructure
provides advantages over the raw kselftest helpers — automatic v1/v2
detection, safe file operations with proper error handling, automatic
cleanup, and integration with the LTP test result framework. Bringing
these tests into LTP also gives them broader CI coverage, as many
distributions run LTP but not kselftest.

Richard Palethorpe is listed in the copyright because the test
structure and common helper (cgroup_cpu_common.h) are modeled after
his memcontrol kselftest conversions (memcontrol01-04). The CPU
cgroup tests themselves were written by the patch author.

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.

v3:
- Removed unnecessary braces around single-statement if blocks in
  cleanup() functions in cgroup_cpu02.c and cgroup_cpu03.c
- Added blank lines after declarations in test functions in
  cgroup_cpu02.c and cgroup_cpu03.c

v2:
- Changed hog_cpus_timed() to use tst_brk() instead of returning -1
- Removed unused status variables, use NULL for SAFE_WAITPID
- Added copyright lines for the author in new files
- Fixed doc block formatting: use :kselftest: references and blank
  lines before bullet lists for reST rendering
- Removed redundant .needs_cgroup_nsdelegate = 0 initializers
- Removed unused USEC_PER_SEC from cgroup_cpu04.c
- Removed unnecessary drain writes in cleanup paths
- Replaced literal .tcnt values with ARRAY_SIZE(test_list) and
  function pointer tables
- Removed .timeout = 30 default value from cgroup_cpu05.c
- Added errno = 0 before nice() call in cgroup_cpu03.c
- Added .needs_kconfigs check for CONFIG_CFS_BANDWIDTH=y in
  cgroup_cpu05.c
- Added .needs_root = 1 to cgroup_cpu02.c for consistency
- Changed doc comment from /*\ to /* in cgroup_cpu_common.h
  since it is a helper header, not a test

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     |  64 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 103 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 115 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 332 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 176 ++++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 131 +++++++
 10 files changed, 942 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


[-- Attachment #2: Type: text/plain, Size: 60 bytes --]


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

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

* [LTP] [PATCH v3 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-31  3:13     ` Shaojie Sun
  2026-07-31  6:20       ` [LTP] " linuxtestproject.agent
  2026-07-31  3:13     ` [LTP] [PATCH v3 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Shaojie Sun @ 2026-07-31  3:13 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.

Note: cpu.stat also exists in cgroup v1, so the v1 name is
specified as "cpu.stat" (same as v2). cpu.weight is v2-only
(NULL for v1) because its v1 counterpart cpu.shares uses a
different scale and cannot be aliased.

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..0ab072db7 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", "cpu.stat", CTRL_CPU },
+	{ "cpu.weight", NULL, CTRL_CPU },
 	{ }
 };
 

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

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

* [LTP] [PATCH v3 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-31  3:13     ` [LTP] [PATCH v3 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
@ 2026-07-31  3:13     ` Shaojie Sun
  2026-07-31  3:13     ` [LTP] [PATCH v3 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
  2026-07-31  8:20     ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Andrea Cervesato via ltp
  3 siblings, 0 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-31  3:13 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     |  64 ++++
 .../kernel/controllers/cpu/cgroup_cpu02.c     | 103 ++++++
 .../kernel/controllers/cpu/cgroup_cpu03.c     | 115 ++++++
 .../kernel/controllers/cpu/cgroup_cpu04.c     | 332 ++++++++++++++++++
 .../kernel/controllers/cpu/cgroup_cpu05.c     | 176 ++++++++++
 .../controllers/cpu/cgroup_cpu_common.h       | 131 +++++++
 8 files changed, 935 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..64ae8bc80
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu01.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_subtree_control from
+ * :kselftest:`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 },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu02.c b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
new file mode 100644
index 000000000..00a8d7c22
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu02.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_stats from
+ * :kselftest:`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;
+
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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)
+		cg_test = tst_cg_group_rm(cg_test);
+}
+
+static struct tst_test test = {
+	.cleanup = cleanup,
+	.test_all = test_cpucg_stats,
+	.forks_child = 1,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu03.c b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
new file mode 100644
index 000000000..3088fcc56
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu03.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest test_cpucg_nice from
+ * :kselftest:`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;
+
+	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());
+
+		errno = 0;
+		if (nice(1) == -1 && errno != 0)
+			exit(1);
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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)
+		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 },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu04.c b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
new file mode 100644
index 000000000..7dd24624b
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu04.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest cpu weight tests from
+ * :kselftest:`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 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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	return pid;
+}
+
+static void wait_children(pid_t *pids, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		SAFE_WAITPID(pids[i], NULL, 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])
+			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])
+			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:
+	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:
+	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 (*const test_list[])(void) = {
+	test_cpucg_weight_overprovisioned,
+	test_cpucg_weight_underprovisioned,
+	test_cpucg_nested_weight_overprovisioned,
+	test_cpucg_nested_weight_underprovisioned,
+};
+
+static void run(unsigned int n)
+{
+	test_list[n]();
+}
+
+static void cleanup(void)
+{
+	int i;
+
+	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 = ARRAY_SIZE(test_list),
+	.forks_child = 1,
+	.timeout = 60,
+	.needs_root = 1,
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
diff --git a/testcases/kernel/controllers/cpu/cgroup_cpu05.c b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
new file mode 100644
index 000000000..4a31b35b9
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu05.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+
+/*\
+ * Conversion of kselftest cpu.max tests from
+ * :kselftest:`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;
+
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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;
+
+	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());
+		hog_cpus_timed(&param);
+		exit(0);
+	}
+
+	SAFE_WAITPID(-1, NULL, 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 (*const test_list[])(void) = {
+	test_cpucg_max,
+	test_cpucg_max_nested,
+};
+
+static void run(unsigned int n)
+{
+	test_list[n]();
+}
+
+static void cleanup(void)
+{
+	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 = ARRAY_SIZE(test_list),
+	.forks_child = 1,
+	.needs_root = 1,
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_CFS_BANDWIDTH=y",
+		NULL
+	},
+	.needs_cgroup_ver = TST_CG_V2,
+	.needs_cgroup_ctrls = (const char *const []){ "cpu", NULL },
+};
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..dbcfd025f
--- /dev/null
+++ b/testcases/kernel/controllers/cpu/cgroup_cpu_common.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Richard Palethorpe <rpalethorpe@suse.com>
+ * Copyright (c) 2026 Shaojie Sun <sunshaojie@kylinos.cn>
+ */
+/*
+ * 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 only on success; failures terminate the child via tst_brk().
+ */
+static inline void 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)
+		tst_brk(TBROK | TERRNO, "clock_gettime(CLOCK_MONOTONIC) failed");
+
+	for (i = 0; i < param->nprocs; i++) {
+		ret = pthread_create(&tid, NULL, &hog_cpu_thread_func, NULL);
+		if (ret != 0)
+			tst_brk(TBROK | TERRNO, "pthread_create failed");
+	}
+
+	while (ts_remaining.tv_sec > 0 || ts_remaining.tv_nsec > 0) {
+		ret = nanosleep(&ts_remaining, NULL);
+		if (ret && errno != EINTR)
+			tst_brk(TBROK | TERRNO, "nanosleep failed");
+
+		if (param->clock_type == CPU_HOG_CLOCK_PROCESS) {
+			ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID,
+					    &ts_total);
+			if (ret != 0)
+				tst_brk(TBROK | TERRNO,
+					"clock_gettime(CLOCK_PROCESS_CPUTIME_ID) failed");
+		} else {
+			struct timespec ts_current;
+
+			ret = clock_gettime(CLOCK_MONOTONIC, &ts_current);
+			if (ret != 0)
+				tst_brk(TBROK | TERRNO,
+					"clock_gettime(CLOCK_MONOTONIC) failed");
+
+			ts_total = timespec_sub(&ts_current, &ts_start);
+		}
+
+		ts_remaining = timespec_sub(&ts_run, &ts_total);
+	}
+}
+
+/*
+ * 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__ */

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

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

* [LTP] [PATCH v3 3/3] runtest/controllers: add CPU cgroup v2 test entries
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
  2026-07-31  3:13     ` [LTP] [PATCH v3 1/3] tst_cgroup: add cpu.stat and cpu.weight to cgroup file table Shaojie Sun
  2026-07-31  3:13     ` [LTP] [PATCH v3 2/3] cgroup: add CPU cgroup v2 tests ported from kselftest Shaojie Sun
@ 2026-07-31  3:13     ` Shaojie Sun
  2026-07-31  8:20     ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Andrea Cervesato via ltp
  3 siblings, 0 replies; 12+ messages in thread
From: Shaojie Sun @ 2026-07-31  3:13 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

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

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

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

Hi Shaojie,

On 2026-07-31, Shaojie Sun wrote:
> tst_cgroup: add cpu.stat and cpu.weight to cgroup file table

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

> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +top_srcdir		?= ../../../..

The build system convention is an SPDX line followed by a Copyright
line; the sibling controller Makefiles (memcg, cgroup) carry one. Could
a Copyright line be added here?

> +LDLIBS			+= -lpthread

This flag is applied directory-wide, but cgroup_cpu01.c does not include
cgroup_cpu_common.h and uses no pthreads. Would a per-target assignment
for the tests that actually need it be cleaner?

> +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 },
> +};

cgroup_cpu02..05 set .needs_root = 1, but this test does not, even
though it creates nested cgroups and writes cgroup.subtree_control. Is
the omission intentional?

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

The other four test files and the shared header also credit the porting
author (2026). Should cgroup_cpu01.c carry the same attribution for
consistency?

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] 12+ messages in thread

* Re: [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest
  2026-07-31  3:13   ` [LTP] [PATCH v3 0/3] Add CPU cgroup v2 tests ported from kselftest Shaojie Sun
                       ` (2 preceding siblings ...)
  2026-07-31  3:13     ` [LTP] [PATCH v3 3/3] runtest/controllers: add CPU cgroup v2 test entries Shaojie Sun
@ 2026-07-31  8:20     ` Andrea Cervesato via ltp
  3 siblings, 0 replies; 12+ messages in thread
From: Andrea Cervesato via ltp @ 2026-07-31  8:20 UTC (permalink / raw)
  To: Shaojie Sun; +Cc: ltp

Hi Shaojie,

we didn't receive any answer about the reason of introducing this
into LTP. kselftests are not working? are they unmaintained?

But more importantly: once we introduce these rewrite into LTP, 
who's gonna maintain them?

We already had an example of kselftests tests rewritten in
memcontrol02/03 and (as you can see from the history of this ML)
these are causing lots of issues and false positives. Mostly,
unfixable.

What I personally see, it's a duplication of code between projects
and even if LTP core library has a "better" cgroup support than
ksefltests, still, there's a risk we are going to maintain something
that is breaking and someone has to figure our why/what needs a fix.

kselftests almost come "for free" and many people are already using
them all around the world to test cgroups.

For this reason, I'm against adding these tests into LTP, unless
there's a valid maintenance plan for the future.


Kind 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] 12+ messages in thread

end of thread, other threads:[~2026-07-31  8:21 UTC | newest]

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

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.