[LTP] [PATCH v2] testcases: sysfs: Add sys_thp02

Xuewen Wang wangxuewen_ky@163.com
Fri Nov 6 04:22:51 CET 2026


From: Xuewen Wang <wangxuewen@kylinos.cn>

A test for the per-size mTHP attributes under
/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/. It iterates all
hugepages-<size>kB directories and checks the directory name structure,
the enabled/shmem_enabled choice files, the stats/ counters and that a
directory for the PMD-sized THP exists.

The set of mTHP attributes differs between kernel versions and
configurations, so missing attributes are skipped gracefully.

Tested on 6.6.160 (TCONF, no mTHP sizes) and 7.3.0-rc5, x86_64: 224/224
TPASS.

Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Changes in v2 (addressing the AI review):

- Replace 'hugepages-*kB/' with 'hugepages-<size>kB/' in the doc comment
  and commit message to fix an unclosed reStructuredText emphasis
  start-string
- Move the TST_SYSFS_ASSERT_CHOICE() calls into the braced
  if (is_anon)/if (is_shmem) blocks and drop the duplicated bare
  if statements
- Reset found_pmd at the beginning of run() so it does not stay set
  across -i iterations

 runtest/sysfs                                 |   1 +
 .../kernel/mm/transparent_hugepage/.gitignore |   1 +
 .../mm/transparent_hugepage/sys_thp02.c       | 229 ++++++++++++++++++
 3 files changed, 231 insertions(+)
 create mode 100644 testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp02.c

diff --git a/runtest/sysfs b/runtest/sysfs
index 5eb8f81f8..08763c21e 100644
--- a/runtest/sysfs
+++ b/runtest/sysfs
@@ -27,3 +27,4 @@ sys_hugepages02 sys_hugepages02
 sys_ksm01 sys_ksm01
 sys_mm_swap01 sys_mm_swap01
 sys_thp01 sys_thp01
+sys_thp02 sys_thp02
diff --git a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
index bdb564dfe..ede87c4c1 100644
--- a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
+++ b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/.gitignore
@@ -1 +1,2 @@
 /sys_thp01
+/sys_thp02
diff --git a/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp02.c b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp02.c
new file mode 100644
index 000000000..530315565
--- /dev/null
+++ b/testcases/kernel/sysfs/kernel/mm/transparent_hugepage/sys_thp02.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Xuewen Wang <wangxuewen@kylinos.cn>
+ */
+
+/*\
+ * Sanity checks for the multi-size Transparent HugePage (mTHP) attributes
+ * exported under /sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/.
+ *
+ * Each THP size has its own hugepages-<size>kB directory with a per-size
+ * enabled flag and a stats/ subdirectory with a set of counters. For each
+ * such directory the test verifies that:
+ *
+ * - the size encoded in the directory name is a power of two, a power-of-two
+ *   multiple of the base page size, and strictly larger than the base page
+ *   size, or at least 4 pages for anonymous sizes
+ * - enabled selects one of always/inherit/madvise/never
+ * - shmem_enabled, if present, selects one of
+ *   always/inherit/within_size/advise/never
+ * - the stats counters are non-negative
+ * - nr_anon_partially_mapped is not larger than nr_anon
+ *
+ * After all directories are checked the test verifies that a directory for
+ * the PMD-sized THP exists, since the kernel always creates it.
+ *
+ * Attributes which are not present on the running kernel are skipped
+ * gracefully, since the set of mTHP stats differs between kernel versions
+ * and configurations (e.g. CONFIG_SHMEM).
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include "tst_test.h"
+#include "tst_sysfs_assert.h"
+#include "tst_path_defs.h"
+
+static const char *const enabled_allowed[] = {
+	"always", "inherit", "madvise", "never", NULL
+};
+
+static const char *const shmem_allowed[] = {
+	"always", "inherit", "within_size", "advise", "never", NULL
+};
+
+static const char *const any_stats[] = {
+	"split",
+	"split_failed",
+	"collapse_alloc",
+	"collapse_alloc_failed",
+	NULL
+};
+
+static const char *const anon_stats[] = {
+	"anon_fault_alloc",
+	"anon_fault_fallback",
+	"anon_fault_fallback_charge",
+	"split_deferred",
+	"nr_anon",
+	"nr_anon_partially_mapped",
+	"collapse_exceed_swap_pte",
+	"collapse_exceed_none_pte",
+	"collapse_exceed_shared_pte",
+	NULL
+};
+
+static const char *const swap_stats[] = {
+	"zswpout",
+	"swpin",
+	"swpin_fallback",
+	"swpin_fallback_charge",
+	"swpout",
+	"swpout_fallback",
+	NULL
+};
+
+static const char *const shmem_stats[] = {
+	"shmem_alloc",
+	"shmem_fallback",
+	"shmem_fallback_charge",
+	NULL
+};
+
+static long pagesize;
+static long pmd_kb;
+static int found_pmd;
+
+static void check_size(const char *dir, int is_anon)
+{
+	long size_kb, npages;
+
+	if (sscanf(dir, "hugepages-%ldkB", &size_kb) != 1) {
+		tst_res(TFAIL, "Malformed mTHP directory name '%s'", dir);
+		return;
+	}
+
+	if (size_kb <= 0 || size_kb & (size_kb - 1)) {
+		tst_res(TFAIL, "%s: size %ld kB is not a power of two", dir, size_kb);
+		return;
+	}
+
+	npages = size_kb * 1024 / pagesize;
+
+	if (npages < 2 || (npages & (npages - 1))) {
+		tst_res(TFAIL,
+			"%s: size %ld kB is not a power-of-two multiple of the page size",
+			dir, size_kb);
+		return;
+	}
+
+	if (is_anon && npages < 4) {
+		tst_res(TFAIL,
+			"%s: anonymous size %ld kB is smaller than 4 pages",
+			dir, size_kb);
+		return;
+	}
+
+	if (size_kb == pmd_kb)
+		found_pmd = 1;
+
+	tst_res(TPASS, "%s: size %ld kB is a valid mTHP size", dir, size_kb);
+}
+
+static void check_dir(const char *dir)
+{
+	size_t i;
+	int is_anon = tst_sysfs_exists(PATH_MM_THP "/%s/enabled", dir);
+	int is_shmem = tst_sysfs_exists(PATH_MM_THP "/%s/shmem_enabled", dir);
+
+	tst_res(TINFO, "Checking mTHP size '%s'", dir);
+
+	check_size(dir, is_anon);
+
+	for (i = 0; any_stats[i]; i++) {
+		TST_SYSFS_ASSERT_RANGEUU_SILENT(0, ULONG_MAX,
+						PATH_MM_THP "/%s/stats/%s",
+						dir, any_stats[i]);
+	}
+
+	if (is_anon) {
+		TST_SYSFS_ASSERT_CHOICE(enabled_allowed, NULL, 0,
+					PATH_MM_THP "/%s/enabled", dir);
+
+		for (i = 0; anon_stats[i]; i++) {
+			TST_SYSFS_ASSERT_RANGEUU_SILENT(0, ULONG_MAX,
+							PATH_MM_THP "/%s/stats/%s",
+							dir, anon_stats[i]);
+		}
+
+		TST_SYSFS_ASSERT_LE_SUFFIX_SILENT("/stats/nr_anon_partially_mapped",
+						  "/stats/nr_anon",
+						  PATH_MM_THP "/%s", dir);
+	}
+
+	if (is_shmem) {
+		TST_SYSFS_ASSERT_CHOICE(shmem_allowed, NULL, 0,
+					PATH_MM_THP "/%s/shmem_enabled", dir);
+
+		for (i = 0; shmem_stats[i]; i++) {
+			TST_SYSFS_ASSERT_RANGEUU_SILENT(0, ULONG_MAX,
+							PATH_MM_THP "/%s/stats/%s",
+							dir, shmem_stats[i]);
+		}
+	}
+
+	for (i = 0; swap_stats[i]; i++) {
+		TST_SYSFS_ASSERT_RANGEUU_SILENT(0, ULONG_MAX,
+						PATH_MM_THP "/%s/stats/%s",
+						dir, swap_stats[i]);
+	}
+}
+
+static void setup(void)
+{
+	long pmd_size;
+
+	pagesize = getpagesize();
+
+	if (access(PATH_MM_THP "/hpage_pmd_size", F_OK))
+		tst_brk(TCONF, PATH_MM_THP "/hpage_pmd_size does not exist");
+
+	pmd_size = TST_SYSFS_READ_LI(PATH_MM_THP "/hpage_pmd_size");
+
+	if (pmd_size <= 0 || pmd_size % 1024)
+		tst_brk(TCONF, "Unexpected hpage_pmd_size %ld", pmd_size);
+
+	pmd_kb = pmd_size / 1024;
+}
+
+static void run(void)
+{
+	DIR *d;
+	struct dirent *ent;
+	int found = 0;
+
+	found_pmd = 0;
+
+	d = TST_SYSFS_TRY_OPENDIR(PATH_MM_THP);
+
+	while ((ent = SAFE_READDIR(d))) {
+		if (strncmp(ent->d_name, "hugepages-", 10))
+			continue;
+
+		found = 1;
+		check_dir(ent->d_name);
+	}
+
+	SAFE_CLOSEDIR(d);
+
+	if (!found) {
+		tst_res(TCONF, "No mTHP sizes found");
+		return;
+	}
+
+	if (found_pmd)
+		tst_res(TPASS, "hugepages-%ldkB directory is present", pmd_kb);
+	else
+		tst_res(TFAIL, "hugepages-%ldkB directory is missing", pmd_kb);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.needs_kconfigs = (const char *const[]){
+		"CONFIG_TRANSPARENT_HUGEPAGE=y",
+		NULL
+	},
+};
-- 
2.25.1



More information about the ltp mailing list