[RFC v2 3/3] mm/lru_gen: expose oldest-generation page counts in memory.stat

Zicheng Wang <[email protected]>
Newsgroups org.kernel.vger.cgroups,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <[email protected]>
Add nr_oldest_anon and nr_oldest_file to memory.stat: pages in the
oldest generation (min_seq) of each type, aggregated over the cgroup
subtree and all N_MEMORY nodes.  This is the observation half of the
proactive-aging loop - it shows a policy how much anon/file memory the
next reclaim can evict directly, so it can decide whether aging is
needed.

Computed on demand from lrugen->nr_pages; no per-page maintenance, no
hot-path cost.  Paired with the AGING counter and workingset_refault_*
to confirm an aging pass took effect and was not too aggressive.

Gated by CONFIG_LRU_GEN; reported in pages, matching the lru_gen dump.

Signed-off-by: Zicheng Wang <[email protected]>
---
 Documentation/admin-guide/cgroup-v2.rst | 18 ++++++++++++++++
 mm/internal.h                           |  2 ++
 mm/memcontrol.c                         | 24 +++++++++++++++++++++
 mm/vmscan.c                             | 28 +++++++++++++++++++++++++
 4 files changed, 72 insertions(+)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index f42ac6dddbf6..a071175d11be 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1658,6 +1658,24 @@ The following nested keys are defined.
 		the value for the foo counter, since the foo counter is type-based, not
 		list-based.
 
+	  nr_oldest_anon, nr_oldest_file
+		Number of pages in the oldest generation of the anonymous and
+		file types. Only present when CONFIG_LRU_GEN is enabled.
+
+		With MGLRU, page reclaim starts from the oldest generation
+		(min_seq) of each type, so these counts show how much anonymous
+		and file memory the next reclaim pass (or a proactive
+		memory.reclaim) can directly evict. They are aggregated across
+		this cgroup's subtree and all NUMA nodes, and are reported in
+		pages, matching the per-generation breakdown in the ``lru_gen``
+		debugfs file.
+
+		Proactive-aging policy can use them together with the ``aging``
+		counter and ``workingset_refault_*``: a large ``nr_oldest_file``
+		with near-zero ``nr_oldest_anon`` indicates file cache piling up
+		in the oldest generation while anonymous pages stay young, which
+		is the condition ``memory.aging`` is meant to rebalance.
+
 	  slab_reclaimable
 		Part of "slab" that might be reclaimed, such as
 		dentries and inodes.
diff --git a/mm/internal.h b/mm/internal.h
index 96add6cd4627..959d376c02a6 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -636,6 +636,8 @@ int user_proactive_reclaim(char *buf,
 
 #ifdef CONFIG_LRU_GEN
 int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens);
+void lru_gen_nr_oldest_pages(struct lruvec *lruvec,
+			     unsigned long *nr_anon, unsigned long *nr_file);
 #endif
 
 /*
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index c6a969b56878..016194cabd60 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1733,6 +1733,30 @@ static void memcg_stat_format(struct mem_cgroup *memcg, struct seq_buf *s)
 		}
 	}
 
+#ifdef CONFIG_LRU_GEN
+	/* Oldest-generation (min_seq) anon/file pages over this cgroup's
+	 * subtree and all N_MEMORY nodes; see lru_gen_nr_oldest_pages().
+	 */
+	{
+		struct mem_cgroup *mi;
+		int nid;
+		unsigned long oldest_anon = 0, oldest_file = 0;
+
+		for_each_mem_cgroup_tree(mi, memcg) {
+			for_each_node_state(nid, N_MEMORY) {
+				unsigned long nr_anon, nr_file;
+				struct lruvec *lruvec = mem_cgroup_lruvec(mi, NODE_DATA(nid));
+
+				lru_gen_nr_oldest_pages(lruvec, &nr_anon, &nr_file);
+				oldest_anon += nr_anon;
+				oldest_file += nr_file;
+			}
+		}
+		seq_buf_printf(s, "nr_oldest_anon %lu\n", oldest_anon);
+		seq_buf_printf(s, "nr_oldest_file %lu\n", oldest_file);
+	}
+#endif
+
 	/* Accumulated memory events */
 	seq_buf_printf(s, "pgscan %lu\n",
 		       memcg_page_state(memcg, PGSCAN_KSWAPD) +
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 805e29c499c8..3800f0c4f1f5 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5935,6 +5935,34 @@ int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens)
 	return ret;
 }
 
+/**
+ * lru_gen_nr_oldest_pages - pages in the oldest generation of a lruvec
+ * @lruvec: target lruvec
+ * @nr_anon: filled with oldest-generation anonymous page count
+ * @nr_file: filled with oldest-generation file page count
+ *
+ * Reclaim starts from the oldest generation (min_seq) of each type, so these
+ * count what the next reclaim can evict directly.  Computed from
+ * lrugen->nr_pages[min_seq type][zone]; eventually consistent, clamped to 0.
+ */
+void lru_gen_nr_oldest_pages(struct lruvec *lruvec,
+			     unsigned long *nr_anon, unsigned long *nr_file)
+{
+	int type, zone;
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+	unsigned long size[ANON_AND_FILE] = {};
+
+	for (type = 0; type < ANON_AND_FILE; type++) {
+		int gen = lru_gen_from_seq(READ_ONCE(lrugen->min_seq[type]));
+
+		for (zone = 0; zone < MAX_NR_ZONES; zone++)
+			size[type] += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+	}
+
+	*nr_anon = size[LRU_GEN_ANON];
+	*nr_file = size[LRU_GEN_FILE];
+}
+
 #else /* !CONFIG_LRU_GEN */
 
 static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
-- 
2.25.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.