[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