* [PATCH v6 1/7] mm/memcontrol: drop unused vmscan tracepoint include
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 2:56 ` [PATCH v6 2/7] mm/vmscan: move vmscan tracepoints to a local header Ridong Chen
` (5 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
memcontrol.c includes <trace/events/vmscan.h> but does not use any of
the tracepoints or macros it defines: all tracepoints invoked here come
from memcg.h and writeback.h.
Drop it ahead of moving the vmscan trace header under mm/, after which
<trace/events/vmscan.h> will no longer exist.
No functional change.
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Barry Song <baohua@kernel.org>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/memcontrol.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index ac6c43ea8c5a..5bad742dc381 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -77,8 +77,6 @@
#include <trace/events/memcg.h>
#undef CREATE_TRACE_POINTS
-#include <trace/events/vmscan.h>
-
struct cgroup_subsys memory_cgrp_subsys __read_mostly;
EXPORT_SYMBOL(memory_cgrp_subsys);
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v6 2/7] mm/vmscan: move vmscan tracepoints to a local header
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
2026-11-03 2:56 ` [PATCH v6 1/7] mm/memcontrol: drop unused vmscan tracepoint include Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 2:56 ` [PATCH v6 3/7] mm/vmscan: move struct scan_control to a dedicated header Ridong Chen
` (4 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
Move include/trace/events/vmscan.h to mm/trace_vmscan.h so that the
tracepoints can be extended to reference vmscan-internal structures such
as struct scan_control, which is not visible outside mm/. This mirrors
what several other subsystems already do with their trace headers (e.g.
fs/fuse/fuse_trace.h, fs/gfs2/trace_gfs2.h, fs/lockd/trace.h). As they
do, set TRACE_INCLUDE_PATH to "." and add mm/ to the include path of
vmscan.o, the only user that defines CREATE_TRACE_POINTS.
No functional change.
Reviewed-by: Barry Song <baohua@kernel.org>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Acked-by: Muchun Song <muchun.song@linux.dev>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
MAINTAINERS | 3 ++-
mm/Makefile | 1 +
mm/shrinker.c | 2 +-
include/trace/events/vmscan.h => mm/trace_vmscan.h | 4 ++++
mm/vmscan.c | 2 +-
5 files changed, 9 insertions(+), 3 deletions(-)
rename include/trace/events/vmscan.h => mm/trace_vmscan.h (99%)
diff --git a/MAINTAINERS b/MAINTAINERS
index f4a6b5735794..95ff3743cec8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17708,6 +17708,7 @@ F: Documentation/admin-guide/mm/multigen_lru.rst
F: Documentation/mm/multigen_lru.rst
F: include/linux/mm_inline.h
F: include/linux/mmzone.h
+F: mm/trace_vmscan.h
F: mm/vmscan.c
F: mm/workingset.c
@@ -17928,7 +17929,7 @@ R: Shakeel Butt <shakeel.butt@linux.dev>
R: Lorenzo Stoakes <ljs@kernel.org>
L: linux-mm@kvack.org
S: Maintained
-F: include/trace/events/vmscan.h
+F: mm/trace_vmscan.h
F: mm/vmscan.c
F: mm/workingset.c
diff --git a/mm/Makefile b/mm/Makefile
index 2a3ec53d62ee..277b97f7f8d7 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -32,6 +32,7 @@ KCOV_INSTRUMENT_vmstat.o := n
KCOV_INSTRUMENT_failslab.o := n
CFLAGS_init-mm.o += -Wno-override-init
+CFLAGS_vmscan.o += -I$(src)
mmu-y := nommu.o
mmu-$(CONFIG_MMU) := highmem.o memory.o mincore.o \
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 4fb424e721bc..4fbdade6d2f6 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -3,7 +3,7 @@
#include <linux/rwsem.h>
#include <linux/shrinker.h>
#include <linux/rculist.h>
-#include <trace/events/vmscan.h>
+#include "trace_vmscan.h"
#include "internal.h"
diff --git a/include/trace/events/vmscan.h b/mm/trace_vmscan.h
similarity index 99%
rename from include/trace/events/vmscan.h
rename to mm/trace_vmscan.h
index 8a872990b4be..ad90f0e9bf2e 100644
--- a/include/trace/events/vmscan.h
+++ b/mm/trace_vmscan.h
@@ -648,4 +648,8 @@ TRACE_EVENT(mm_vmscan_kswapd_clear_hopeless,
#endif /* _TRACE_VMSCAN_H */
/* This part must be outside protection */
+#undef TRACE_INCLUDE_FILE
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE trace_vmscan
#include <trace/define_trace.h>
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 7ddef3b488b3..b51fae79c6ab 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -71,7 +71,7 @@
#include "swap.h"
#define CREATE_TRACE_POINTS
-#include <trace/events/vmscan.h>
+#include "trace_vmscan.h"
struct scan_control {
/* How many pages shrink_list() should reclaim */
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v6 3/7] mm/vmscan: move struct scan_control to a dedicated header
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
2026-11-03 2:56 ` [PATCH v6 1/7] mm/memcontrol: drop unused vmscan tracepoint include Ridong Chen
2026-11-03 2:56 ` [PATCH v6 2/7] mm/vmscan: move vmscan tracepoints to a local header Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 2:56 ` [PATCH v6 4/7] mm/vmscan: pass scan_control to the vmscan tracepoints Ridong Chen
` (3 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
The definition of struct scan_control has lived inline at the top of
mm/vmscan.c. Move it out to a new mm/vmscan.h so the reclaim-internal
state can be referenced from other reclaim translation units without
duplicating the definition.
The header uses an ordinary include guard and pulls in only what the
struct needs (nodemask_t, gfp_t and struct reclaim_state).
mm/trace_vmscan.h includes the new header directly so that any
translation unit pulling in the trace events (mm/vmscan.c, mm/shrinker.c)
sees the full definition of struct scan_control regardless of include
order.
No functional change.
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
MAINTAINERS | 2 +
mm/trace_vmscan.h | 2 +
mm/vmscan.c | 108 ------------------------------------------
mm/vmscan.h | 118 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 122 insertions(+), 108 deletions(-)
create mode 100644 mm/vmscan.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 95ff3743cec8..47e4673b04ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17710,6 +17710,7 @@ F: include/linux/mm_inline.h
F: include/linux/mmzone.h
F: mm/trace_vmscan.h
F: mm/vmscan.c
+F: mm/vmscan.h
F: mm/workingset.c
MEMORY MANAGEMENT - MIGRATION
@@ -17931,6 +17932,7 @@ L: linux-mm@kvack.org
S: Maintained
F: mm/trace_vmscan.h
F: mm/vmscan.c
+F: mm/vmscan.h
F: mm/workingset.c
MEMORY MANAGEMENT - RMAP (REVERSE MAPPING)
diff --git a/mm/trace_vmscan.h b/mm/trace_vmscan.h
index ad90f0e9bf2e..3917e1b62dfa 100644
--- a/mm/trace_vmscan.h
+++ b/mm/trace_vmscan.h
@@ -11,6 +11,8 @@
#include <linux/memcontrol.h>
#include <trace/events/mmflags.h>
+#include "vmscan.h"
+
#define RECLAIM_WB_ANON 0x0001u
#define RECLAIM_WB_FILE 0x0002u
#define RECLAIM_WB_MIXED 0x0010u
diff --git a/mm/vmscan.c b/mm/vmscan.c
index b51fae79c6ab..88d9d15da893 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -73,114 +73,6 @@
#define CREATE_TRACE_POINTS
#include "trace_vmscan.h"
-struct scan_control {
- /* How many pages shrink_list() should reclaim */
- unsigned long nr_to_reclaim;
-
- /*
- * Nodemask of nodes allowed by the caller. If NULL, all nodes
- * are scanned.
- */
- const nodemask_t *nodemask;
-
- /*
- * The memory cgroup that hit its limit and as a result is the
- * primary target of this reclaim invocation.
- */
- struct mem_cgroup *target_mem_cgroup;
-
- /*
- * Scan pressure balancing between anon and file LRUs
- */
- unsigned long anon_cost;
- unsigned long file_cost;
-
- /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */
- int *proactive_swappiness;
-
- /* Can active folios be deactivated as part of reclaim? */
-#define DEACTIVATE_ANON 1
-#define DEACTIVATE_FILE 2
- unsigned int may_deactivate:2;
- unsigned int force_deactivate:1;
- unsigned int skipped_deactivate:1;
-
- /* zone_reclaim_mode, boost reclaim */
- unsigned int may_writepage:1;
-
- /* zone_reclaim_mode */
- unsigned int may_unmap:1;
-
- /* zone_reclaim_mode, boost reclaim, cgroup restrictions */
- unsigned int may_swap:1;
-
- /* Not allow cache_trim_mode to be turned on as part of reclaim? */
- unsigned int no_cache_trim_mode:1;
-
- /* Has cache_trim_mode failed at least once? */
- unsigned int cache_trim_mode_failed:1;
-
- /* Proactive reclaim invoked by userspace */
- unsigned int proactive:1;
-
- /*
- * Cgroup memory below memory.low is protected as long as we
- * don't threaten to OOM. If any cgroup is reclaimed at
- * reduced force or passed over entirely due to its memory.low
- * setting (memcg_low_skipped), and nothing is reclaimed as a
- * result, then go back for one more cycle that reclaims the protected
- * memory (memcg_low_reclaim) to avert OOM.
- */
- unsigned int memcg_low_reclaim:1;
- unsigned int memcg_low_skipped:1;
-
- /* Shared cgroup tree walk failed, rescan the whole tree */
- unsigned int memcg_full_walk:1;
-
- unsigned int hibernation_mode:1;
-
- /* One of the zones is ready for compaction */
- unsigned int compaction_ready:1;
-
- /* There is easily reclaimable cold cache in the current node */
- unsigned int cache_trim_mode:1;
-
- /* The file folios on the current node are dangerously low */
- unsigned int file_is_tiny:1;
-
- /* Always discard instead of demoting to lower tier memory */
- unsigned int no_demotion:1;
-
- /* Allocation order */
- s8 order;
-
- /* Scan (total_size >> priority) pages at once */
- s8 priority;
-
- /* The highest zone to isolate folios for reclaim from */
- s8 reclaim_idx;
-
- /* This context's GFP mask */
- gfp_t gfp_mask;
-
- /* Incremented by the number of inactive pages that were scanned */
- unsigned long nr_scanned;
-
- /* Number of pages freed so far during a call to shrink_zones() */
- unsigned long nr_reclaimed;
-
- struct {
- unsigned int dirty;
- unsigned int congested;
- unsigned int writeback;
- unsigned int immediate;
- unsigned int taken;
- } nr;
-
- /* for recording the reclaimed slab by now */
- struct reclaim_state reclaim_state;
-};
-
#ifdef ARCH_HAS_PREFETCHW
static inline void prefetchw_prev_lru_folio(struct folio *folio,
struct list_head *base)
diff --git a/mm/vmscan.h b/mm/vmscan.h
new file mode 100644
index 000000000000..adb22e80d760
--- /dev/null
+++ b/mm/vmscan.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _MM_VMSCAN_H
+#define _MM_VMSCAN_H
+
+#include <linux/gfp_types.h>
+#include <linux/nodemask.h>
+#include <linux/swap.h>
+#include <linux/types.h>
+
+struct scan_control {
+ /* How many pages shrink_list() should reclaim */
+ unsigned long nr_to_reclaim;
+
+ /*
+ * Nodemask of nodes allowed by the caller. If NULL, all nodes
+ * are scanned.
+ */
+ const nodemask_t *nodemask;
+
+ /*
+ * The memory cgroup that hit its limit and as a result is the
+ * primary target of this reclaim invocation.
+ */
+ struct mem_cgroup *target_mem_cgroup;
+
+ /*
+ * Scan pressure balancing between anon and file LRUs
+ */
+ unsigned long anon_cost;
+ unsigned long file_cost;
+
+ /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */
+ int *proactive_swappiness;
+
+ /* Can active folios be deactivated as part of reclaim? */
+#define DEACTIVATE_ANON 1
+#define DEACTIVATE_FILE 2
+ unsigned int may_deactivate:2;
+ unsigned int force_deactivate:1;
+ unsigned int skipped_deactivate:1;
+
+ /* zone_reclaim_mode, boost reclaim */
+ unsigned int may_writepage:1;
+
+ /* zone_reclaim_mode */
+ unsigned int may_unmap:1;
+
+ /* zone_reclaim_mode, boost reclaim, cgroup restrictions */
+ unsigned int may_swap:1;
+
+ /* Not allow cache_trim_mode to be turned on as part of reclaim? */
+ unsigned int no_cache_trim_mode:1;
+
+ /* Has cache_trim_mode failed at least once? */
+ unsigned int cache_trim_mode_failed:1;
+
+ /* Proactive reclaim invoked by userspace */
+ unsigned int proactive:1;
+
+ /*
+ * Cgroup memory below memory.low is protected as long as we
+ * don't threaten to OOM. If any cgroup is reclaimed at
+ * reduced force or passed over entirely due to its memory.low
+ * setting (memcg_low_skipped), and nothing is reclaimed as a
+ * result, then go back for one more cycle that reclaims the protected
+ * memory (memcg_low_reclaim) to avert OOM.
+ */
+ unsigned int memcg_low_reclaim:1;
+ unsigned int memcg_low_skipped:1;
+
+ /* Shared cgroup tree walk failed, rescan the whole tree */
+ unsigned int memcg_full_walk:1;
+
+ unsigned int hibernation_mode:1;
+
+ /* One of the zones is ready for compaction */
+ unsigned int compaction_ready:1;
+
+ /* There is easily reclaimable cold cache in the current node */
+ unsigned int cache_trim_mode:1;
+
+ /* The file folios on the current node are dangerously low */
+ unsigned int file_is_tiny:1;
+
+ /* Always discard instead of demoting to lower tier memory */
+ unsigned int no_demotion:1;
+
+ /* Allocation order */
+ s8 order;
+
+ /* Scan (total_size >> priority) pages at once */
+ s8 priority;
+
+ /* The highest zone to isolate folios for reclaim from */
+ s8 reclaim_idx;
+
+ /* This context's GFP mask */
+ gfp_t gfp_mask;
+
+ /* Incremented by the number of inactive pages that were scanned */
+ unsigned long nr_scanned;
+
+ /* Number of pages freed so far during a call to shrink_zones() */
+ unsigned long nr_reclaimed;
+
+ struct {
+ unsigned int dirty;
+ unsigned int congested;
+ unsigned int writeback;
+ unsigned int immediate;
+ unsigned int taken;
+ } nr;
+
+ /* for recording the reclaimed slab by now */
+ struct reclaim_state reclaim_state;
+};
+
+#endif /* _MM_VMSCAN_H */
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v6 4/7] mm/vmscan: pass scan_control to the vmscan tracepoints
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
` (2 preceding siblings ...)
2026-11-03 2:56 ` [PATCH v6 3/7] mm/vmscan: move struct scan_control to a dedicated header Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 2:56 ` [PATCH v6 5/7] mm/mglru: add tracepoint for scan_folios() Ridong Chen
` (2 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
Several vmscan tracepoints spell out reclaim state as individual
arguments, all of which the caller already holds in struct scan_control.
Now that the struct is visible from the trace header, pass sc directly
and pick the fields out in TP_fast_assign instead of spelling them out
at every callsite:
- mm_vmscan_direct_reclaim_begin, mm_vmscan_memcg_reclaim_begin and
mm_vmscan_node_reclaim_begin: gfp_mask and order.
- mm_vmscan_lru_isolate, mm_vmscan_lru_shrink_inactive and
mm_vmscan_lru_shrink_active: reclaim_idx, order and priority.
- mm_vmscan_reclaim_pages: nr_scanned. nr_reclaimed stays a separate
argument: it is the return value of shrink_folio_list(), which does
not update sc->nr_reclaimed.
- mm_vmscan_balance_pgdat_end: order and nr_reclaimed. highest_zoneidx
stays a separate argument: it is a balance_pgdat() parameter, and
sc.reclaim_idx may differ from it by the time the end event fires.
No functional change. The tracefs event format (TP_STRUCT__entry and
TP_printk) is unchanged, so tracefs-based consumers are unaffected.
BPF programs attached via raw_tp/tp_btf see the new TP_PROTO and need
to be adjusted; tracepoints are not a stable ABI (see
Documentation/bpf/bpf_design_QA.rst).
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/trace_vmscan.h | 74 +++++++++++++++++++++++------------------------
mm/vmscan.c | 26 ++++++++---------
2 files changed, 48 insertions(+), 52 deletions(-)
diff --git a/mm/trace_vmscan.h b/mm/trace_vmscan.h
index 3917e1b62dfa..b2ad83e5f25e 100644
--- a/mm/trace_vmscan.h
+++ b/mm/trace_vmscan.h
@@ -124,10 +124,9 @@ TRACE_EVENT(mm_vmscan_balance_pgdat_begin,
TRACE_EVENT(mm_vmscan_balance_pgdat_end,
- TP_PROTO(int nid, int order, int highest_zoneidx,
- unsigned long nr_reclaimed),
+ TP_PROTO(int nid, struct scan_control *sc, int highest_zoneidx),
- TP_ARGS(nid, order, highest_zoneidx, nr_reclaimed),
+ TP_ARGS(nid, sc, highest_zoneidx),
TP_STRUCT__entry(
__field(int, nid)
@@ -138,9 +137,9 @@ TRACE_EVENT(mm_vmscan_balance_pgdat_end,
TP_fast_assign(
__entry->nid = nid;
- __entry->order = order;
+ __entry->order = sc->order;
__entry->highest_zoneidx = highest_zoneidx;
- __entry->nr_reclaimed = nr_reclaimed;
+ __entry->nr_reclaimed = sc->nr_reclaimed;
),
TP_printk("nid=%d order=%d highest_zoneidx=%-8s nr_reclaimed=%lu",
@@ -178,9 +177,9 @@ TRACE_EVENT(mm_vmscan_wakeup_kswapd,
DECLARE_EVENT_CLASS(mm_vmscan_direct_reclaim_begin_template,
- TP_PROTO(gfp_t gfp_flags, int order, struct mem_cgroup *memcg),
+ TP_PROTO(struct scan_control *sc, struct mem_cgroup *memcg),
- TP_ARGS(gfp_flags, order, memcg),
+ TP_ARGS(sc, memcg),
TP_STRUCT__entry(
__field( unsigned long, gfp_flags )
@@ -189,8 +188,8 @@ DECLARE_EVENT_CLASS(mm_vmscan_direct_reclaim_begin_template,
),
TP_fast_assign(
- __entry->gfp_flags = (__force unsigned long)gfp_flags;
- __entry->order = order;
+ __entry->gfp_flags = (__force unsigned long)sc->gfp_mask;
+ __entry->order = sc->order;
__entry->memcg_id = mem_cgroup_id(memcg);
),
@@ -204,17 +203,17 @@ DECLARE_EVENT_CLASS(mm_vmscan_direct_reclaim_begin_template,
DEFINE_EVENT(mm_vmscan_direct_reclaim_begin_template, mm_vmscan_direct_reclaim_begin,
- TP_PROTO(gfp_t gfp_flags, int order, struct mem_cgroup *memcg),
+ TP_PROTO(struct scan_control *sc, struct mem_cgroup *memcg),
- TP_ARGS(gfp_flags, order, memcg)
+ TP_ARGS(sc, memcg)
);
#ifdef CONFIG_MEMCG
DEFINE_EVENT(mm_vmscan_direct_reclaim_begin_template, mm_vmscan_memcg_reclaim_begin,
- TP_PROTO(gfp_t gfp_flags, int order, struct mem_cgroup *memcg),
+ TP_PROTO(struct scan_control *sc, struct mem_cgroup *memcg),
- TP_ARGS(gfp_flags, order, memcg)
+ TP_ARGS(sc, memcg)
);
#endif /* CONFIG_MEMCG */
@@ -350,15 +349,14 @@ TRACE_EVENT(mm_shrink_slab_end,
);
TRACE_EVENT(mm_vmscan_lru_isolate,
- TP_PROTO(int highest_zoneidx,
- int order,
- unsigned long nr_requested,
- unsigned long nr_scanned,
- unsigned long nr_skipped,
- unsigned long nr_taken,
- int lru),
+ TP_PROTO(struct scan_control *sc,
+ unsigned long nr_requested,
+ unsigned long nr_scanned,
+ unsigned long nr_skipped,
+ unsigned long nr_taken,
+ int lru),
- TP_ARGS(highest_zoneidx, order, nr_requested, nr_scanned, nr_skipped, nr_taken, lru),
+ TP_ARGS(sc, nr_requested, nr_scanned, nr_skipped, nr_taken, lru),
TP_STRUCT__entry(
__field(int, highest_zoneidx)
@@ -371,8 +369,8 @@ TRACE_EVENT(mm_vmscan_lru_isolate,
),
TP_fast_assign(
- __entry->highest_zoneidx = highest_zoneidx;
- __entry->order = order;
+ __entry->highest_zoneidx = sc->reclaim_idx;
+ __entry->order = sc->order;
__entry->nr_requested = nr_requested;
__entry->nr_scanned = nr_scanned;
__entry->nr_skipped = nr_skipped;
@@ -419,11 +417,11 @@ TRACE_EVENT(mm_vmscan_write_folio,
TRACE_EVENT(mm_vmscan_reclaim_pages,
- TP_PROTO(int nid,
- unsigned long nr_scanned, unsigned long nr_reclaimed,
- struct reclaim_stat *stat),
+ TP_PROTO(int nid, struct scan_control *sc,
+ unsigned long nr_reclaimed,
+ struct reclaim_stat *stat),
- TP_ARGS(nid, nr_scanned, nr_reclaimed, stat),
+ TP_ARGS(nid, sc, nr_reclaimed, stat),
TP_STRUCT__entry(
__field(int, nid)
@@ -441,7 +439,7 @@ TRACE_EVENT(mm_vmscan_reclaim_pages,
TP_fast_assign(
__entry->nid = nid;
- __entry->nr_scanned = nr_scanned;
+ __entry->nr_scanned = sc->nr_scanned;
__entry->nr_reclaimed = nr_reclaimed;
__entry->nr_dirty = stat->nr_dirty;
__entry->nr_writeback = stat->nr_writeback;
@@ -466,9 +464,9 @@ TRACE_EVENT(mm_vmscan_lru_shrink_inactive,
TP_PROTO(int nid,
unsigned long nr_scanned, unsigned long nr_reclaimed,
- struct reclaim_stat *stat, int priority, int file),
+ struct reclaim_stat *stat, struct scan_control *sc, int file),
- TP_ARGS(nid, nr_scanned, nr_reclaimed, stat, priority, file),
+ TP_ARGS(nid, nr_scanned, nr_reclaimed, stat, sc, file),
TP_STRUCT__entry(
__field(int, nid)
@@ -498,7 +496,7 @@ TRACE_EVENT(mm_vmscan_lru_shrink_inactive,
__entry->nr_activate1 = stat->nr_activate[1];
__entry->nr_ref_keep = stat->nr_ref_keep;
__entry->nr_unmap_fail = stat->nr_unmap_fail;
- __entry->priority = priority;
+ __entry->priority = sc->priority;
__entry->reclaim_flags = trace_reclaim_flags(file);
),
@@ -517,9 +515,9 @@ TRACE_EVENT(mm_vmscan_lru_shrink_active,
TP_PROTO(int nid, unsigned long nr_taken,
unsigned long nr_active, unsigned long nr_deactivated,
- unsigned long nr_referenced, int priority, int file),
+ unsigned long nr_referenced, struct scan_control *sc, int file),
- TP_ARGS(nid, nr_taken, nr_active, nr_deactivated, nr_referenced, priority, file),
+ TP_ARGS(nid, nr_taken, nr_active, nr_deactivated, nr_referenced, sc, file),
TP_STRUCT__entry(
__field(int, nid)
@@ -537,7 +535,7 @@ TRACE_EVENT(mm_vmscan_lru_shrink_active,
__entry->nr_active = nr_active;
__entry->nr_deactivated = nr_deactivated;
__entry->nr_referenced = nr_referenced;
- __entry->priority = priority;
+ __entry->priority = sc->priority;
__entry->reclaim_flags = trace_reclaim_flags(file);
),
@@ -551,9 +549,9 @@ TRACE_EVENT(mm_vmscan_lru_shrink_active,
TRACE_EVENT(mm_vmscan_node_reclaim_begin,
- TP_PROTO(int nid, int order, gfp_t gfp_flags),
+ TP_PROTO(int nid, struct scan_control *sc),
- TP_ARGS(nid, order, gfp_flags),
+ TP_ARGS(nid, sc),
TP_STRUCT__entry(
__field(int, nid)
@@ -563,8 +561,8 @@ TRACE_EVENT(mm_vmscan_node_reclaim_begin,
TP_fast_assign(
__entry->nid = nid;
- __entry->order = order;
- __entry->gfp_flags = (__force unsigned long)gfp_flags;
+ __entry->order = sc->order;
+ __entry->gfp_flags = (__force unsigned long)sc->gfp_mask;
),
TP_printk("nid=%d order=%d gfp_flags=%s",
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 88d9d15da893..f3ec19352e6d 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1764,7 +1764,7 @@ static unsigned long isolate_lru_folios(unsigned long nr_to_scan,
}
}
*nr_scanned = total_scan;
- trace_mm_vmscan_lru_isolate(sc->reclaim_idx, sc->order, nr_to_scan,
+ trace_mm_vmscan_lru_isolate(sc, nr_to_scan,
total_scan, skipped, nr_taken, lru);
update_lru_sizes(lruvec, lru, nr_zone_taken);
return nr_taken;
@@ -2054,7 +2054,7 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan,
handle_reclaim_writeback(nr_taken, pgdat, sc, &stat);
trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id,
- nr_scanned, nr_reclaimed, &stat, sc->priority, file);
+ nr_scanned, nr_reclaimed, &stat, sc, file);
return nr_reclaimed;
}
@@ -2161,7 +2161,7 @@ static void shrink_active_list(unsigned long nr_to_scan,
mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated);
trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate,
- nr_deactivate, nr_rotated, sc->priority, file);
+ nr_deactivate, nr_rotated, sc, file);
}
static unsigned int reclaim_folio_list(struct list_head *folio_list,
@@ -2184,7 +2184,7 @@ static unsigned int reclaim_folio_list(struct list_head *folio_list,
list_del(&folio->lru);
folio_putback_lru(folio);
}
- trace_mm_vmscan_reclaim_pages(pgdat->node_id, sc.nr_scanned, nr_reclaimed, &stat);
+ trace_mm_vmscan_reclaim_pages(pgdat->node_id, &sc, nr_reclaimed, &stat);
return nr_reclaimed;
}
@@ -4830,9 +4830,9 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
mod_lruvec_state(lruvec, item, isolated);
mod_lruvec_state(lruvec, PGREFILL, sorted);
mod_lruvec_state(lruvec, PGSCAN_ANON + type, isolated);
- trace_mm_vmscan_lru_isolate(sc->reclaim_idx, sc->order, nr_to_scan,
- scanned, skipped, isolated,
- type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
+ trace_mm_vmscan_lru_isolate(sc, nr_to_scan,
+ scanned, skipped, isolated,
+ type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
*isolatedp = isolated;
return scanned;
@@ -4974,7 +4974,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
if (isolated)
handle_reclaim_writeback(isolated, pgdat, sc, &stat);
trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id,
- type_scanned, reclaimed, &stat, sc->priority,
+ type_scanned, reclaimed, &stat, sc,
type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
list_for_each_entry_safe_reverse(folio, next, &list, lru) {
@@ -6870,7 +6870,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
return 1;
set_task_reclaim_state(current, &sc.reclaim_state);
- trace_mm_vmscan_direct_reclaim_begin(sc.gfp_mask, order, NULL);
+ trace_mm_vmscan_direct_reclaim_begin(&sc, NULL);
nr_reclaimed = do_try_to_free_pages(zonelist, &sc);
@@ -6911,7 +6911,7 @@ unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,
struct zonelist *zonelist = node_zonelist(numa_node_id(), sc.gfp_mask);
set_task_reclaim_state(current, &sc.reclaim_state);
- trace_mm_vmscan_memcg_reclaim_begin(sc.gfp_mask, 0, memcg);
+ trace_mm_vmscan_memcg_reclaim_begin(&sc, memcg);
noreclaim_flag = memalloc_noreclaim_save();
nr_reclaimed = do_try_to_free_pages(zonelist, &sc);
@@ -7388,8 +7388,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx)
psi_memstall_leave(&pflags);
set_task_reclaim_state(current, NULL);
- trace_mm_vmscan_balance_pgdat_end(pgdat->node_id, sc.order,
- highest_zoneidx, sc.nr_reclaimed);
+ trace_mm_vmscan_balance_pgdat_end(pgdat->node_id, &sc, highest_zoneidx);
/*
* Return the order kswapd stopped reclaiming at as
@@ -7867,8 +7866,7 @@ static unsigned long __node_reclaim(struct pglist_data *pgdat,
unsigned int noreclaim_flag;
unsigned long pflags;
- trace_mm_vmscan_node_reclaim_begin(pgdat->node_id, sc->order,
- sc->gfp_mask);
+ trace_mm_vmscan_node_reclaim_begin(pgdat->node_id, sc);
cond_resched();
if (!sc->proactive)
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v6 5/7] mm/mglru: add tracepoint for scan_folios()
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
` (3 preceding siblings ...)
2026-11-03 2:56 ` [PATCH v6 4/7] mm/vmscan: pass scan_control to the vmscan tracepoints Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 2:56 ` [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper Ridong Chen
2026-11-03 2:56 ` [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq() Ridong Chen
6 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
MGLRU's scan_folios() emits the classic-LRU tracepoint
trace_mm_vmscan_lru_isolate(), which predates MGLRU. It reports the
scan/isolate counts and the LRU type, but carries no generation or
memcg context, so a trace of an MGLRU run cannot tell which memcg a
given scan belongs to, nor how far reclaim has progressed through the
generations.
Add mm_mglru_scan_folios next to it, reporting the same counters plus
nr_sorted (folios moved to a younger generation by sort_folio()) and
the context the classic tracepoint lacks: the node id and memcg id of
the lruvec, and the max_seq, tier and min_seq of the type being
scanned.
Now that struct scan_control is visible to the trace header, pass sc,
lruvec, type and tier straight in and derive everything else in
TP_fast_assign(), so nothing is computed at the call site when the
event is disabled. The int fields are grouped at the end of the entry
to avoid padding on 64-bit.
scan_folios() is the MGLRU-specific layer where folios are actually
scanned, so it has no classic-LRU counterpart. The existing tracepoint
is left unchanged.
Tested-by: Barry Song <baohua@kernel.org>
Reviewed-by: Barry Song <baohua@kernel.org>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/trace_vmscan.h | 66 +++++++++++++++++++++++++++++++++++++++++++++++
mm/vmscan.c | 2 ++
2 files changed, 68 insertions(+)
diff --git a/mm/trace_vmscan.h b/mm/trace_vmscan.h
index b2ad83e5f25e..8148b8224422 100644
--- a/mm/trace_vmscan.h
+++ b/mm/trace_vmscan.h
@@ -392,6 +392,72 @@ TRACE_EVENT(mm_vmscan_lru_isolate,
__print_symbolic(__entry->lru, LRU_NAMES))
);
+#ifdef CONFIG_LRU_GEN
+TRACE_EVENT(mm_mglru_scan_folios,
+
+ TP_PROTO(struct lruvec *lruvec,
+ struct scan_control *sc,
+ unsigned long nr_requested,
+ unsigned long nr_scanned,
+ unsigned long nr_sorted,
+ unsigned long nr_skipped,
+ unsigned long nr_taken,
+ int type,
+ int tier),
+
+ TP_ARGS(lruvec, sc, nr_requested, nr_scanned, nr_sorted, nr_skipped,
+ nr_taken, type, tier),
+
+ TP_STRUCT__entry(
+ __field(u64, memcg_id)
+ __field(unsigned long, nr_requested)
+ __field(unsigned long, nr_scanned)
+ __field(unsigned long, nr_sorted)
+ __field(unsigned long, nr_skipped)
+ __field(unsigned long, nr_taken)
+ __field(unsigned long, max_seq)
+ __field(unsigned long, min_seq)
+ __field(int, nid)
+ __field(int, highest_zoneidx)
+ __field(int, order)
+ __field(int, lru)
+ __field(int, tier)
+ ),
+
+ TP_fast_assign(
+ __entry->memcg_id = mem_cgroup_id(lruvec_memcg(lruvec));
+ __entry->nr_requested = nr_requested;
+ __entry->nr_scanned = nr_scanned;
+ __entry->nr_sorted = nr_sorted;
+ __entry->nr_skipped = nr_skipped;
+ __entry->nr_taken = nr_taken;
+ __entry->max_seq = lruvec->lrugen.max_seq;
+ __entry->min_seq = lruvec->lrugen.min_seq[type];
+ __entry->nid = lruvec_pgdat(lruvec)->node_id;
+ __entry->highest_zoneidx = sc->reclaim_idx;
+ __entry->order = sc->order;
+ __entry->lru = type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON;
+ __entry->tier = tier;
+ ),
+
+ TP_printk("nid=%d memcg_id=%llu classzone=%d order=%d nr_requested=%lu nr_scanned=%lu nr_sorted=%lu nr_skipped=%lu nr_taken=%lu lru=%s max_seq=%lu tier=%d min_seq=%lu",
+ __entry->nid,
+ __entry->memcg_id,
+ __entry->highest_zoneidx,
+ __entry->order,
+ __entry->nr_requested,
+ __entry->nr_scanned,
+ __entry->nr_sorted,
+ __entry->nr_skipped,
+ __entry->nr_taken,
+ __print_symbolic(__entry->lru, LRU_NAMES),
+ __entry->max_seq,
+ __entry->tier,
+ __entry->min_seq)
+);
+
+#endif /* CONFIG_LRU_GEN */
+
TRACE_EVENT(mm_vmscan_write_folio,
TP_PROTO(struct folio *folio),
diff --git a/mm/vmscan.c b/mm/vmscan.c
index f3ec19352e6d..3054175717ab 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4833,6 +4833,8 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
trace_mm_vmscan_lru_isolate(sc, nr_to_scan,
scanned, skipped, isolated,
type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
+ trace_mm_mglru_scan_folios(lruvec, sc, nr_to_scan, scanned, sorted,
+ skipped, isolated, type, tier);
*isolatedp = isolated;
return scanned;
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
` (4 preceding siblings ...)
2026-11-03 2:56 ` [PATCH v6 5/7] mm/mglru: add tracepoint for scan_folios() Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 5:54 ` Baoquan He
2026-11-03 6:26 ` Barry Song
2026-11-03 2:56 ` [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq() Ridong Chen
6 siblings, 2 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
The number of pages of a given type in a generation is computed by
summing lrugen->nr_pages[gen][type][] over all zones and clamping each
per-zone counter to >= 0, since the counters are updated locklessly and
can transiently go negative. This loop is open-coded in
lruvec_evictable_size() and in the debugfs lru_gen output.
Add lruvec_gen_size() to compute it from a seq, and convert both users.
Put it in mm/vmscan.h so that the vmscan tracepoints, which include that
header, can use it as well.
An upcoming tracepoint and the MGLRU swappiness rework will also use it.
No functional change.
Suggested-by: Barry Song <baohua@kernel.org>
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/vmscan.c | 18 ++++++------------
mm/vmscan.h | 16 ++++++++++++++++
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 3054175717ab..1581b73aec56 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4207,18 +4207,15 @@ static void set_initial_priority(struct pglist_data *pgdat, struct scan_control
static unsigned long lruvec_evictable_size(struct lruvec *lruvec, int swappiness)
{
- int gen, type, zone;
+ int type;
unsigned long seq, total = 0;
struct lru_gen_folio *lrugen = &lruvec->lrugen;
DEFINE_MAX_SEQ(lruvec);
DEFINE_MIN_SEQ(lruvec);
for_each_evictable_type(type, swappiness) {
- for (seq = min_seq[type]; seq <= max_seq; seq++) {
- gen = lru_gen_from_seq(seq);
- for (zone = 0; zone < MAX_NR_ZONES; zone++)
- total += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
- }
+ for (seq = min_seq[type]; seq <= max_seq; seq++)
+ total += lruvec_gen_size(lrugen, type, seq);
}
return total;
@@ -5723,20 +5720,17 @@ static int lru_gen_seq_show(struct seq_file *m, void *v)
seq = 0;
for (; seq <= max_seq; seq++) {
- int type, zone;
+ int type;
int gen = lru_gen_from_seq(seq);
unsigned long birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
seq_printf(m, " %10lu %10u", seq, jiffies_to_msecs(jiffies - birth));
for (type = 0; type < ANON_AND_FILE; type++) {
- unsigned long size = 0;
char mark = full && seq < min_seq[type] ? 'x' : ' ';
- for (zone = 0; zone < MAX_NR_ZONES; zone++)
- size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
-
- seq_printf(m, " %10lu%c", size, mark);
+ seq_printf(m, " %10lu%c",
+ lruvec_gen_size(lrugen, type, seq), mark);
}
seq_putc(m, '\n');
diff --git a/mm/vmscan.h b/mm/vmscan.h
index adb22e80d760..c6b583fc7493 100644
--- a/mm/vmscan.h
+++ b/mm/vmscan.h
@@ -3,6 +3,7 @@
#define _MM_VMSCAN_H
#include <linux/gfp_types.h>
+#include <linux/mm_inline.h>
#include <linux/nodemask.h>
#include <linux/swap.h>
#include <linux/types.h>
@@ -115,4 +116,19 @@ struct scan_control {
struct reclaim_state reclaim_state;
};
+#ifdef CONFIG_LRU_GEN
+/* The number of pages of @type in the generation @seq, summed over all zones */
+static inline unsigned long lruvec_gen_size(struct lru_gen_folio *lrugen,
+ int type, unsigned long seq)
+{
+ int gen = lru_gen_from_seq(seq);
+ unsigned long size = 0;
+
+ for (int zone = 0; zone < MAX_NR_ZONES; zone++)
+ size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+
+ return size;
+}
+#endif /* CONFIG_LRU_GEN */
+
#endif /* _MM_VMSCAN_H */
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper
2026-11-03 2:56 ` [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper Ridong Chen
@ 2026-11-03 5:54 ` Baoquan He
2026-11-03 6:26 ` Barry Song
1 sibling, 0 replies; 14+ messages in thread
From: Baoquan He @ 2026-11-03 5:54 UTC (permalink / raw)
To: Ridong Chen
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On 11/03/26 at 10:56am, Ridong Chen wrote:
> From: Ridong Chen <chenridong@xiaomi.com>
>
> The number of pages of a given type in a generation is computed by
> summing lrugen->nr_pages[gen][type][] over all zones and clamping each
> per-zone counter to >= 0, since the counters are updated locklessly and
> can transiently go negative. This loop is open-coded in
> lruvec_evictable_size() and in the debugfs lru_gen output.
>
> Add lruvec_gen_size() to compute it from a seq, and convert both users.
> Put it in mm/vmscan.h so that the vmscan tracepoints, which include that
> header, can use it as well.
> An upcoming tracepoint and the MGLRU swappiness rework will also use it.
>
> No functional change.
>
> Suggested-by: Barry Song <baohua@kernel.org>
> Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
> ---
> mm/vmscan.c | 18 ++++++------------
> mm/vmscan.h | 16 ++++++++++++++++
> 2 files changed, 22 insertions(+), 12 deletions(-)
LGTM,
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 3054175717ab..1581b73aec56 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -4207,18 +4207,15 @@ static void set_initial_priority(struct pglist_data *pgdat, struct scan_control
>
> static unsigned long lruvec_evictable_size(struct lruvec *lruvec, int swappiness)
> {
> - int gen, type, zone;
> + int type;
> unsigned long seq, total = 0;
> struct lru_gen_folio *lrugen = &lruvec->lrugen;
> DEFINE_MAX_SEQ(lruvec);
> DEFINE_MIN_SEQ(lruvec);
>
> for_each_evictable_type(type, swappiness) {
> - for (seq = min_seq[type]; seq <= max_seq; seq++) {
> - gen = lru_gen_from_seq(seq);
> - for (zone = 0; zone < MAX_NR_ZONES; zone++)
> - total += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
> - }
> + for (seq = min_seq[type]; seq <= max_seq; seq++)
> + total += lruvec_gen_size(lrugen, type, seq);
> }
>
> return total;
> @@ -5723,20 +5720,17 @@ static int lru_gen_seq_show(struct seq_file *m, void *v)
> seq = 0;
>
> for (; seq <= max_seq; seq++) {
> - int type, zone;
> + int type;
> int gen = lru_gen_from_seq(seq);
> unsigned long birth = READ_ONCE(lruvec->lrugen.timestamps[gen]);
>
> seq_printf(m, " %10lu %10u", seq, jiffies_to_msecs(jiffies - birth));
>
> for (type = 0; type < ANON_AND_FILE; type++) {
> - unsigned long size = 0;
> char mark = full && seq < min_seq[type] ? 'x' : ' ';
>
> - for (zone = 0; zone < MAX_NR_ZONES; zone++)
> - size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
> -
> - seq_printf(m, " %10lu%c", size, mark);
> + seq_printf(m, " %10lu%c",
> + lruvec_gen_size(lrugen, type, seq), mark);
> }
>
> seq_putc(m, '\n');
> diff --git a/mm/vmscan.h b/mm/vmscan.h
> index adb22e80d760..c6b583fc7493 100644
> --- a/mm/vmscan.h
> +++ b/mm/vmscan.h
> @@ -3,6 +3,7 @@
> #define _MM_VMSCAN_H
>
> #include <linux/gfp_types.h>
> +#include <linux/mm_inline.h>
> #include <linux/nodemask.h>
> #include <linux/swap.h>
> #include <linux/types.h>
> @@ -115,4 +116,19 @@ struct scan_control {
> struct reclaim_state reclaim_state;
> };
>
> +#ifdef CONFIG_LRU_GEN
> +/* The number of pages of @type in the generation @seq, summed over all zones */
> +static inline unsigned long lruvec_gen_size(struct lru_gen_folio *lrugen,
> + int type, unsigned long seq)
> +{
> + int gen = lru_gen_from_seq(seq);
> + unsigned long size = 0;
> +
> + for (int zone = 0; zone < MAX_NR_ZONES; zone++)
> + size += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
> +
> + return size;
> +}
> +#endif /* CONFIG_LRU_GEN */
> +
> #endif /* _MM_VMSCAN_H */
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper
2026-11-03 2:56 ` [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper Ridong Chen
2026-11-03 5:54 ` Baoquan He
@ 2026-11-03 6:26 ` Barry Song
1 sibling, 0 replies; 14+ messages in thread
From: Barry Song @ 2026-11-03 6:26 UTC (permalink / raw)
To: Ridong Chen
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On Tue, Nov 3, 2026 at 10:57 AM Ridong Chen <ridong.chen@linux.dev> wrote:
>
> From: Ridong Chen <chenridong@xiaomi.com>
>
> The number of pages of a given type in a generation is computed by
> summing lrugen->nr_pages[gen][type][] over all zones and clamping each
> per-zone counter to >= 0, since the counters are updated locklessly and
> can transiently go negative. This loop is open-coded in
> lruvec_evictable_size() and in the debugfs lru_gen output.
>
> Add lruvec_gen_size() to compute it from a seq, and convert both users.
> Put it in mm/vmscan.h so that the vmscan tracepoints, which include that
> header, can use it as well.
> An upcoming tracepoint and the MGLRU swappiness rework will also use it.
>
> No functional change.
>
> Suggested-by: Barry Song <baohua@kernel.org>
> Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
> ---
Reviewed-by: Barry Song <baohua@kernel.org>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq()
2026-11-03 2:56 [PATCH v6 0/7] mm/vmscan: move tracepoints to mm/ and add MGLRU tracepoints Ridong Chen
` (5 preceding siblings ...)
2026-11-03 2:56 ` [PATCH v6 6/7] mm/mglru: add lruvec_gen_size() helper Ridong Chen
@ 2026-11-03 2:56 ` Ridong Chen
2026-11-03 6:48 ` Barry Song
6 siblings, 1 reply; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 2:56 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner
Cc: Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
Aging in MGLRU advances max_seq via inc_max_seq(), creating a new
youngest generation. There is currently no tracepoint on this path, so
the moment a new generation is created, and how the min_seq of each type
trails behind it, cannot be observed as it happens.
Add mm_mglru_inc_max_seq, emitted right after max_seq is bumped while
the lruvec lock is still held, with the node id, memcg id, the new
max_seq, the anon and file min_seq, and the number of pages in each
generation for both types, as computed by lruvec_gen_size().
The nr_anon/nr_file arrays are ordered by seq rather than by generation
slot: entry i holds the generation max_seq - MAX_NR_GENS + 1 + i, i.e.
they go from the oldest generation to the newest, the same order as the
debugfs lru_gen file, so the output can be read directly without mapping
slots back to seqs. Entries for seqs below the corresponding min_seq are
not in use and normally read as 0. The arrays are printed with
__print_array(), so the output is hex and its length follows
MAX_NR_GENS automatically.
Only the lruvec is passed in; all of the above is collected in
TP_fast_assign(), so inc_max_seq() does no extra work when the event is
disabled. Paired with mm_mglru_scan_folios it makes the full
aging-to-eviction window observable per memcg.
Signed-off-by: Ridong Chen <chenridong@xiaomi.com>
---
mm/trace_vmscan.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++
mm/vmscan.c | 2 ++
2 files changed, 48 insertions(+)
diff --git a/mm/trace_vmscan.h b/mm/trace_vmscan.h
index 8148b8224422..40d6d90f1d7a 100644
--- a/mm/trace_vmscan.h
+++ b/mm/trace_vmscan.h
@@ -456,6 +456,52 @@ TRACE_EVENT(mm_mglru_scan_folios,
__entry->min_seq)
);
+TRACE_EVENT(mm_mglru_inc_max_seq,
+
+ TP_PROTO(struct lruvec *lruvec),
+
+ TP_ARGS(lruvec),
+
+ TP_STRUCT__entry(
+ __field(u64, memcg_id)
+ __field(unsigned long, max_seq)
+ __field(unsigned long, anon_min_seq)
+ __field(unsigned long, file_min_seq)
+ __array(unsigned long, nr_anon, MAX_NR_GENS)
+ __array(unsigned long, nr_file, MAX_NR_GENS)
+ __field(int, nid)
+ ),
+
+ TP_fast_assign(
+ struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ unsigned long max_seq = lrugen->max_seq;
+ int i;
+
+ __entry->memcg_id = mem_cgroup_id(lruvec_memcg(lruvec));
+ __entry->max_seq = max_seq;
+ __entry->anon_min_seq = lrugen->min_seq[LRU_GEN_ANON];
+ __entry->file_min_seq = lrugen->min_seq[LRU_GEN_FILE];
+ /* from the oldest generation to the newest, as debugfs lru_gen does */
+ for (i = 0; i < MAX_NR_GENS; i++) {
+ unsigned long seq = max_seq - MAX_NR_GENS + 1 + i;
+
+ __entry->nr_anon[i] =
+ lruvec_gen_size(lrugen, LRU_GEN_ANON, seq);
+ __entry->nr_file[i] =
+ lruvec_gen_size(lrugen, LRU_GEN_FILE, seq);
+ }
+ __entry->nid = lruvec_pgdat(lruvec)->node_id;
+ ),
+
+ TP_printk("nid=%d memcg_id=%llu max_seq=%lu anon_min_seq=%lu file_min_seq=%lu nr_anon=%s nr_file=%s",
+ __entry->nid,
+ __entry->memcg_id,
+ __entry->max_seq,
+ __entry->anon_min_seq,
+ __entry->file_min_seq,
+ __print_array(__entry->nr_anon, MAX_NR_GENS, sizeof(unsigned long)),
+ __print_array(__entry->nr_file, MAX_NR_GENS, sizeof(unsigned long)))
+);
#endif /* CONFIG_LRU_GEN */
TRACE_EVENT(mm_vmscan_write_folio,
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 1581b73aec56..841fefa68d1f 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4115,6 +4115,8 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness
WRITE_ONCE(lrugen->timestamps[next], jiffies);
/* make sure preceding modifications appear */
smp_store_release(&lrugen->max_seq, lrugen->max_seq + 1);
+
+ trace_mm_mglru_inc_max_seq(lruvec);
unlock:
lruvec_unlock_irq(lruvec);
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq()
2026-11-03 2:56 ` [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq() Ridong Chen
@ 2026-11-03 6:48 ` Barry Song
2026-11-03 8:38 ` Ridong Chen
0 siblings, 1 reply; 14+ messages in thread
From: Barry Song @ 2026-11-03 6:48 UTC (permalink / raw)
To: Ridong Chen
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On Tue, Nov 3, 2026 at 10:58 AM Ridong Chen <ridong.chen@linux.dev> wrote:
>
> From: Ridong Chen <chenridong@xiaomi.com>
>
> Aging in MGLRU advances max_seq via inc_max_seq(), creating a new
> youngest generation. There is currently no tracepoint on this path, so
> the moment a new generation is created, and how the min_seq of each type
> trails behind it, cannot be observed as it happens.
We might be more interested in why aging is triggered rather
than the exact moment when max_seq is advanced.
With your patch, we get:
~ # cat /sys/kernel/tracing/trace | grep mglru_inc_max_seq
a.out-93 [001] d..1. 278.276539:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=4 anon_min_seq=2
file_min_seq=1 nr_anon={0x0,0xf016,0x12,0x0}
nr_file={0x1886,0xb,0x4c,0x0}
kswapd0-50 [000] d..1. 278.422400:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=5 anon_min_seq=2
file_min_seq=2 nr_anon={0x5,0xccb6,0x3439,0x0}
nr_file={0x4ae,0x4f,0x55,0x0}
kswapd0-50 [000] d..1. 278.508003:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=6 anon_min_seq=3
file_min_seq=4 nr_anon={0xccb6,0x3d29,0x1,0x0}
nr_file={0x0,0x59,0x7,0x0}
kswapd0-50 [000] d..1. 280.046056:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=7 anon_min_seq=4
file_min_seq=5 nr_anon={0x3bf3,0xcbbd,0x23e,0x0}
nr_file={0x0,0xb,0x1,0x0}
kswapd0-50 [000] d..1. 280.124876:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=8 anon_min_seq=5
file_min_seq=6 nr_anon={0xcafd,0x3e31,0x0,0x0}
nr_file={0x0,0x5,0x7,0x0}
a.out-96 [001] d..1. 290.629014:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=9 anon_min_seq=7
file_min_seq=6 nr_anon={0x0,0x10615,0xb,0x0}
nr_file={0x331,0xb,0x9a,0x0}
kswapd0-50 [000] d..1. 290.653098:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=10 anon_min_seq=7
file_min_seq=7 nr_anon={0x15,0xef23,0x18c4,0x0}
nr_file={0xb,0xb8,0x55,0x0}
a.out-96 [001] d..1. 290.684875:
mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=11 anon_min_seq=8
file_min_seq=9 nr_anon={0xeee3,0x1afb,0x2,0x0}
nr_file={0x0,0x76,0x4,0x0}
You can see that the youngest generation is always zero,
because you're just getting the newly added youngest
generation :-)
This somehow suggests that the information is redundant, as
there is always a zero there.
I suspect we'll need an additional tracepoint to show why
aging is triggered and at what sc->priority it happens.
But do you know why you trace the moment just after aging
is completed, rather than the reason why aging is needed?
Best,
Regards
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq()
2026-11-03 6:48 ` Barry Song
@ 2026-11-03 8:38 ` Ridong Chen
2026-11-03 10:00 ` Barry Song
0 siblings, 1 reply; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 8:38 UTC (permalink / raw)
To: Barry Song
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On 11/3/2026 2:48 PM, Barry Song wrote:
> On Tue, Nov 3, 2026 at 10:58 AM Ridong Chen <ridong.chen@linux.dev> wrote:
>>
>> From: Ridong Chen <chenridong@xiaomi.com>
>>
>> Aging in MGLRU advances max_seq via inc_max_seq(), creating a new
>> youngest generation. There is currently no tracepoint on this path, so
>> the moment a new generation is created, and how the min_seq of each type
>> trails behind it, cannot be observed as it happens.
>
> We might be more interested in why aging is triggered rather
> than the exact moment when max_seq is advanced.
>
> With your patch, we get:
>
> ~ # cat /sys/kernel/tracing/trace | grep mglru_inc_max_seq
> a.out-93 [001] d..1. 278.276539:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=4 anon_min_seq=2
> file_min_seq=1 nr_anon={0x0,0xf016,0x12,0x0}
> nr_file={0x1886,0xb,0x4c,0x0}
> kswapd0-50 [000] d..1. 278.422400:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=5 anon_min_seq=2
> file_min_seq=2 nr_anon={0x5,0xccb6,0x3439,0x0}
> nr_file={0x4ae,0x4f,0x55,0x0}
> kswapd0-50 [000] d..1. 278.508003:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=6 anon_min_seq=3
> file_min_seq=4 nr_anon={0xccb6,0x3d29,0x1,0x0}
> nr_file={0x0,0x59,0x7,0x0}
> kswapd0-50 [000] d..1. 280.046056:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=7 anon_min_seq=4
> file_min_seq=5 nr_anon={0x3bf3,0xcbbd,0x23e,0x0}
> nr_file={0x0,0xb,0x1,0x0}
> kswapd0-50 [000] d..1. 280.124876:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=8 anon_min_seq=5
> file_min_seq=6 nr_anon={0xcafd,0x3e31,0x0,0x0}
> nr_file={0x0,0x5,0x7,0x0}
> a.out-96 [001] d..1. 290.629014:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=9 anon_min_seq=7
> file_min_seq=6 nr_anon={0x0,0x10615,0xb,0x0}
> nr_file={0x331,0xb,0x9a,0x0}
> kswapd0-50 [000] d..1. 290.653098:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=10 anon_min_seq=7
> file_min_seq=7 nr_anon={0x15,0xef23,0x18c4,0x0}
> nr_file={0xb,0xb8,0x55,0x0}
> a.out-96 [001] d..1. 290.684875:
> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=11 anon_min_seq=8
> file_min_seq=9 nr_anon={0xeee3,0x1afb,0x2,0x0}
> nr_file={0x0,0x76,0x4,0x0}
>
> You can see that the youngest generation is always zero,
> because you're just getting the newly added youngest
> generation :-)
>
Hi Barry,
Thank you very much for testing and reviewing.
> This somehow suggests that the information is redundant, as
> there is always a zero there.
>
> I suspect we'll need an additional tracepoint to show why
> aging is triggered and at what sc->priority it happens.
>
> But do you know why you trace the moment just after aging
> is completed, rather than the reason why aging is needed?
>
My original goal was to pair it with mm_mglru_scan_folios, to see how
the aging window of each memcg moves. The per-generation anon/file
nr_pages were added following your suggestion on v1. I put it at the
end of inc_max_seq() because of the restart there, as max_seq and
min_seq are only final after that.
You make a very good point that the reason for aging is also
important. So I'd like to discuss which way is better:
1) Keep mm_mglru_inc_max_seq, and add another tracepoint at the aging
decision in try_to_shrink_lruvec(), reporting why aging is needed
and at what sc->priority.
2) Replace mm_mglru_inc_max_seq with a single tracepoint in
try_to_shrink_lruvec(), right before try_to_inc_max_seq(), reporting
the reason, sc->priority, swappiness, max_seq/min_seq and the
per-generation nr_pages before aging. The youngest generation is
not empty at that point, and the new window after aging can still
be seen from the following mm_mglru_scan_folios.
Which one do you prefer?
--
Best regards
Ridong
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq()
2026-11-03 8:38 ` Ridong Chen
@ 2026-11-03 10:00 ` Barry Song
2026-11-03 12:23 ` Ridong Chen
0 siblings, 1 reply; 14+ messages in thread
From: Barry Song @ 2026-11-03 10:00 UTC (permalink / raw)
To: Ridong Chen
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On Tue, Nov 3, 2026 at 4:39 PM Ridong Chen <ridong.chen@linux.dev> wrote:
>
>
>
> On 11/3/2026 2:48 PM, Barry Song wrote:
> > On Tue, Nov 3, 2026 at 10:58 AM Ridong Chen <ridong.chen@linux.dev> wrote:
> >>
> >> From: Ridong Chen <chenridong@xiaomi.com>
> >>
> >> Aging in MGLRU advances max_seq via inc_max_seq(), creating a new
> >> youngest generation. There is currently no tracepoint on this path, so
> >> the moment a new generation is created, and how the min_seq of each type
> >> trails behind it, cannot be observed as it happens.
> >
> > We might be more interested in why aging is triggered rather
> > than the exact moment when max_seq is advanced.
> >
> > With your patch, we get:
> >
> > ~ # cat /sys/kernel/tracing/trace | grep mglru_inc_max_seq
> > a.out-93 [001] d..1. 278.276539:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=4 anon_min_seq=2
> > file_min_seq=1 nr_anon={0x0,0xf016,0x12,0x0}
> > nr_file={0x1886,0xb,0x4c,0x0}
> > kswapd0-50 [000] d..1. 278.422400:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=5 anon_min_seq=2
> > file_min_seq=2 nr_anon={0x5,0xccb6,0x3439,0x0}
> > nr_file={0x4ae,0x4f,0x55,0x0}
> > kswapd0-50 [000] d..1. 278.508003:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=6 anon_min_seq=3
> > file_min_seq=4 nr_anon={0xccb6,0x3d29,0x1,0x0}
> > nr_file={0x0,0x59,0x7,0x0}
> > kswapd0-50 [000] d..1. 280.046056:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=7 anon_min_seq=4
> > file_min_seq=5 nr_anon={0x3bf3,0xcbbd,0x23e,0x0}
> > nr_file={0x0,0xb,0x1,0x0}
> > kswapd0-50 [000] d..1. 280.124876:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=8 anon_min_seq=5
> > file_min_seq=6 nr_anon={0xcafd,0x3e31,0x0,0x0}
> > nr_file={0x0,0x5,0x7,0x0}
> > a.out-96 [001] d..1. 290.629014:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=9 anon_min_seq=7
> > file_min_seq=6 nr_anon={0x0,0x10615,0xb,0x0}
> > nr_file={0x331,0xb,0x9a,0x0}
> > kswapd0-50 [000] d..1. 290.653098:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=10 anon_min_seq=7
> > file_min_seq=7 nr_anon={0x15,0xef23,0x18c4,0x0}
> > nr_file={0xb,0xb8,0x55,0x0}
> > a.out-96 [001] d..1. 290.684875:
> > mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=11 anon_min_seq=8
> > file_min_seq=9 nr_anon={0xeee3,0x1afb,0x2,0x0}
> > nr_file={0x0,0x76,0x4,0x0}
> >
> > You can see that the youngest generation is always zero,
> > because you're just getting the newly added youngest
> > generation :-)
> >
>
> Hi Barry,
>
> Thank you very much for testing and reviewing.
>
> > This somehow suggests that the information is redundant, as
> > there is always a zero there.
> >
> > I suspect we'll need an additional tracepoint to show why
> > aging is triggered and at what sc->priority it happens.
> >
> > But do you know why you trace the moment just after aging
> > is completed, rather than the reason why aging is needed?
> >
> My original goal was to pair it with mm_mglru_scan_folios, to see how
> the aging window of each memcg moves. The per-generation anon/file
> nr_pages were added following your suggestion on v1. I put it at the
> end of inc_max_seq() because of the restart there, as max_seq and
> min_seq are only final after that.
The per-generation anon/file nr_pages are perfectly good, as
they can also help explain why aging is needed, especially
now that we're introducing new code for imbalance-based
aging.
>
> You make a very good point that the reason for aging is also
> important. So I'd like to discuss which way is better:
>
> 1) Keep mm_mglru_inc_max_seq, and add another tracepoint at the aging
> decision in try_to_shrink_lruvec(), reporting why aging is needed
> and at what sc->priority.
>
> 2) Replace mm_mglru_inc_max_seq with a single tracepoint in
> try_to_shrink_lruvec(), right before try_to_inc_max_seq(), reporting
> the reason, sc->priority, swappiness, max_seq/min_seq and the
> per-generation nr_pages before aging. The youngest generation is
> not empty at that point, and the new window after aging can still
> be seen from the following mm_mglru_scan_folios.
>
> Which one do you prefer?
Maybe we should keep option 2 for now. Understanding why
we are aging and at what priority is much more important,
especially since we're still introducing some
imbalance-based aging code.
It should be quite simple, just a matter of moving the code
around a little.
If we later find a good reason to have option 1, we can
always add it, right?
Best,
Barry
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 7/7] mm/mglru: add tracepoint for inc_max_seq()
2026-11-03 10:00 ` Barry Song
@ 2026-11-03 12:23 ` Ridong Chen
0 siblings, 0 replies; 14+ messages in thread
From: Ridong Chen @ 2026-11-03 12:23 UTC (permalink / raw)
To: Barry Song
Cc: Steven Rostedt, Masami Hiramatsu, Andrew Morton, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Dave Chinner,
Mathieu Desnoyers, Muchun Song, Qi Zheng, David Hildenbrand,
Lorenzo Stoakes, Kairui Song, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Baoquan He, Baolin Wang, linux-kernel,
linux-trace-kernel, linux-mm, cgroups,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_),
Ridong Chen
On 11/3/2026 6:00 PM, Barry Song wrote:
> On Tue, Nov 3, 2026 at 4:39 PM Ridong Chen <ridong.chen@linux.dev> wrote:
>>
>>
>>
>> On 11/3/2026 2:48 PM, Barry Song wrote:
>>> On Tue, Nov 3, 2026 at 10:58 AM Ridong Chen <ridong.chen@linux.dev> wrote:
>>>>
>>>> From: Ridong Chen <chenridong@xiaomi.com>
>>>>
>>>> Aging in MGLRU advances max_seq via inc_max_seq(), creating a new
>>>> youngest generation. There is currently no tracepoint on this path, so
>>>> the moment a new generation is created, and how the min_seq of each type
>>>> trails behind it, cannot be observed as it happens.
>>>
>>> We might be more interested in why aging is triggered rather
>>> than the exact moment when max_seq is advanced.
>>>
>>> With your patch, we get:
>>>
>>> ~ # cat /sys/kernel/tracing/trace | grep mglru_inc_max_seq
>>> a.out-93 [001] d..1. 278.276539:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=4 anon_min_seq=2
>>> file_min_seq=1 nr_anon={0x0,0xf016,0x12,0x0}
>>> nr_file={0x1886,0xb,0x4c,0x0}
>>> kswapd0-50 [000] d..1. 278.422400:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=5 anon_min_seq=2
>>> file_min_seq=2 nr_anon={0x5,0xccb6,0x3439,0x0}
>>> nr_file={0x4ae,0x4f,0x55,0x0}
>>> kswapd0-50 [000] d..1. 278.508003:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=6 anon_min_seq=3
>>> file_min_seq=4 nr_anon={0xccb6,0x3d29,0x1,0x0}
>>> nr_file={0x0,0x59,0x7,0x0}
>>> kswapd0-50 [000] d..1. 280.046056:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=7 anon_min_seq=4
>>> file_min_seq=5 nr_anon={0x3bf3,0xcbbd,0x23e,0x0}
>>> nr_file={0x0,0xb,0x1,0x0}
>>> kswapd0-50 [000] d..1. 280.124876:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=8 anon_min_seq=5
>>> file_min_seq=6 nr_anon={0xcafd,0x3e31,0x0,0x0}
>>> nr_file={0x0,0x5,0x7,0x0}
>>> a.out-96 [001] d..1. 290.629014:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=9 anon_min_seq=7
>>> file_min_seq=6 nr_anon={0x0,0x10615,0xb,0x0}
>>> nr_file={0x331,0xb,0x9a,0x0}
>>> kswapd0-50 [000] d..1. 290.653098:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=10 anon_min_seq=7
>>> file_min_seq=7 nr_anon={0x15,0xef23,0x18c4,0x0}
>>> nr_file={0xb,0xb8,0x55,0x0}
>>> a.out-96 [001] d..1. 290.684875:
>>> mm_mglru_inc_max_seq: nid=0 memcg_id=1 max_seq=11 anon_min_seq=8
>>> file_min_seq=9 nr_anon={0xeee3,0x1afb,0x2,0x0}
>>> nr_file={0x0,0x76,0x4,0x0}
>>>
>>> You can see that the youngest generation is always zero,
>>> because you're just getting the newly added youngest
>>> generation :-)
>>>
>>
>> Hi Barry,
>>
>> Thank you very much for testing and reviewing.
>>
>>> This somehow suggests that the information is redundant, as
>>> there is always a zero there.
>>>
>>> I suspect we'll need an additional tracepoint to show why
>>> aging is triggered and at what sc->priority it happens.
>>>
>>> But do you know why you trace the moment just after aging
>>> is completed, rather than the reason why aging is needed?
>>>
>> My original goal was to pair it with mm_mglru_scan_folios, to see how
>> the aging window of each memcg moves. The per-generation anon/file
>> nr_pages were added following your suggestion on v1. I put it at the
>> end of inc_max_seq() because of the restart there, as max_seq and
>> min_seq are only final after that.
>
> The per-generation anon/file nr_pages are perfectly good, as
> they can also help explain why aging is needed, especially
> now that we're introducing new code for imbalance-based
> aging.
>
>>
>> You make a very good point that the reason for aging is also
>> important. So I'd like to discuss which way is better:
>>
>> 1) Keep mm_mglru_inc_max_seq, and add another tracepoint at the aging
>> decision in try_to_shrink_lruvec(), reporting why aging is needed
>> and at what sc->priority.
>>
>> 2) Replace mm_mglru_inc_max_seq with a single tracepoint in
>> try_to_shrink_lruvec(), right before try_to_inc_max_seq(), reporting
>> the reason, sc->priority, swappiness, max_seq/min_seq and the
>> per-generation nr_pages before aging. The youngest generation is
>> not empty at that point, and the new window after aging can still
>> be seen from the following mm_mglru_scan_folios.
>>
>> Which one do you prefer?
>
> Maybe we should keep option 2 for now. Understanding why
> we are aging and at what priority is much more important,
> especially since we're still introducing some
> imbalance-based aging code.
>
> It should be quite simple, just a matter of moving the code
> around a little.
>
> If we later find a good reason to have option 1, we can
> always add it, right?
>
Hi Barry,
Agreed, I'll go with option 2. Two things I'd like to confirm first:
1) To report the reason, should_run_aging() would need to return it
instead of a bool, e.g. an enum like:
enum lru_gen_aging_reason {
AGING_NONE,
AGING_NO_EVICTABLE, /* eviction is not possible anymore */
AGING_MIN_GENS, /* better to age, eviction still possible */
};
Or just report sc->priority, swappiness, max_seq/min_seq and the
per-generation nr_pages, and let users derive the reason from them?
The former is more direct, the latter keeps should_run_aging()
unchanged.
2) try_to_shrink_lruvec() has changed quite a lot recently, e.g.
Kairui's reclaim loop rework, and your swappiness series also touches it,
including the aging part. Is it stable enough now to carry a tracepoint there?
--
Best regards
Ridong
^ permalink raw reply [flat|nested] 14+ messages in thread