[LTP] [PATCH v3 05/31] testcases: sysfs: Add sys_node01
Cyril Hrubis
chrubis@suse.cz
Tue Aug 25 13:35:59 CEST 2026
A test for a /sys/devices/system/node/* files.
Signed-off-by: Cyril Hrubis <chrubis@suse.cz>
---
runtest/sysfs | 1 +
.../sysfs/devices/system/node/.gitignore | 1 +
.../kernel/sysfs/devices/system/node/Makefile | 7 +
.../sysfs/devices/system/node/sys_node01.c | 130 ++++++++++++++++++
4 files changed, 139 insertions(+)
create mode 100644 testcases/kernel/sysfs/devices/system/node/.gitignore
create mode 100644 testcases/kernel/sysfs/devices/system/node/Makefile
create mode 100644 testcases/kernel/sysfs/devices/system/node/sys_node01.c
diff --git a/runtest/sysfs b/runtest/sysfs
index 7c014a57e..347bd78c1 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -1,3 +1,4 @@
sys_power01 sys_power01
sys_kernel01 sys_kernel01
sys_clocksource01 sys_clocksource01
+sys_node01 sys_node01
diff --git a/testcases/kernel/sysfs/devices/system/node/.gitignore b/testcases/kernel/sysfs/devices/system/node/.gitignore
new file mode 100644
index 000000000..42a442639
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/.gitignore
@@ -0,0 +1 @@
+/sys_node01
diff --git a/testcases/kernel/sysfs/devices/system/node/Makefile b/testcases/kernel/sysfs/devices/system/node/Makefile
new file mode 100644
index 000000000..781865569
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+
+top_srcdir ?= ../../../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/sysfs/devices/system/node/sys_node01.c b/testcases/kernel/sysfs/devices/system/node/sys_node01.c
new file mode 100644
index 000000000..90f0e9383
--- /dev/null
+++ b/testcases/kernel/sysfs/devices/system/node/sys_node01.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Cyril Hrubis <chrubis@suse.cz>
+ */
+
+/*\
+ * Sanity checks for the NUMA node attributes exported under
+ * /sys/devices/system/node/.
+ *
+ * The kernel exports cpulist-style files describing the node state:
+ *
+ * - possible - node ids that could possibly be online
+ * - online - node ids that are currently online
+ *
+ * as well as a per-node memory summary in nodeN/meminfo.
+ *
+ * The test verifies that:
+ *
+ * - online is a subset of possible
+ * - for each online node MemUsed == MemTotal - MemFree
+ * - the sum of all per-node MemTotal values matches MemTotal in /proc/meminfo
+ *
+ * All checks skip gracefully with TCONF on systems without NUMA sysfs.
+ *
+ * The last check is only enforced (TFAIL on mismatch) on physical hardware.
+ * /proc/meminfo's MemTotal (si_meminfo()'s totalram_pages()) and a node's
+ * MemTotal (si_meminfo_node()'s sum of that node's zones' managed pages) are
+ * two independently maintained counters, normally kept in lockstep by
+ * adjust_managed_page_count() updating both together, so their sums match
+ * exactly on real hardware. Under virtualization, memory hotplug/ballooning
+ * mechanisms (e.g. virtio-mem, virtio-balloon, Xen balloon) are a known
+ * source of drift between the two, since they can affect one counter without
+ * (yet) correspondingly attributing the change to a specific NUMA node's
+ * zones, so a mismatch there is only reported as TINFO.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+
+#define SYS_NODE "/sys/devices/system/node"
+
+static long read_node_meminfo(int nid, const char *item)
+{
+ char path[PATH_MAX];
+ char fmt[128];
+ long val;
+
+ snprintf(path, sizeof(path), SYS_NODE "/node%d/meminfo", nid);
+ snprintf(fmt, sizeof(fmt), "Node %d %s: %%ld", nid, item);
+
+ SAFE_FILE_LINES_SCANF(path, fmt, &val);
+
+ return val;
+}
+
+static void check_node_mem(int nid, long *sum_total)
+{
+ long total, free, used;
+
+ total = read_node_meminfo(nid, "MemTotal");
+ free = read_node_meminfo(nid, "MemFree");
+ used = read_node_meminfo(nid, "MemUsed");
+
+ *sum_total += total;
+
+ if (used == total - free) {
+ tst_res(TPASS,
+ "node%d: MemUsed (%ld) == MemTotal (%ld) - MemFree (%ld)",
+ nid, used, total, free);
+ } else {
+ tst_res(TFAIL,
+ "node%d: MemUsed (%ld) != MemTotal (%ld) - MemFree (%ld)",
+ nid, used, total, free);
+ }
+}
+
+static void check_sum_memtotal(long sum_total)
+{
+ long global_total;
+
+ SAFE_FILE_LINES_SCANF("/proc/meminfo", "MemTotal: %ld", &global_total);
+
+ if (sum_total == global_total) {
+ tst_res(TPASS,
+ "sum of node MemTotal (%ld kB) matches /proc/meminfo MemTotal (%ld kB)",
+ sum_total, global_total);
+ return;
+ }
+
+ if (tst_is_virt(VIRT_ANY)) {
+ tst_res(TINFO,
+ "Running in a VM, sum of node MemTotal (%ld kB) differs from /proc/meminfo MemTotal (%ld kB), likely memory hotplug/ballooning",
+ sum_total, global_total);
+ } else {
+ tst_res(TFAIL,
+ "sum of node MemTotal (%ld kB) differs from /proc/meminfo MemTotal (%ld kB)",
+ sum_total, global_total);
+ }
+}
+
+static void run(void)
+{
+ int count, max_id, nid;
+ long sum_total = 0;
+
+ if (access(SYS_NODE "/online", F_OK)) {
+ tst_res(TCONF, SYS_NODE " is not available");
+ return;
+ }
+
+ TST_SYSFS_ASSERT_LIST_SUBSET(SYS_NODE "/online", SYS_NODE "/possible");
+
+ if (TST_SYSFS_ASSERT_PARSE_LIST(&count, &max_id, SYS_NODE "/online"))
+ return;
+
+ for (nid = 0; nid <= max_id; nid++) {
+ if (!tst_sysfs_exists(SYS_NODE "/node%d", nid))
+ continue;
+
+ check_node_mem(nid, &sum_total);
+ }
+
+ check_sum_memtotal(sum_total);
+}
+
+static struct tst_test test = {
+ .test_all = run,
+};
--
2.51.0
More information about the ltp
mailing list