Re: [PATCH v7 7/7] mm/mglru: add tracepoint for aging

From: Barry Song

Date: Wed Nov 04 2026 - 17:02:08 EST


On Wed, Nov 4, 2026 at 9:10 PM Ridong Chen <ridong.chen@xxxxxxxxx> wrote:
>
> From: Ridong Chen <chenridong@xxxxxxxxxx>
>
> Aging in MGLRU advances max_seq to create a new youngest generation.
> It is triggered from try_to_shrink_lruvec() when should_run_aging()
> finds that eviction is no longer possible, or that it would be better
> to age first at the current priority. There is currently no tracepoint
> on this path, so it cannot be observed when and why aging is needed.
>
> Add mm_mglru_aging, emitted in try_to_shrink_lruvec() right before
> try_to_inc_max_seq(), with the node id, memcg id, sc->priority,
> swappiness, 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 reason for aging is not reported explicitly, as the logic in
> should_run_aging() may change, e.g. for imbalance-based aging. It can
> be derived from the generation window and the per-generation sizes,
> which are taken before aging, so the youngest generation is populated
> as well.
>
> 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. The arrays are printed with __print_array(), so the
> output is hex and its length follows MAX_NR_GENS automatically.
>
> Only the lruvec, sc and swappiness are passed in; everything else,
> including max_seq, is read in TP_fast_assign(), so try_to_shrink_lruvec()
> does no extra work when the event is disabled. Reading max_seq there
> rather than using the caller's copy keeps the window consistent with
> the per-generation sizes read right after it, should another reclaimer
> age the lruvec in between.
>
> The event marks the aging decision, not its result: the following
> try_to_inc_max_seq() may find that aging is already in progress or
> done by another reclaimer, so concurrent reclaimers can each emit it
> for the same max_seq. Whether max_seq was advanced can be seen from the
> following mm_mglru_scan_folios.
>
> Suggested-by: Barry Song <baohua@xxxxxxxxxx>
> Signed-off-by: Ridong Chen <chenridong@xxxxxxxxxx>
> ---

Much better now:

~ # echo 1 > /sys/kernel/tracing/events/vmscan/mm_mglru_aging/enable
~ # ./a.out
Mapped 400 MB at 0xffff98a00000
^C
~ # cat /sys/kernel/tracing/trace | grep mglru_aging
kswapd0-49 [001] ..... 104.277689: mm_mglru_aging:
nid=0 memcg_id=1 priority=6 swappiness=60 max_seq=3 anon_min_seq=2
file_min_seq=1 nr_anon={0x0,0x0,0xd360,0x0}
nr_file={0x0,0x23e2,0x0,0x0}
kswapd0-49 [001] ..... 104.536448: mm_mglru_aging:
nid=0 memcg_id=1 priority=3 swappiness=60 max_seq=4 anon_min_seq=2
file_min_seq=2 nr_anon={0x0,0x20,0xf8bc,0x0}
nr_file={0x0,0x91a,0xbd,0x0}
a.out-79 [000] ..... 104.689518: mm_mglru_aging:
nid=0 memcg_id=1 priority=4 swappiness=60 max_seq=5 anon_min_seq=3
file_min_seq=4 nr_anon={0x0,0xf8a0,0x1188,0x0}
nr_file={0x0,0x0,0x2,0x7}
kswapd0-49 [001] ..... 107.282038: mm_mglru_aging:
nid=0 memcg_id=1 priority=2 swappiness=60 max_seq=6 anon_min_seq=4
file_min_seq=5 nr_anon={0x0,0x6c50,0xa168,0x605}
nr_file={0x0,0x0,0x98,0x18}
kswapd0-49 [001] ..... 107.812598: mm_mglru_aging:
nid=0 memcg_id=1 priority=1 swappiness=60 max_seq=7 anon_min_seq=5
file_min_seq=6 nr_anon={0x0,0xfe10,0x1721,0x54}
nr_file={0x0,0x0,0x1a,0x3}
a.out-79 [000] ..... 108.936872: mm_mglru_aging:
nid=0 memcg_id=1 priority=2 swappiness=60 max_seq=8 anon_min_seq=6
file_min_seq=7 nr_anon={0x8,0x7d29,0x8c54,0xc90}
nr_file={0x0,0x0,0x4,0x2}
kswapd0-49 [001] ..... 108.937115: mm_mglru_aging:
nid=0 memcg_id=1 priority=2 swappiness=60 max_seq=8 anon_min_seq=6
file_min_seq=7 nr_anon={0x0,0x7d15,0x8c94,0xcac}
nr_file={0x0,0x0,0x4,0x2}
kswapd0-49 [001] ..... 109.425258: mm_mglru_aging:
nid=0 memcg_id=1 priority=1 swappiness=60 max_seq=9 anon_min_seq=7
file_min_seq=8 nr_anon={0x0,0xed69,0x24bc,0x488}
nr_file={0x0,0x0,0x3,0x3}

One thing that looks a bit weird, though. For example:

max_seq=3 anon_min_seq=2

We have only two generations for anon, but we print four:

nr_anon={0x0,0x0,0xd360,0x0}

The two oldest generations don't actually exist. Ideally,
we would print only the two existing generations:

nr_anon={0xd360,0x0}

you should be able to fix it by:

__print_array(__entry->nr_anon, __entry->max_seq -
__entry->anon_min_seq + 1 , sizeof(unsigned long)),
__print_array(__entry->nr_file, __entry->max_seq -
__entry->file_min_seq + 1, sizeof(unsigned long)))

But I guess it's fine, since the two extra entries are both
zero.

And, I personally prefer the youngest-to-oldest order,
as we usually have:

head ---------- tail

where head is young and tail is old.

Your output uses:

tail ---------- head

But anyway, I guess this is just a matter of whether we
read it from left to right or right to left. Personally, I
find it more natural to read from youngest to oldest.

If you're not tired of working on this and would like to make
a few more improvements, you could address the two issues
above. But it is up to you.

For the patch,

Reviewed-by: Barry Song <baohua@xxxxxxxxxx>