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

Kiryl Shutsemau <[email protected]>
Newsgroups org.kernel.vger.linux-trace-kernel,org.kernel.vger.bpf,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kvack.linux-mm
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.