From: "Kiryl Shutsemau (Meta)" <[email protected]> The fault-in pass is the one place a collapse does work on someone else's behalf: a swap read, or a CoW break, for every slot that needs one. How much of that a round pays is invisible, and it is the first thing to look at when collapses are slow, or when a workload notices khugepaged at all.
Add mm_collapse_faultin: the faults taken across the round, with the outcome. A round that collapses a full table without faulting anything and one that reads sixty-four pages back from swap are otherwise indistinguishable. The mm is captured before the walk, because the pass returns with mmap_lock dropped on failure and the VMA is then unsafe to touch at the report. Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]> --- include/trace/events/huge_memory.h | 24 ++++++++++++++++++++++++ mm/collapse.c | 19 ++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h index 573cf5428969..c2314e26111c 100644 --- a/include/trace/events/huge_memory.h +++ b/include/trace/events/huge_memory.h @@ -160,6 +160,30 @@ TRACE_EVENT(mm_collapse_scan, __print_symbolic(__entry->result, SCAN_STATUS)) ); +TRACE_EVENT(mm_collapse_faultin, + + TP_PROTO(struct mm_struct *mm, unsigned int nr_faults, int result), + + TP_ARGS(mm, nr_faults, result), + + TP_STRUCT__entry( + __field(struct mm_struct *, mm) + __field(unsigned int, nr_faults) + __field(int, result) + ), + + TP_fast_assign( + __entry->mm = mm; + __entry->nr_faults = nr_faults; + __entry->result = result; + ), + + TP_printk("mm=%p, nr_faults=%u, result=%s", + __entry->mm, + __entry->nr_faults, + __print_symbolic(__entry->result, SCAN_STATUS)) +); + TRACE_EVENT(mm_collapse_candidate, TP_PROTO(struct mm_struct *mm, unsigned long addr, unsigned int order, diff --git a/mm/collapse.c b/mm/collapse.c index b750a1fc81a5..1b5db42b6991 100644 --- a/mm/collapse.c +++ b/mm/collapse.c @@ -509,8 +509,10 @@ static enum scan_result collapse_revalidate(struct vm_area_struct *vma, /* * Bring one address to a state the freeze will accept: present, and exclusive if - * it is anonymous. Returns with mmap_lock dropped on every failure, because the - * fault path may drop it and the caller cannot tell which case it is in. + * it is anonymous. Every fault it takes to get there counts in *nr_faults, each + * one an allocation or a read the round is paying for. Returns with mmap_lock + * dropped on every failure, because the fault path may drop it and the caller + * cannot tell which case it is in. * * SCAN_EXCEED_SWAP_PTE is the exception: it is a verdict on this candidate * rather than on the round, nothing was faulted to reach it, and it keeps the @@ -518,7 +520,8 @@ static enum scan_result collapse_revalidate(struct vm_area_struct *vma, */ static enum scan_result collapse_faultin_addr(struct vm_area_struct *vma, struct collapse_candidate *cand, - pmd_t *pmd, unsigned long addr) + pmd_t *pmd, unsigned long addr, + unsigned int *nr_faults) { struct mm_struct *mm = vma->vm_mm; const unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_UNSHARE | @@ -571,6 +574,7 @@ static enum scan_result collapse_faultin_addr(struct vm_area_struct *vma, /* Only swap or shared PTEs reach here; the rest broke out */ ret = handle_mm_fault(vma, addr, flags, NULL); + (*nr_faults)++; /* * Not a verdict on this window: the fault dropped the lock to * wait, which is what a swap-in normally does. Distinct from @@ -600,7 +604,9 @@ static enum scan_result collapse_faultin(struct vm_area_struct *vma, struct collapse_control *cc, pmd_t *pmd) { + struct mm_struct *mm = vma->vm_mm; enum scan_result result = SCAN_SUCCEED; + unsigned int nr_faults = 0; unsigned int i; for (i = 0; i < cc->nr_candidates; i++) { @@ -616,7 +622,8 @@ static enum scan_result collapse_faultin(struct vm_area_struct *vma, j++, addr += PAGE_SIZE) { enum scan_result r; - r = collapse_faultin_addr(vma, cand, pmd, addr); + r = collapse_faultin_addr(vma, cand, pmd, addr, + &nr_faults); /* * The one failure that judges this candidate rather * than the round, and so the one that leaves the lock @@ -627,7 +634,7 @@ static enum scan_result collapse_faultin(struct vm_area_struct *vma, if (r == SCAN_EXCEED_SWAP_PTE) { cand->state = CAND_SKIPPED; cand->result = r; - collapse_trace_candidate(vma->vm_mm, cand, + collapse_trace_candidate(mm, cand, COLLAPSE_PASS_FAULTIN); break; } @@ -638,6 +645,8 @@ static enum scan_result collapse_faultin(struct vm_area_struct *vma, } } out: + /* @vma is unsafe on the failure path: the callee dropped mmap_lock */ + trace_mm_collapse_faultin(mm, nr_faults, result); return result; } -- 2.54.0
