[PATCH 1/2] mm/mempolicy: track page allocations per mempolicy

From: JP Kobryn

Date: Wed Feb 11 2026 - 23:51:44 EST


It would be useful to see a breakdown of allocations to understand which
NUMA policies are driving them. For example, when investigating memory
pressure, having policy-specific counts could show that allocations were
bound to the affected node (via MPOL_BIND).

Add per-policy page allocation counters as new node stat items. These
counters can provide correlation between a mempolicy and pressure on a
given node.

Signed-off-by: JP Kobryn <inwardvessel@xxxxxxxxx>
Suggested-by: Johannes Weiner <hannes@xxxxxxxxxxx>
---
include/linux/mmzone.h | 9 +++++++++
mm/mempolicy.c | 30 ++++++++++++++++++++++++++++--
mm/vmstat.c | 9 +++++++++
3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index fc5d6c88d2f0..762609d5f0af 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -255,6 +255,15 @@ enum node_stat_item {
PGDEMOTE_DIRECT,
PGDEMOTE_KHUGEPAGED,
PGDEMOTE_PROACTIVE,
+#ifdef CONFIG_NUMA
+ PGALLOC_MPOL_DEFAULT,
+ PGALLOC_MPOL_PREFERRED,
+ PGALLOC_MPOL_BIND,
+ PGALLOC_MPOL_INTERLEAVE,
+ PGALLOC_MPOL_LOCAL,
+ PGALLOC_MPOL_PREFERRED_MANY,
+ PGALLOC_MPOL_WEIGHTED_INTERLEAVE,
+#endif
#ifdef CONFIG_HUGETLB_PAGE
NR_HUGETLB,
#endif
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 68a98ba57882..3c64784af761 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -217,6 +217,21 @@ static void reduce_interleave_weights(unsigned int *bw, u8 *new_iw)
new_iw[nid] /= iw_gcd;
}

+#define CHECK_MPOL_NODE_STAT_OFFSET(mpol) \
+ BUILD_BUG_ON(PGALLOC_##mpol - mpol != PGALLOC_MPOL_DEFAULT)
+
+static enum node_stat_item mpol_node_stat(unsigned short mode)
+{
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_PREFERRED);
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_BIND);
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_INTERLEAVE);
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_LOCAL);
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_PREFERRED_MANY);
+ CHECK_MPOL_NODE_STAT_OFFSET(MPOL_WEIGHTED_INTERLEAVE);
+
+ return PGALLOC_MPOL_DEFAULT + mode;
+}
+
int mempolicy_set_node_perf(unsigned int node, struct access_coordinate *coords)
{
struct weighted_interleave_state *new_wi_state, *old_wi_state = NULL;
@@ -2446,8 +2461,14 @@ static struct page *alloc_pages_mpol(gfp_t gfp, unsigned int order,

nodemask = policy_nodemask(gfp, pol, ilx, &nid);

- if (pol->mode == MPOL_PREFERRED_MANY)
- return alloc_pages_preferred_many(gfp, order, nid, nodemask);
+ if (pol->mode == MPOL_PREFERRED_MANY) {
+ page = alloc_pages_preferred_many(gfp, order, nid, nodemask);
+ if (page)
+ __mod_node_page_state(page_pgdat(page),
+ mpol_node_stat(MPOL_PREFERRED_MANY), 1 << order);
+
+ return page;
+ }

if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
/* filter "hugepage" allocation, unless from alloc_pages() */
@@ -2472,6 +2493,9 @@ static struct page *alloc_pages_mpol(gfp_t gfp, unsigned int order,
page = __alloc_frozen_pages_noprof(
gfp | __GFP_THISNODE | __GFP_NORETRY, order,
nid, NULL);
+ if (page)
+ __mod_node_page_state(page_pgdat(page),
+ mpol_node_stat(pol->mode), 1 << order);
if (page || !(gfp & __GFP_DIRECT_RECLAIM))
return page;
/*
@@ -2484,6 +2508,8 @@ static struct page *alloc_pages_mpol(gfp_t gfp, unsigned int order,
}

page = __alloc_frozen_pages_noprof(gfp, order, nid, nodemask);
+ if (page)
+ __mod_node_page_state(page_pgdat(page), mpol_node_stat(pol->mode), 1 << order);

if (unlikely(pol->mode == MPOL_INTERLEAVE ||
pol->mode == MPOL_WEIGHTED_INTERLEAVE) && page) {
diff --git a/mm/vmstat.c b/mm/vmstat.c
index 65de88cdf40e..74e0ddde1e93 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1291,6 +1291,15 @@ const char * const vmstat_text[] = {
[I(PGDEMOTE_DIRECT)] = "pgdemote_direct",
[I(PGDEMOTE_KHUGEPAGED)] = "pgdemote_khugepaged",
[I(PGDEMOTE_PROACTIVE)] = "pgdemote_proactive",
+#ifdef CONFIG_NUMA
+ [I(PGALLOC_MPOL_DEFAULT)] = "pgalloc_mpol_default",
+ [I(PGALLOC_MPOL_PREFERRED)] = "pgalloc_mpol_preferred",
+ [I(PGALLOC_MPOL_BIND)] = "pgalloc_mpol_bind",
+ [I(PGALLOC_MPOL_INTERLEAVE)] = "pgalloc_mpol_interleave",
+ [I(PGALLOC_MPOL_LOCAL)] = "pgalloc_mpol_local",
+ [I(PGALLOC_MPOL_PREFERRED_MANY)] = "pgalloc_mpol_preferred_many",
+ [I(PGALLOC_MPOL_WEIGHTED_INTERLEAVE)] = "pgalloc_mpol_weighted_interleave",
+#endif
#ifdef CONFIG_HUGETLB_PAGE
[I(NR_HUGETLB)] = "nr_hugetlb",
#endif
--
2.47.3