[RFC PATCH 37/57] mm/collapse: report what the fault-in pass paid

From: Kiryl Shutsemau

Date: Sun Aug 16 2026 - 18:53:37 EST


From: "Kiryl Shutsemau (Meta)" <kas@xxxxxxxxxx>

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) <kas@xxxxxxxxxx>
---
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