Thread (119 messages) flat view 119 messages, 9 authors, 8d ago
COOLING8d

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

From: Kiryl Shutsemau <hidden>
Date: 2026-08-16 22:47:28
Also in: bpf, linux-kselftest, linux-mm, lkml
Subsystem: memory management, memory management - thp (transparent huge page), the rest, tracing · Maintainers: Andrew Morton, David Hildenbrand, Linus Torvalds, Steven Rostedt, Masami Hiramatsu

From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>

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@kernel.org>
---
 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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help