[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