* Re: [LTP] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-04 0:24 [LTP] [PATCH v7 1/1] " Changwei Zou via ltp
@ 2026-11-04 3:30 ` linuxtestproject.agent
0 siblings, 0 replies; 7+ messages in thread
From: linuxtestproject.agent @ 2026-11-04 3:30 UTC (permalink / raw)
To: Changwei Zou; +Cc: ltp
Hi Changwei,
On Wed Nov 4 11:24:00 2026 +1100, Changwei Zou wrote:
> cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
> + * where the file's pages landed. Instead of looking at the noisy global
> + * per-node page-cache counters in ``/sys``, it accounts for only the data
> + * file's own pages: after reading the file it mmaps it, faults every page
> + * in and uses ``move_pages()`` (with a NULL node array, so nothing is
Use :manpage:`move_pages(2)` instead of ``move_pages()`` in the doc comment.
> +/* Build the cpuset.cpus string listing all online CPUs of node 'nd'. */
> +static void node_cpus(int nd, char *cpus, size_t size)
> +{
> + int ncpus = 0, i;
> + long online;
> + char path[PATH_MAX], buf[32];
> +
> + while (tst_path_exists(PATH_SYS_SYSTEM "/cpu/cpu%d", ncpus))
> + ncpus++;
numa_helper.h (linked via testcases/kernel/include/lib.mk) already provides
write_node_cpusets(), which gathers a node's online CPUs and sets
cpuset.cpus and cpuset.mems. Use write_node_cpusets() instead of duplicating
this helper.
> + fd = SAFE_OPEN(DATAFILE, O_RDONLY);
> + while ((ret = read(fd, buf, sizeof(buf))) > 0)
> + ;
> + if (ret < 0)
> + tst_brk(TBROK | TERRNO, "read %s", DATAFILE);
> + SAFE_CLOSE(fd);
Use SAFE_READ(0, fd, buf, sizeof(buf)) in the loop instead of raw read() and
a manual error check.
> +static void run(void)
> +{
> + int a = nodes[0];
> + int b = nodes[1];
> + const int expect_a[] = { a };
> + const int expect_b[] = { b };
> + const int expect_ab[] = { a, b };
> +
> + do_spread_test(a, -1, 0, expect_a, 1,
> + "spread off, single node: local allocation");
> + do_spread_test(a, -1, 1, expect_a, 1,
> + "spread on, single node: all on that node");
> + do_spread_test(b, -1, 0, expect_b, 1,
> + "spread off, single node: local allocation");
> + do_spread_test(b, -1, 1, expect_b, 1,
> + "spread on, single node: all on that node");
> + do_spread_test(a, b, 0, expect_a, 1,
> + "spread off, two nodes: local allocation");
> + do_spread_test(a, b, 1, expect_ab, 2,
> + "spread on, two nodes: even spread");
> +}
Define a struct tcase array and use .test + .tcnt = ARRAY_SIZE(tcases) in
struct tst_test instead of calling do_spread_test() multiple times from
.test_all. Also, in the original test_spread_page1(), cases 3 and 4 kept
cpuset.cpus on node a while setting cpuset.mems to node b to verify
non-local memory node allocation, whereas passing b as cpus_node here binds
both cpuset.cpus and cpuset.mems to node b and repeats cases 1 and 2.
> + counts = SAFE_MALLOC(MAXNODES * sizeof(*counts));
> +
> + tst_fill_file(DATAFILE, 0, 1024 * 1024, FILE_MB);
> +
> + SAFE_CG_PRINTF(tst_cg, "cgroup.procs", "%d", getpid());
tst_fill_file() does not call tst_brk() on failure; it unlinks DATAFILE and
returns -1, which later causes SAFE_OPEN(DATAFILE, O_RDONLY) to fail with
ENOENT. Check the return value of tst_fill_file() and call tst_brk(TBROK |
TERRNO, ...) on non-zero return.
> + .test_all = run,
> + .needs_cgroup_ctrls = (const char *const []){ "cpuset", NULL },
> + .needs_cgroup_ver = TST_CG_V1,
> +};
> +
> +#else
> + TST_TEST_TCONF(NUMA_ERROR_MSG);
> +#endif
Pass a literal string to TST_TEST_TCONF() (e.g.
TST_TEST_TCONF("test requires libnuma >= 2")) without leading indentation
instead of the NUMA_ERROR_MSG macro.
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] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-04 7:19 [LTP] [PATCH v8 " Changwei Zou via ltp
@ 2026-11-04 11:41 ` linuxtestproject.agent
0 siblings, 0 replies; 7+ messages in thread
From: linuxtestproject.agent @ 2026-11-04 11:41 UTC (permalink / raw)
To: Changwei Zou; +Cc: ltp
Hi Changwei,
On Wed Nov 4 18:19:08 2026 +1100, Changwei Zou wrote:
> cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
Verdict - Reviewed
---
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
* [LTP] [PATCH v9 0/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
@ 2026-11-05 0:55 Changwei Zou via ltp
2026-11-05 0:55 ` [LTP] [PATCH v9 1/1] " Changwei Zou via ltp
0 siblings, 1 reply; 7+ messages in thread
From: Changwei Zou via ltp @ 2026-11-05 0:55 UTC (permalink / raw)
To: ltp; +Cc: Changwei Zou
The cpuset_memory_spread test (from 2009) was split between a shell script
that mounted /dev/cpuset by hand and a C helper (cpuset_mem_hog) driven over
a FIFO and SIGUSR1/SIGUSR2. It checked the cpuset.memory_spread_page policy
by comparing the global per-node FilePages counters in
/sys/devices/system/node/nodeX/meminfo, which also account for unrelated
page-cache activity, so on large or busy NUMA machines the empirical
thresholds became unreliable and the test failed spuriously.
This series rewrites it as a single C test on the new LTP test API, the LTP
cgroup library (tst_cgroup.h) and the NUMA helpers from tse_numa.h, dropping
the shell script, the FIFO/signal handshake and the manual /dev/cpuset
management.
For each case the test pins itself to the local node, sets cpuset.mems and
cpuset.memory_spread_page, faults a file-backed shared mapping and uses
tse_nodemap_count_pages() (get_mempolicy(2)) to learn the NUMA node of each
of the mapping's pages. Only the test mapping's own pages are counted, so
unrelated page-cache activity can no longer perturb the result.
cpuset.memory_spread_page only exists in the cgroup v1 cpuset controller, so
the test requires a v1 cpuset hierarchy (.needs_cgroup_ver = TST_CG_V1) and
is skipped (TCONF) on a unified (v2) hierarchy. The patch also registers
cpuset.memory_spread_page in the LTP cgroup library's cpuset file table so
it can be driven through the SAFE_CG_* helpers.
Tested on a 2-node NUMA machine with a v1 cpuset hierarchy: all six subtests
pass.
Changes in v9 (addresses Cyril's v8 review):
- Use tse_numa.h instead of the deprecated numa_helper.h: node discovery
and per-node free-memory filtering via tse_get_nodemap(), and page
accounting via tse_numa_map()/tse_numa_fault()/tse_nodemap_count_pages().
This also drops the move_pages(2) syscall (counting now uses
get_mempolicy(2)) and the write_node_cpusets() dependency; the task is
pinned to the local node with numa_run_on_node().
- struct tcase: use const char *mems / const char *expect instead of
int[2] arrays with a -1 sentinel.
- Move the tmpfs/ramfs explanation out of the struct into the top-level
test doc comment.
- Makefile: link libltpnuma (LTPLIBS = numa) instead of pulling in the
kernel test lib.
v8:
https://lore.kernel.org/ltp/20261104071908.128726-1-changwei.zou@canonical.com/
v7:
https://lore.kernel.org/ltp/20261104002400.56841-1-changwei.zou@canonical.com/
Changwei Zou (1):
cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
lib/tst_cgroup.c | 1 +
runtest/controllers | 2 +-
.../kernel/controllers/cpuset/.gitignore | 2 +-
.../cpuset/cpuset_memory_spread_test/Makefile | 7 +-
.../cpuset_mem_hog.c | 126 ------
.../cpuset_memory_spread01.c | 214 +++++++++++
.../cpuset_memory_spread_testset.sh | 362 ------------------
7 files changed, 223 insertions(+), 491 deletions(-)
delete mode 100644 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_mem_hog.c
create mode 100644 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread01.c
delete mode 100755 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread_testset.sh
--
2.43.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 7+ messages in thread
* [LTP] [PATCH v9 1/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-05 0:55 [LTP] [PATCH v9 0/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library Changwei Zou via ltp
@ 2026-11-05 0:55 ` Changwei Zou via ltp
2026-11-05 7:31 ` [LTP] " linuxtestproject.agent
2026-11-11 13:57 ` [LTP] [PATCH v9 1/1] " Petr Vorel
0 siblings, 2 replies; 7+ messages in thread
From: Changwei Zou via ltp @ 2026-11-05 0:55 UTC (permalink / raw)
To: ltp; +Cc: Changwei Zou
The cpuset_memory_spread test, written in 2009, was split between a shell
script (cpuset_memory_spread_testset.sh) that mounted /dev/cpuset by hand
and a C helper (cpuset_mem_hog) driven over a FIFO and SIGUSR1/SIGUSR2.
It checked the cpuset.memory_spread_page policy by comparing the global
per-node FilePages counters in /sys/devices/system/node/nodeX/meminfo,
which also account for unrelated page-cache activity, so on large or busy
NUMA machines the empirical thresholds became unreliable and the test
failed spuriously.
Rewrite the whole thing as a single C test built on the new LTP test API,
the LTP cgroup library (tst_cgroup.h) and the NUMA helpers from
tse_numa.h, dropping the shell script, the FIFO/signal handshake and the
manual /dev/cpuset management.
For each case the test pins itself to the local node, sets cpuset.mems
and cpuset.memory_spread_page, faults a file-backed shared mapping and
uses tse_nodemap_count_pages() (get_mempolicy(2)) to learn the NUMA node
of each of the mapping's pages. Only the test mapping's own pages are
counted, so unrelated page-cache activity can no longer perturb the
result. The distribution is then verified: the pages must land on the
expected node(s), the other nodes may hold at most UNEXPECTED_TOLERANCE
(5%) of them, and when several expected nodes are given each must hold at
least (100 - BALANCE_TOLERANCE, 80%) of its even share.
cpuset.memory_spread_page only exists in the cgroup v1 cpuset controller,
so the test requires a v1 cpuset hierarchy (.needs_cgroup_ver = TST_CG_V1)
and is skipped with TCONF when cpuset is mounted on the unified (v2)
hierarchy. Register cpuset.memory_spread_page in the LTP cgroup library's
cpuset file table so it can be driven through the SAFE_CG_* helpers.
Signed-off-by: Changwei Zou <changwei.zou@canonical.com>
Assisted-By: opencode (claude-opus-4.8)
---
lib/tst_cgroup.c | 1 +
runtest/controllers | 2 +-
.../kernel/controllers/cpuset/.gitignore | 2 +-
.../cpuset/cpuset_memory_spread_test/Makefile | 7 +-
.../cpuset_mem_hog.c | 126 ------
.../cpuset_memory_spread01.c | 214 +++++++++++
.../cpuset_memory_spread_testset.sh | 362 ------------------
7 files changed, 223 insertions(+), 491 deletions(-)
delete mode 100644 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_mem_hog.c
create mode 100644 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread01.c
delete mode 100755 testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread_testset.sh
diff --git a/lib/tst_cgroup.c b/lib/tst_cgroup.c
index 7c6ecd4d2..74459138f 100644
--- a/lib/tst_cgroup.c
+++ b/lib/tst_cgroup.c
@@ -206,6 +206,7 @@ static const struct cgroup_file cpuset_ctrl_files[] = {
{ "cpuset.cpus", "cpuset.cpus", CTRL_CPUSET },
{ "cpuset.mems", "cpuset.mems", CTRL_CPUSET },
{ "cpuset.memory_migrate", "cpuset.memory_migrate", CTRL_CPUSET },
+ { "cpuset.memory_spread_page", "cpuset.memory_spread_page", CTRL_CPUSET },
{ }
};
diff --git a/runtest/controllers b/runtest/controllers
index 93c52c439..0af338369 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -357,7 +357,7 @@ cpuset_load_balance cpuset_load_balance_test.sh
cpuset_hotplug cpuset_hotplug_test.sh
cpuset_memory cpuset_memory_testset.sh
cpuset_memory_pressure cpuset_memory_pressure_testset.sh
-cpuset_memory_spread cpuset_memory_spread_testset.sh
+cpuset_memory_spread01 cpuset_memory_spread01
cpuset_regression_test cpuset_regression_test.sh
diff --git a/testcases/kernel/controllers/cpuset/.gitignore b/testcases/kernel/controllers/cpuset/.gitignore
index c65f2fb61..6a8d713c2 100644
--- a/testcases/kernel/controllers/cpuset/.gitignore
+++ b/testcases/kernel/controllers/cpuset/.gitignore
@@ -2,6 +2,6 @@
/cpuset_load_balance_test/cpuset_cpu_hog
/cpuset_load_balance_test/cpuset_sched_domains_check
/cpuset_memory_pressure_test/cpuset_memory_pressure
-/cpuset_memory_spread_test/cpuset_mem_hog
+/cpuset_memory_spread_test/cpuset_memory_spread01
/cpuset_memory_test/cpuset_memory_test
/cpuset_syscall_test/cpuset_syscall_test
diff --git a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/Makefile b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/Makefile
index b3c76f0de..5b681f77c 100644
--- a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/Makefile
+++ b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/Makefile
@@ -22,6 +22,11 @@
top_srcdir ?= ../../../../..
+LTPLIBS = numa
+
include $(top_srcdir)/include/mk/testcases.mk
-include $(abs_srcdir)/../Makefile.inc
+
+cpuset_memory_spread01: LDLIBS += $(NUMA_LIBS)
+cpuset_memory_spread01: LTPLDLIBS = -lltpnuma
+
include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_mem_hog.c b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_mem_hog.c
deleted file mode 100644
index 56e039eee..000000000
--- a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_mem_hog.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/******************************************************************************/
-/* */
-/* Copyright (c) 2009 FUJITSU LIMITED */
-/* */
-/* This program is free software; you can redistribute it and/or modify */
-/* it under the terms of the GNU General Public License as published by */
-/* the Free Software Foundation; either version 2 of the License, or */
-/* (at your option) any later version. */
-/* */
-/* This program is distributed in the hope that it will be useful, */
-/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
-/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See */
-/* the GNU General Public License for more details. */
-/* */
-/* You should have received a copy of the GNU General Public License */
-/* along with this program; if not, write to the Free Software */
-/* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
-/* */
-/* Author: Miao Xie <miaox@cn.fujitsu.com> */
-/* */
-/******************************************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-#include <getopt.h>
-#include <err.h>
-#include <signal.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "../cpuset_lib/common.h"
-
-#define BUFFER_SIZE 100
-
-volatile int end;
-
-void sighandler1(UNUSED int signo)
-{
-}
-
-void sighandler2(UNUSED int signo)
-{
- end = 1;
-}
-
-int page_cache_hog(void)
-{
- int fd = -1;
- char buff[BUFFER_SIZE];
- char path[BUFFER_SIZE];
- int ret = 0;
-
- sprintf(path, "%s", "DATAFILE");
- fd = open(path, O_RDONLY);
- if (fd == -1) {
- warn("open %s failed", path);
- return -1;
- }
-
- while ((ret = read(fd, buff, sizeof(buff))) > 0) ;
- if (ret == -1)
- warn("read %s failed", path);
-
- close(fd);
- return ret;
-}
-
-int mem_hog(void)
-{
- sigset_t signalset;
- int fd;
- int ret = 0;
-
- if (sigemptyset(&signalset) < 0)
- err(1, "sigemptyset()");
- sigsuspend(&signalset);
-
- while (!end) {
- ret = page_cache_hog();
-
- fd = open("./myfifo", O_WRONLY);
- if (fd == -1)
- err(1, "open fifo failed");
-
- if (ret) {
- if (write(fd, "0", 1) == -1)
- warn("write fifo failed.");
- } else {
- if (write(fd, "1", 1) == -1)
- warn("write fifo failed.");
- }
-
- close(fd);
-
- sigsuspend(&signalset);
- }
-
- return ret;
-}
-
-int main(void)
-{
- struct sigaction sa1, sa2;
-
- sa1.sa_handler = sighandler1;
- if (sigemptyset(&sa1.sa_mask) < 0)
- err(1, "sigemptyset()");
-
- sa1.sa_flags = 0;
- if (sigaction(SIGUSR1, &sa1, NULL) < 0)
- err(1, "sigaction()");
-
- sa2.sa_handler = sighandler2;
- if (sigemptyset(&sa2.sa_mask) < 0)
- err(1, "sigemptyset()");
-
- sa2.sa_flags = 0;
- if (sigaction(SIGUSR2, &sa2, NULL) < 0)
- err(1, "sigaction()");
-
- return mem_hog();
-}
diff --git a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread01.c b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread01.c
new file mode 100644
index 000000000..0edaaebb0
--- /dev/null
+++ b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread01.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2009 FUJITSU LIMITED
+ * Author: Miao Xie <miaox@cn.fujitsu.com>
+ *
+ * Rewritten into a single C test using the LTP cgroup library.
+ */
+
+/*\
+ * Verify the ``cpuset.memory_spread_page`` control file.
+ *
+ * A task's file-backed page cache is normally allocated on the node the task
+ * runs on (local allocation). When ``cpuset.memory_spread_page`` is enabled,
+ * the page cache of a task in the cpuset is instead spread evenly over all
+ * the memory nodes allowed by ``cpuset.mems``.
+ *
+ * For every case the test pins itself to the local node, sets ``cpuset.mems``
+ * and ``cpuset.memory_spread_page``, then faults a file-backed shared mapping
+ * and uses :manpage:`get_mempolicy(2)` (through the tse_numa helpers) to learn
+ * the NUMA node each of the mapping's pages resides on. Because only the test
+ * mapping's own pages are counted, unrelated page-cache activity on the system
+ * cannot perturb the result. The resulting distribution is verified:
+ *
+ * - with spreading off the pages must land on the expected node (the local
+ * node, or the only node allowed by ``cpuset.mems``);
+ * - with spreading on over several nodes the pages must be spread evenly.
+ *
+ * ``cpuset.memory_spread_page`` exists only in the cgroup v1 cpuset
+ * controller, so the test requires a v1 cpuset hierarchy and is skipped
+ * (TCONF) when cpuset is mounted on the unified (v2) hierarchy.
+ *
+ * The test is skipped on tmpfs/ramfs, where the mapping's pages are the
+ * backing store itself rather than ordinary page cache, so their placement
+ * is not governed by this knob.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef HAVE_NUMA_H
+# include <numa.h>
+#endif
+
+#include "tst_test.h"
+#include "tst_cgroup.h"
+#include "tse_numa.h"
+
+#ifdef HAVE_NUMA_V2
+
+/* Path of the file-backed mapping whose page placement we measure. */
+#define DATAFILE "numa_data_file"
+
+/* Size of the mapping. Large enough to measure an even spread reliably. */
+#define SIZE_MB 100
+#define SIZE ((size_t)SIZE_MB * 1024 * 1024)
+
+/* Each node needs enough free memory to hold the whole mapping plus slack. */
+#define MIN_FREE_KB ((SIZE_MB + 50) * 1024)
+
+/*
+ * The pages must land on the expected node(s); the other nodes may hold at
+ * most this fraction (in percent) of them.
+ */
+#define UNEXPECTED_TOLERANCE 5
+
+/*
+ * When the pages are spread across several expected nodes, the kernel spreads
+ * them evenly. Every expected node must hold at least
+ * (100 - BALANCE_TOLERANCE) percent of its even share.
+ */
+#define BALANCE_TOLERANCE 20
+
+static struct tse_nodemap *nodes;
+
+/*
+ * The task's CPUs are pinned to the local node (nodes->map[0]) for every case
+ * (see setup()); only cpuset.mems and cpuset.memory_spread_page change.
+ *
+ * mems/expect are strings of single-digit indices into nodes->map: '0' is the
+ * local node, '1' the remote one.
+ */
+static const struct tcase {
+ int spread;
+ const char *mems;
+ const char *expect;
+ const char *desc;
+} tcases[] = {
+ {0, "0", "0", "spread off, mems on the local node"},
+ {1, "0", "0", "spread on, mems on the local node"},
+ {0, "1", "1", "spread off, mems on the remote node"},
+ {1, "1", "1", "spread on, mems on the remote node"},
+ {0, "01", "0", "spread off, mems on both nodes (local allocation)"},
+ {1, "01", "01", "spread on, mems on both nodes (even spread)"},
+};
+
+static void check_result(const char *expect, const char *desc)
+{
+ unsigned int i, total = 0, expected_sum = 0, n_expect = 0, unexpected;
+
+ for (i = 0; i < nodes->cnt; i++)
+ total += nodes->counters[i];
+
+ if (!total) {
+ tst_res(TFAIL, "%s: no pages were allocated", desc);
+ return;
+ }
+
+ for (i = 0; expect[i]; i++) {
+ expected_sum += nodes->counters[expect[i] - '0'];
+ n_expect++;
+ }
+
+ unexpected = total - expected_sum;
+
+ tst_res(TINFO,
+ "%s: unexpected nodes hold %.2f%% of the pages (tolerance %d%%)",
+ desc, (double)unexpected * 100 / total, UNEXPECTED_TOLERANCE);
+
+ if (unexpected * 100 > total * UNEXPECTED_TOLERANCE) {
+ tst_res(TFAIL, "%s: too many pages on unexpected node(s)", desc);
+ return;
+ }
+
+ /*
+ * Every expected node must hold at least (100 - BALANCE_TOLERANCE)% of
+ * its even share. This enforces an even spread across several expected
+ * nodes and rejects an expected node that received nothing. A single
+ * expected node always passes this check.
+ */
+ for (i = 0; expect[i]; i++) {
+ unsigned int idx = expect[i] - '0';
+
+ if (nodes->counters[idx] * n_expect * 100 <
+ expected_sum * (100 - BALANCE_TOLERANCE)) {
+ tst_res(TFAIL, "%s: node %u holds too few pages",
+ desc, nodes->map[idx]);
+ return;
+ }
+ }
+
+ tst_res(TPASS, "%s", desc);
+}
+
+static void run(unsigned int n)
+{
+ const struct tcase *tc = &tcases[n];
+ char mems[64];
+ size_t len = 0;
+ void *ptr;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; tc->mems[i]; i++) {
+ ret = snprintf(mems + len, sizeof(mems) - len, "%s%u",
+ i ? "," : "", nodes->map[tc->mems[i] - '0']);
+ if (ret < 0 || (size_t)ret >= sizeof(mems) - len)
+ tst_brk(TBROK, "failed to build cpuset.mems string");
+ len += ret;
+ }
+
+ SAFE_CG_PRINT(tst_cg, "cpuset.mems", mems);
+ SAFE_CG_PRINTF(tst_cg, "cpuset.memory_spread_page", "%d", tc->spread);
+
+ ptr = tse_numa_map(DATAFILE, SIZE);
+ tse_numa_fault(ptr, SIZE);
+ tse_nodemap_reset_counters(nodes);
+ tse_nodemap_count_pages(nodes, ptr, SIZE);
+ tse_numa_unmap(ptr, SIZE);
+
+ check_result(tc->expect, tc->desc);
+}
+
+static void setup(void)
+{
+ nodes = tse_get_nodemap(TST_NUMA_MEM, MIN_FREE_KB);
+ if (nodes->cnt < 2)
+ tst_brk(TCONF, "requires at least 2 NUMA memory nodes");
+
+ if (!SAFE_CG_HAS(tst_cg, "cpuset.memory_spread_page"))
+ tst_brk(TCONF, "cpuset.memory_spread_page not available");
+
+ SAFE_CG_PRINTF(tst_cg, "cgroup.procs", "%d", getpid());
+
+ /* Pin the task to the local node for every case. */
+ if (numa_run_on_node(nodes->map[0]))
+ tst_brk(TCONF, "cannot run on node %u (no CPUs?)",
+ nodes->map[0]);
+}
+
+static void cleanup(void)
+{
+ if (nodes)
+ tse_nodemap_free(nodes);
+}
+
+static struct tst_test test = {
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .skip_filesystems = (const char *const []){ "tmpfs", "ramfs", NULL },
+ .setup = setup,
+ .cleanup = cleanup,
+ .test = run,
+ .tcnt = ARRAY_SIZE(tcases),
+ .needs_cgroup_ctrls = (const char *const []){ "cpuset", NULL },
+ .needs_cgroup_ver = TST_CG_V1,
+};
+
+#else
+TST_TEST_TCONF("test requires libnuma >= 2");
+#endif
diff --git a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread_testset.sh b/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread_testset.sh
deleted file mode 100755
index 4c49bb8fd..000000000
--- a/testcases/kernel/controllers/cpuset/cpuset_memory_spread_test/cpuset_memory_spread_testset.sh
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/bin/sh
-
-################################################################################
-# #
-# Copyright (c) 2009 FUJITSU LIMITED #
-# #
-# This program is free software; you can redistribute it and#or modify #
-# it under the terms of the GNU General Public License as published by #
-# the Free Software Foundation; either version 2 of the License, or #
-# (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License #
-# for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program; if not, write to the Free Software #
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #
-# #
-# Author: Miao Xie <miaox@cn.fujitsu.com> #
-# #
-################################################################################
-
-export TCID="cpuset_memory_spread"
-export TST_TOTAL=6
-export TST_COUNT=1
-
-. cpuset_funcs.sh
-
-check
-
-exit_status=0
-nr_cpus=$NR_CPUS
-nr_mems=$N_NODES
-
-# In general, the cache hog will use more than 10000 kb slab space on the nodes
-# on which it is running. The other nodes' slab space has littler change.(less
-# than 1000 kb).
-upperlimit=10000
-
-# set lowerlimit according to pagesize
-# pagesize(bytes) | lowerlimit(kb)
-# ------------------------------------
-# 4096 | 2048
-# 16384 | 8192
-
-PAGE_SIZE=`tst_getconf PAGESIZE`
-lowerlimit=$((PAGE_SIZE * 512 / 1024))
-
-cpus_all="$(seq -s, 0 $((nr_cpus-1)))"
-mems_all="$(seq -s, 0 $((nr_mems-1)))"
-
-nodedir="/sys/devices/system/node"
-
-FIFO="./myfifo"
-
-# memsinfo is an array implementation of the form of a multi-line string
-# _0: value0
-# _1: value1
-# _2: value2
-#
-memsinfo=""
-
-# set value to memsinfo ($1 - index, $2 - value)
-set_memsinfo_val()
-{
- local nl='
-'
- # clearing existent value (if present)
- memsinfo=`echo "$memsinfo" | sed -r "/^\_$1\: /d"`
-
- if [ -z "$memsinfo" ]; then
- memsinfo="_$1: $2"
- else
- memsinfo="$memsinfo${nl}_$1: $2"
- fi
-}
-
-# get value from memsinfo ($1 - index)
-get_memsinfo_val()
-{
- local value=
- value=`echo "$memsinfo" | grep -e "^_$1: "`
- value=`echo "$value" | sed -r "s/^.*\: (.*)$/\1/"`
- echo "$value"
-}
-
-
-init_memsinfo_array()
-{
- local i=
-
- for i in `seq 0 $((nr_mems-1))`
- do
- set_memsinfo_val $i 0
- done
-}
-
-# get_meminfo <nodeid> <item>
-get_meminfo()
-{
- local nodeid="$1"
- local nodepath="$nodedir/node$nodeid"
- local nodememinfo="$nodepath/meminfo"
- local item="$2"
- local info=`cat $nodememinfo | grep $item | awk '{print $4}'`
- set_memsinfo_val $nodeid $info
-}
-
-# freemem_check
-# We need enough memory space on every node to run this test, so we must check
-# whether every node has enough free memory or not.
-# return value: 1 - Some node doesn't have enough free memory
-# 0 - Every node has enough free memory, We can do this test
-freemem_check()
-{
- local i=
-
- for i in `seq 0 $((nr_mems-1))`
- do
- get_meminfo $i "MemFree"
- done
-
- for i in `seq 0 $((nr_mems-1))`
- do
- # I think we need 100MB free memory to run test
- if [ $(get_memsinfo_val $i) -lt 100000 ]; then
- return 1
- fi
- done
-}
-
-# get_memsinfo
-get_memsinfo()
-{
- local i=
-
- for i in `seq 0 $((nr_mems-1))`
- do
- get_meminfo $i "FilePages"
- done
-}
-
-# account_meminfo <nodeId>
-account_meminfo()
-{
- local nodeId="$1"
- local tmp="$(get_memsinfo_val $nodeId)"
- get_meminfo $@ "FilePages"
- set_memsinfo_val $nodeId $(($(get_memsinfo_val $nodeId)-$tmp))
-}
-
-# account_memsinfo
-account_memsinfo()
-{
- local i=
-
- for i in `seq 0 $((nr_mems-1))`
- do
- account_meminfo $i
- done
-}
-
-
-# result_check <nodelist>
-# return 0: success
-# 1: fail
-result_check()
-{
- local nodelist="`echo $1 | sed -e 's/,/ /g'`"
- local i=
-
- for i in $nodelist
- do
- if [ $(get_memsinfo_val $i) -le $upperlimit ]; then
- return 1
- fi
- done
-
- local allnodelist="`echo $mems_all | sed -e 's/,/ /g'`"
- allnodelist=" "$allnodelist" "
- nodelist=" "$nodelist" "
-
- local othernodelist="$allnodelist"
- for i in $nodelist
- do
- othernodelist=`echo "$othernodelist" | sed -e "s/ $i / /g"`
- done
-
- for i in $othernodelist
- do
- if [ $(get_memsinfo_val $i) -gt $lowerlimit ]; then
- return 1
- fi
- done
-}
-
-# general_memory_spread_test <cpusetpath> <is_spread> <cpu_list> <node_list> \
-# <expect_nodes> <test_pid>
-# expect_nodes: we expect to use the slab or cache on which node
-general_memory_spread_test()
-{
- local cpusetpath="$CPUSET/1"
- local is_spread="$1"
- local cpu_list="$2"
- local node_list="$3"
- local expect_nodes="$4"
- local test_pid="$5"
-
- cpuset_set "$cpusetpath" "$cpu_list" "$node_list" "0" 2> $CPUSET_TMP/stderr
- if [ $? -ne 0 ]; then
- cpuset_log_error $CPUSET_TMP/stderr
- tst_resm TFAIL "set general group parameter failed."
- return 1
- fi
-
- /bin/echo "$is_spread" > "$cpusetpath/cpuset.memory_spread_page" 2> $CPUSET_TMP/stderr
- if [ $? -ne 0 ]; then
- cpuset_log_error $CPUSET_TMP/stderr
- tst_resm TFAIL "set spread value failed."
- return 1
- fi
-
- /bin/echo "$test_pid" > "$cpusetpath/tasks" 2> $CPUSET_TMP/stderr
- if [ $? -ne 0 ]; then
- cpuset_log_error $CPUSET_TMP/stderr
- tst_resm TFAIL "attach task failed."
- return 1
- fi
-
- # we'd better drop the caches before we test page cache.
- sync
- /bin/echo 3 > /proc/sys/vm/drop_caches 2> $CPUSET_TMP/stderr
- if [ $? -ne 0 ]; then
- cpuset_log_error $CPUSET_TMP/stderr
- tst_resm TFAIL "drop caches failed."
- return 1
- fi
-
- get_memsinfo
- /bin/kill -s SIGUSR1 $test_pid
- read exit_num < $FIFO
- if [ $exit_num -eq 0 ]; then
- tst_resm TFAIL "hot mem task failed."
- return 1
- fi
-
- account_memsinfo
- result_check $expect_nodes
- if [ $? -ne 0 ]; then
- tst_resm TFAIL "hog the memory on the unexpected node(FilePages_For_Nodes(KB): ${memsinfo}, Expect Nodes: $expect_nodes)."
- return 1
- fi
-}
-
-base_test()
-{
- local pid=
- local result_num=
-
- setup
- if [ $? -ne 0 ]; then
- exit_status=1
- else
- cpuset_mem_hog &
- pid=$!
- general_memory_spread_test "$@" "$pid"
- result_num=$?
- if [ $result_num -ne 0 ]; then
- exit_status=1
- fi
-
- /bin/kill -s SIGUSR2 $pid
- wait $pid
-
- cleanup
- if [ $? -ne 0 ]; then
- exit_status=1
- elif [ $result_num -eq 0 ]; then
- tst_resm TPASS "Cpuset memory spread page test succeeded."
- fi
- fi
- TST_COUNT=$(($TST_COUNT + 1))
-}
-
-# test general spread page cache in a cpuset
-test_spread_page1()
-{
- while read spread cpus nodes exp_nodes
- do
- base_test "$spread" "$cpus" "$nodes" "$exp_nodes"
- done <<- EOF
- 0 0 0 0
- 1 0 0 0
- 0 0 1 1
- 1 0 1 1
- 0 0 0,1 0
- 1 0 0,1 0,1
- EOF
- # while read spread cpus nodes exp_nodes
-}
-
-test_spread_page2()
-{
- local pid=
- local result_num=
-
- setup
- if [ $? -ne 0 ]; then
- exit_status=1
- else
- cpuset_mem_hog &
- pid=$!
- general_memory_spread_test "1" "$cpus_all" "0" "0" "$pid"
- result_num=$?
- if [ $result_num -ne 0 ]; then
- exit_status=1
- else
- general_memory_spread_test "1" "$cpus_all" "1" "1" "$pid"
- result_num=$?
- if [ $result_num -ne 0 ]; then
- exit_status=1
- fi
- fi
-
- /bin/kill -s SIGUSR2 $pid
- wait $pid
-
- cleanup
- if [ $? -ne 0 ]; then
- exit_status=1
- elif [ $result_num -eq 0 ]; then
- tst_resm TPASS "Cpuset memory spread page test succeeded."
- fi
- fi
-}
-
-init_memsinfo_array
-freemem_check
-if [ $? -ne 0 ]; then
- tst_brkm TCONF "Some node doesn't has enough free memory(100MB) to do test(MemFree_For_Nodes(KB): ${memsinfo[*]})."
-fi
-
-dd if=/dev/zero of=./DATAFILE bs=1M count=100
-if [ $? -ne 0 ]; then
- tst_brkm TFAIL "Creating DATAFILE failed."
-fi
-
-rm -f $FIFO
-mkfifo $FIFO
-if [ $? -ne 0 ]; then
- rm -f DATAFILE
- tst_brkm TFAIL "failed to mkfifo $FIFO"
-fi
-
-test_spread_page1
-test_spread_page2
-
-rm -f DATAFILE $FIFO
-
-exit $exit_status
--
2.43.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [LTP] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-05 0:55 ` [LTP] [PATCH v9 1/1] " Changwei Zou via ltp
@ 2026-11-05 7:31 ` linuxtestproject.agent
2026-11-11 13:57 ` [LTP] [PATCH v9 1/1] " Petr Vorel
1 sibling, 0 replies; 7+ messages in thread
From: linuxtestproject.agent @ 2026-11-05 7:31 UTC (permalink / raw)
To: Changwei Zou; +Cc: ltp
Hi Changwei,
On Thu Nov 5 11:55:05 2026 +1100, Changwei Zou wrote:
> cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
Verdict - Reviewed
---
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] [PATCH v9 1/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-05 0:55 ` [LTP] [PATCH v9 1/1] " Changwei Zou via ltp
2026-11-05 7:31 ` [LTP] " linuxtestproject.agent
@ 2026-11-11 13:57 ` Petr Vorel
2026-11-12 10:00 ` Changwei Zou via ltp
1 sibling, 1 reply; 7+ messages in thread
From: Petr Vorel @ 2026-11-11 13:57 UTC (permalink / raw)
To: Changwei Zou; +Cc: ltp
Hi Changwei Zou,
...
> +static struct tst_test test = {
> + .needs_root = 1,
> + .needs_tmpdir = 1,
> + .skip_filesystems = (const char *const []){ "tmpfs", "ramfs", NULL },
> + .setup = setup,
> + .cleanup = cleanup,
> + .test = run,
> + .tcnt = ARRAY_SIZE(tcases),
> + .needs_cgroup_ctrls = (const char *const []){ "cpuset", NULL },
> + .needs_cgroup_ver = TST_CG_V1,
> +};
> +
> +#else
> +TST_TEST_TCONF("test requires libnuma >= 2");
IMHO at least this suggestion by agent in v7 was wrong. You should have kept
using:
TST_TEST_TCONF(NUMA_ERROR_MSG);
BTW on which Ubuntu version do you test which still uses v1 and have libnuma2?
Kind regards,
Petr
> +#endif
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [LTP] [PATCH v9 1/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library
2026-11-11 13:57 ` [LTP] [PATCH v9 1/1] " Petr Vorel
@ 2026-11-12 10:00 ` Changwei Zou via ltp
0 siblings, 0 replies; 7+ messages in thread
From: Changwei Zou via ltp @ 2026-11-12 10:00 UTC (permalink / raw)
To: pvorel; +Cc: Changwei Zou, ltp
Hi Petr,
Thanks for the review.
> +#else
> +TST_TEST_TCONF("test requires libnuma >= 2");
> IMHO at least this suggestion by agent in v7 was wrong. You should have kept
> using:
> TST_TEST_TCONF(NUMA_ERROR_MSG);
Agreed, that was a mistake on my side. I've restored
TST_TEST_TCONF(NUMA_ERROR_MSG);
in v10.
While at it I also dropped ".needs_cgroup_ver = TST_CG_V1". The
SAFE_CG_HAS() check on cpuset.memory_spread_page in setup() already skips
the test with TCONF when cpuset is mounted on the unified (v2) hierarchy,
so forcing the controller version is redundant.
> BTW on which Ubuntu version do you test which still uses v1 and have
> libnuma2?
I tested on an AWS instance running Ubuntu with the 6.8.0-1069-aws kernel
(2 NUMA memory nodes, 96 CPUs, libnuma present).
To be precise, the system does not use cgroup v1 by default -- /sys/fs/cgroup
is cgroup v2 (cgroup2fs). However, systemd does not use the cpuset
controller on the v2 hierarchy there, so cpuset is idle and the LTP cgroup
library is able to mount a v1 cpuset hierarchy on the fly:
tst_cgroup.c:793: TINFO: Mounted V1 cpuset CGroup on /tmp/cgroup_cpuset
cpuset.memory_spread_page then exists in that v1 cpuset and the test runs;
all six subtests pass. On a host where cpuset is actively bound to the v2
hierarchy the test simply skips with TCONF, which is the expected behaviour
since cpuset.memory_spread_page is a cgroup v1 only knob.
I'll submit v10 with these changes later.
Thanks,
Changwei
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-12 10:01 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 0:55 [LTP] [PATCH v9 0/1] cpuset_memory_spread: rewrite as a C test using the LTP cgroup library Changwei Zou via ltp
2026-11-05 0:55 ` [LTP] [PATCH v9 1/1] " Changwei Zou via ltp
2026-11-05 7:31 ` [LTP] " linuxtestproject.agent
2026-11-11 13:57 ` [LTP] [PATCH v9 1/1] " Petr Vorel
2026-11-12 10:00 ` Changwei Zou via ltp
-- strict thread matches above, loose matches on Subject: below --
2026-11-04 7:19 [LTP] [PATCH v8 " Changwei Zou via ltp
2026-11-04 11:41 ` [LTP] " linuxtestproject.agent
2026-11-04 0:24 [LTP] [PATCH v7 1/1] " Changwei Zou via ltp
2026-11-04 3:30 ` [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.