From: Ridong Chen <[email protected]> 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, with the memcg id, the new max_seq, the anon and file min_seq, and the number of pages in each generation for both types, summed over zones the same way the debugfs lru_gen file reports them. The nr_anon/nr_file arrays are indexed by generation slot, so the emitted max_seq/min_seq say which slot holds which seq. They are printed with __print_array(), so the output is hex and its length follows MAX_NR_GENS automatically. Paired with the mm_mglru_scan_folios tracepoint it makes the full aging-to-eviction window observable per memcg. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ridong Chen <[email protected]> --- include/trace/events/vmscan.h | 38 +++++++++++++++++++++++++++++++++++ mm/vmscan.c | 23 +++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/include/trace/events/vmscan.h b/include/trace/events/vmscan.h index 5defa8f6719c..a20c6b2e062f 100644 --- a/include/trace/events/vmscan.h +++ b/include/trace/events/vmscan.h @@ -455,6 +455,44 @@ TRACE_EVENT(mm_mglru_scan_folios, __entry->min_seq) ); +TRACE_EVENT(mm_mglru_inc_max_seq, + + TP_PROTO(struct lruvec *lruvec, + unsigned long max_seq, + unsigned long anon_min_seq, + unsigned long file_min_seq, + unsigned long *nr_anon, + unsigned long *nr_file), + + TP_ARGS(lruvec, max_seq, anon_min_seq, file_min_seq, nr_anon, nr_file), + + 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) + ), + + TP_fast_assign( + __entry->memcg_id = mem_cgroup_id(lruvec_memcg(lruvec)); + __entry->max_seq = max_seq; + __entry->anon_min_seq = anon_min_seq; + __entry->file_min_seq = file_min_seq; + memcpy(__entry->nr_anon, nr_anon, sizeof(__entry->nr_anon)); + memcpy(__entry->nr_file, nr_file, sizeof(__entry->nr_file)); + ), + + TP_printk("memcg_id=%llu max_seq=%lu anon_min_seq=%lu file_min_seq=%lu nr_anon=%s nr_file=%s", + __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))) +); + TRACE_EVENT(mm_vmscan_write_folio, TP_PROTO(struct folio *folio), diff --git a/mm/vmscan.c b/mm/vmscan.c index 67f59aa73fb9..d1495a7d469d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4100,6 +4100,27 @@ static void try_to_inc_min_seq(struct lruvec *lruvec, int swappiness) } } +static void trace_inc_max_seq(struct lruvec *lruvec) +{ + int type, gen; + unsigned long nr[ANON_AND_FILE][MAX_NR_GENS]; + struct lru_gen_folio *lrugen; + + if (!trace_mm_mglru_inc_max_seq_enabled()) + return; + + lrugen = &lruvec->lrugen; + for (type = 0; type < ANON_AND_FILE; type++) + for (gen = 0; gen < MAX_NR_GENS; gen++) + nr[type][gen] = lru_gen_seq_nr_pages(lrugen, gen, type); + + trace_mm_mglru_inc_max_seq(lruvec, + lrugen->max_seq, + lrugen->min_seq[LRU_GEN_ANON], + lrugen->min_seq[LRU_GEN_FILE], + nr[LRU_GEN_ANON], nr[LRU_GEN_FILE]); +} + static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness) { bool success; @@ -4159,6 +4180,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_inc_max_seq(lruvec); unlock: lruvec_unlock_irq(lruvec); -- 2.34.1
