* [PATCH v2] mm/migrate_device: add class-based migration trace events
@ 2026-08-28 4:08 Balbir Singh
2026-08-28 4:13 ` Matthew Brost
0 siblings, 1 reply; 3+ messages in thread
From: Balbir Singh @ 2026-08-28 4:08 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, Balbir Singh, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Andrew Morton, David Hildenbrand, Zi Yan,
Matthew Brost, Joshua Hahn, Rakie Kim, Byungchul Park,
Gregory Price, Ying Huang, Alistair Popple
Device-private migrations currently require ad-hoc instrumentation to
understand selection, unmap, copy, and finalization behavior.
Add reusable ftrace event classes for migrate_vma ranges, device
batches, and individual folios. Emit start, done traces with
ranges, flags, page counts, success/failure summaries, PFNs,
and migrate flags.
Enabling all traces can make the migration traces verbose, when used
they can also help debug issues with device migration.
Signed-off-by: Balbir Singh <balbirs@nvidia.com>
Suggested-by: Steven Rostedt <rostedt@goodmis.org>
Assisted-by: Codex:gpt-5.6-sol
Link: https://lore.kernel.org/linux-mm/20251110161954.47d88433@gandalf.local.home/
Cc: Steven Rostedt <rostedt@goodmis.org>
CC: Masami Hiramatsu <mhiramat@kernel.org>
CC: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: David Hildenbrand <david@kernel.org>
CC: Zi Yan <ziy@nvidia.com>
CC: Matthew Brost <matthew.brost@intel.com>
CC: Joshua Hahn <joshua.hahnjy@gmail.com>
CC: Rakie Kim <rakie.kim@sk.com>
CC: Byungchul Park <byungchul@sk.com>
CC: Gregory Price <gourry@gourry.net>
CC: Ying Huang <ying.huang@linux.alibaba.com>
CC: Alistair Popple <apopple@nvidia.com>
---
include/trace/events/migrate_device.h | 324 ++++++++++++++++++++++++++
mm/migrate_device.c | 79 ++++++-
2 files changed, 391 insertions(+), 12 deletions(-)
create mode 100644 include/trace/events/migrate_device.h
diff --git a/include/trace/events/migrate_device.h b/include/trace/events/migrate_device.h
new file mode 100644
index 000000000000..46582c919dc2
--- /dev/null
+++ b/include/trace/events/migrate_device.h
@@ -0,0 +1,324 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM migrate_device
+
+#if !defined(_TRACE_MIGRATE_DEVICE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_MIGRATE_DEVICE_H
+
+#include <linux/migrate.h>
+#include <linux/tracepoint.h>
+
+/*
+ * Define enums for tracing information.
+ */
+#ifndef __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY
+#define __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY
+
+/*
+ * Why a page table walk callback classified the range the way it did.
+ * Several call sites collect the same kind of range, so the reason is
+ * what distinguishes them in a trace.
+ */
+enum migrate_vma_walk_reason {
+ MIGRATE_VMA_WALK_HOLE,
+ MIGRATE_VMA_WALK_HOLE_COMPOUND,
+ MIGRATE_VMA_WALK_HUGE_PMD,
+ MIGRATE_VMA_WALK_SKIP_NOT_ANON,
+ MIGRATE_VMA_WALK_SKIP_NOT_SELECTED,
+ MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH,
+ MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED,
+ MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL,
+ MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED,
+};
+
+#endif /* __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY */
+
+TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_SYSTEM);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_DEVICE_PRIVATE);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_DEVICE_COHERENT);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_COMPOUND);
+
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HOLE);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HOLE_COMPOUND);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HUGE_PMD);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_NOT_ANON);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
+TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
+
+#define show_walk_reason(reason) \
+ __print_symbolic(reason, \
+ { MIGRATE_VMA_WALK_HOLE, "HOLE" }, \
+ { MIGRATE_VMA_WALK_HOLE_COMPOUND, "HOLE_COMPOUND" }, \
+ { MIGRATE_VMA_WALK_HUGE_PMD, "HUGE_PMD" }, \
+ { MIGRATE_VMA_WALK_SKIP_NOT_ANON, "NOT_ANON" }, \
+ { MIGRATE_VMA_WALK_SKIP_NOT_SELECTED, "NOT_SELECTED" }, \
+ { MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH, "OWNER_MISMATCH" }, \
+ { MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED, "LOCK_CONTENDED" }, \
+ { MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL, "COMPOUND_TAIL" }, \
+ { MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED, "SPLIT_FAILED" })
+
+#define show_migrate_vma_flags(flags) \
+ __print_flags(flags, "|", \
+ { MIGRATE_VMA_SELECT_SYSTEM, "SYSTEM" }, \
+ { MIGRATE_VMA_SELECT_DEVICE_PRIVATE, "DEVICE_PRIVATE" }, \
+ { MIGRATE_VMA_SELECT_DEVICE_COHERENT, "DEVICE_COHERENT" }, \
+ { MIGRATE_VMA_SELECT_COMPOUND, "COMPOUND" })
+
+DECLARE_EVENT_CLASS(migrate_vma_range_class,
+
+ TP_PROTO(const struct migrate_vma *migrate),
+
+ TP_ARGS(migrate),
+
+ TP_STRUCT__entry(
+ __field(void *, vma)
+ __field(void *, mm)
+ __field(void *, src)
+ __field(void *, dst)
+ __field(void *, pgmap_owner)
+ __field(void *, fault_page)
+ __field(unsigned long, start)
+ __field(unsigned long, end)
+ __field(unsigned long, npages)
+ __field(unsigned long, cpages)
+ __field(unsigned long, flags)
+ ),
+
+ TP_fast_assign(
+ __entry->vma = migrate->vma;
+ __entry->mm = migrate->vma ? migrate->vma->vm_mm : NULL;
+ __entry->src = migrate->src;
+ __entry->dst = migrate->dst;
+ __entry->pgmap_owner = migrate->pgmap_owner;
+ __entry->fault_page = migrate->fault_page;
+ __entry->start = migrate->start;
+ __entry->end = migrate->end;
+ __entry->npages = migrate->npages;
+ __entry->cpages = migrate->cpages;
+ __entry->flags = migrate->flags;
+ ),
+
+ TP_printk("mm=%p vma=%p range=%#lx-%#lx bytes=%lu npages=%lu cpages=%lu flags=%s src=%p dst=%p owner=%p fault_page=%p",
+ __entry->mm, __entry->vma, __entry->start, __entry->end,
+ __entry->end - __entry->start, __entry->npages,
+ __entry->cpages,
+ show_migrate_vma_flags(__entry->flags),
+ __entry->src, __entry->dst, __entry->pgmap_owner,
+ __entry->fault_page)
+);
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_setup_start,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_setup_done,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_collect_start,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_collect_done,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_pages_start,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_pages_done,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_finalize_start,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+DEFINE_EVENT(migrate_vma_range_class, migrate_vma_finalize_done,
+ TP_PROTO(const struct migrate_vma *migrate),
+ TP_ARGS(migrate));
+
+/*
+ * Page table walk callbacks act on a sub-range of the migration and can be
+ * reached from several call sites, so they report the range they were handed
+ * and why, in addition to the migrate-wide state.
+ */
+DECLARE_EVENT_CLASS(migrate_vma_walk_class,
+
+ TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
+ unsigned long end, enum migrate_vma_walk_reason reason),
+
+ TP_ARGS(migrate, start, end, reason),
+
+ TP_STRUCT__entry(
+ __field(void *, vma)
+ __field(void *, mm)
+ __field(void *, src)
+ __field(void *, dst)
+ __field(void *, pgmap_owner)
+ __field(void *, fault_page)
+ __field(unsigned long, walk_start)
+ __field(unsigned long, walk_end)
+ __field(unsigned long, start)
+ __field(unsigned long, end)
+ __field(unsigned long, npages)
+ __field(unsigned long, cpages)
+ __field(unsigned long, flags)
+ __field(unsigned int, reason)
+ ),
+
+ TP_fast_assign(
+ __entry->vma = migrate->vma;
+ __entry->mm = migrate->vma ? migrate->vma->vm_mm : NULL;
+ __entry->src = migrate->src;
+ __entry->dst = migrate->dst;
+ __entry->pgmap_owner = migrate->pgmap_owner;
+ __entry->fault_page = migrate->fault_page;
+ __entry->walk_start = start;
+ __entry->walk_end = end;
+ __entry->start = migrate->start;
+ __entry->end = migrate->end;
+ __entry->npages = migrate->npages;
+ __entry->cpages = migrate->cpages;
+ __entry->flags = migrate->flags;
+ __entry->reason = reason;
+ ),
+
+ TP_printk("mm=%p vma=%p walk=%#lx-%#lx bytes=%lu reason=%s range=%#lx-%#lx npages=%lu cpages=%lu flags=%s src=%p dst=%p owner=%p fault_page=%p",
+ __entry->mm, __entry->vma,
+ __entry->walk_start, __entry->walk_end,
+ __entry->walk_end - __entry->walk_start,
+ show_walk_reason(__entry->reason),
+ __entry->start, __entry->end, __entry->npages,
+ __entry->cpages,
+ show_migrate_vma_flags(__entry->flags),
+ __entry->src, __entry->dst, __entry->pgmap_owner,
+ __entry->fault_page)
+);
+
+DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_skip,
+ TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
+ unsigned long end, enum migrate_vma_walk_reason reason),
+ TP_ARGS(migrate, start, end, reason));
+
+DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_hole,
+ TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
+ unsigned long end, enum migrate_vma_walk_reason reason),
+ TP_ARGS(migrate, start, end, reason));
+
+DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_huge_pmd,
+ TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
+ unsigned long end, enum migrate_vma_walk_reason reason),
+ TP_ARGS(migrate, start, end, reason));
+
+DECLARE_EVENT_CLASS(migrate_device_batch_class,
+
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+
+ TP_ARGS(src, dst, npages),
+
+ TP_STRUCT__entry(
+ __field(void *, src)
+ __field(void *, dst)
+ __field(unsigned long, src_head)
+ __field(unsigned long, dst_head)
+ __field(unsigned long, npages)
+ ),
+
+ TP_fast_assign(
+ __entry->src = (void *)src;
+ __entry->dst = (void *)dst;
+ __entry->src_head = npages && src ? src[0] : 0;
+ __entry->dst_head = npages && dst ? dst[0] : 0;
+ __entry->npages = npages;
+ ),
+
+ TP_printk("src=%p dst=%p npages=%lu src_head=%#lx dst_head=%#lx",
+ __entry->src, __entry->dst, __entry->npages,
+ __entry->src_head, __entry->dst_head)
+);
+
+DEFINE_EVENT(migrate_device_batch_class, migrate_device_unmap_done,
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+ TP_ARGS(src, dst, npages));
+
+DEFINE_EVENT(migrate_device_batch_class, migrate_device_pages_start,
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+ TP_ARGS(src, dst, npages));
+
+DEFINE_EVENT(migrate_device_batch_class, migrate_device_pages_done,
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+ TP_ARGS(src, dst, npages));
+
+DEFINE_EVENT(migrate_device_batch_class, migrate_device_finalize_start,
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+ TP_ARGS(src, dst, npages));
+
+DEFINE_EVENT(migrate_device_batch_class, migrate_device_finalize_done,
+ TP_PROTO(const unsigned long *src, const unsigned long *dst,
+ unsigned long npages),
+ TP_ARGS(src, dst, npages));
+
+DECLARE_EVENT_CLASS(migrate_device_folio_class,
+
+ TP_PROTO(unsigned long index, unsigned long nr_pages,
+ unsigned long src, unsigned long dst),
+
+ TP_ARGS(index, nr_pages, src, dst),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, index)
+ __field(unsigned long, nr_pages)
+ __field(unsigned long, src)
+ __field(unsigned long, dst)
+ __field(unsigned long, src_pfn)
+ __field(unsigned long, dst_pfn)
+ ),
+
+ TP_fast_assign(
+ __entry->index = index;
+ __entry->nr_pages = nr_pages;
+ __entry->src = src;
+ __entry->dst = dst;
+ __entry->src_pfn = src >> MIGRATE_PFN_SHIFT;
+ __entry->dst_pfn = dst >> MIGRATE_PFN_SHIFT;
+ ),
+
+ TP_printk("index=%lu nr_pages=%lu src=%#lx src_pfn=%#lx src_flags=%s dst=%#lx dst_pfn=%#lx dst_flags=%s",
+ __entry->index, __entry->nr_pages, __entry->src,
+ __entry->src_pfn,
+ __print_flags(__entry->src & ((1UL << MIGRATE_PFN_SHIFT) - 1),
+ "|",
+ { MIGRATE_PFN_VALID, "VALID" },
+ { MIGRATE_PFN_MIGRATE, "MIGRATE" },
+ { MIGRATE_PFN_WRITE, "WRITE" },
+ { MIGRATE_PFN_COMPOUND, "COMPOUND" }),
+ __entry->dst, __entry->dst_pfn,
+ __print_flags(__entry->dst & ((1UL << MIGRATE_PFN_SHIFT) - 1),
+ "|",
+ { MIGRATE_PFN_VALID, "VALID" },
+ { MIGRATE_PFN_MIGRATE, "MIGRATE" },
+ { MIGRATE_PFN_WRITE, "WRITE" },
+ { MIGRATE_PFN_COMPOUND, "COMPOUND" }))
+);
+
+DEFINE_EVENT(migrate_device_folio_class, migrate_device_folio_finalize,
+ TP_PROTO(unsigned long index, unsigned long nr_pages,
+ unsigned long src, unsigned long dst),
+ TP_ARGS(index, nr_pages, src, dst));
+
+#endif /* _TRACE_MIGRATE_DEVICE_H */
+
+#include <trace/define_trace.h>
diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 009bfa8b212d..cf2a78d2e18e 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -16,11 +16,16 @@
#include <linux/leafops.h>
#include <linux/pgalloc.h>
#include <asm/tlbflush.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/migrate_device.h>
+
#include "internal.h"
static int migrate_vma_collect_skip(unsigned long start,
unsigned long end,
- struct mm_walk *walk)
+ struct mm_walk *walk,
+ enum migrate_vma_walk_reason reason)
{
struct migrate_vma *migrate = walk->private;
unsigned long addr;
@@ -30,6 +35,7 @@ static int migrate_vma_collect_skip(unsigned long start,
migrate->src[migrate->npages++] = 0;
}
+ trace_migrate_vma_collect_skip(migrate, start, end, reason);
return 0;
}
@@ -43,7 +49,8 @@ static int migrate_vma_collect_hole(unsigned long start,
/* Only allow populating anonymous memory. */
if (!vma_is_anonymous(walk->vma))
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_NOT_ANON);
if (thp_migration_supported() &&
(migrate->flags & MIGRATE_VMA_SELECT_COMPOUND) &&
@@ -55,11 +62,15 @@ static int migrate_vma_collect_hole(unsigned long start,
migrate->npages++;
migrate->cpages++;
+ trace_migrate_vma_collect_hole(migrate, start, end,
+ MIGRATE_VMA_WALK_HOLE_COMPOUND);
+
/*
* Collect the remaining entries as holes, in case we
* need to split later
*/
- return migrate_vma_collect_skip(start + PAGE_SIZE, end, walk);
+ return migrate_vma_collect_skip(start + PAGE_SIZE, end, walk,
+ MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
}
for (addr = start; addr < end; addr += PAGE_SIZE) {
@@ -69,6 +80,8 @@ static int migrate_vma_collect_hole(unsigned long start,
migrate->cpages++;
}
+ trace_migrate_vma_collect_hole(migrate, start, end,
+ MIGRATE_VMA_WALK_HOLE);
return 0;
}
@@ -154,7 +167,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
if (pmd_trans_huge(*pmdp)) {
if (!(migrate->flags & MIGRATE_VMA_SELECT_SYSTEM)) {
spin_unlock(ptl);
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
}
folio = pmd_folio(*pmdp);
@@ -170,13 +184,15 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
if (!softleaf_is_device_private(entry) ||
!(migrate->flags & MIGRATE_VMA_SELECT_DEVICE_PRIVATE)) {
spin_unlock(ptl);
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
}
folio = softleaf_to_folio(entry);
if (folio->pgmap->owner != migrate->pgmap_owner) {
spin_unlock(ptl);
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH);
}
if (softleaf_is_device_private_write(entry))
@@ -190,7 +206,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
if (folio != fault_folio && unlikely(!folio_trylock(folio))) {
spin_unlock(ptl);
folio_put(folio);
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED);
}
if (thp_migration_supported() &&
@@ -220,8 +237,12 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
migrate->dst[migrate->npages] = 0;
goto fallback;
}
- migrate_vma_collect_skip(start + PAGE_SIZE, end, walk);
+ migrate_vma_collect_skip(start + PAGE_SIZE, end, walk,
+ MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
spin_unlock(ptl);
+
+ trace_migrate_vma_collect_huge_pmd(migrate, start, end,
+ MIGRATE_VMA_WALK_HUGE_PMD);
return 0;
}
@@ -234,7 +255,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
folio_unlock(folio);
folio_put(folio);
if (ret)
- return migrate_vma_collect_skip(start, end, walk);
+ return migrate_vma_collect_skip(start, end, walk,
+ MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
if (pmd_none(pmdp_get_lockless(pmdp)))
return migrate_vma_collect_hole(start, end, -1, walk);
@@ -323,7 +345,9 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
if (unmapped)
flush_tlb_range(walk->vma, start, end);
- return migrate_vma_collect_skip(addr, end, walk);
+ return migrate_vma_collect_skip(addr,
+ end, walk,
+ MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
}
goto again;
@@ -369,7 +393,9 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
if (unmapped)
flush_tlb_range(walk->vma, start, end);
- return migrate_vma_collect_skip(addr, end, walk);
+ return migrate_vma_collect_skip(addr,
+ end, walk,
+ MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
}
goto again;
@@ -514,6 +540,8 @@ static void migrate_vma_collect(struct migrate_vma *migrate)
{
struct mmu_notifier_range range;
+ trace_migrate_vma_collect_start(migrate);
+
/*
* Note that the pgmap_owner is passed to the mmu notifier callback so
* that the registered device driver can skip invalidating device
@@ -529,6 +557,7 @@ static void migrate_vma_collect(struct migrate_vma *migrate)
mmu_notifier_invalidate_range_end(&range);
migrate->end = migrate->start + (migrate->npages << PAGE_SHIFT);
+ trace_migrate_vma_collect_done(migrate);
}
/*
@@ -653,6 +682,7 @@ static unsigned long migrate_device_unmap(unsigned long *src_pfns,
restore--;
}
+ trace_migrate_device_unmap_done(src_pfns, NULL, npages);
return unmapped;
}
@@ -763,11 +793,13 @@ int migrate_vma_setup(struct migrate_vma *args)
memset(args->src, 0, sizeof(*args->src) * nr_pages);
args->cpages = 0;
args->npages = 0;
+ trace_migrate_vma_setup_start(args);
migrate_vma_collect(args);
if (args->cpages)
migrate_vma_unmap(args);
+ trace_migrate_vma_setup_done(args);
/*
* At this point pages are locked and unmapped, and thus they have
@@ -1115,7 +1147,9 @@ static void __migrate_device_pages(unsigned long *src_pfns,
bool notified = false;
unsigned long addr;
- for (i = 0; i < npages; ) {
+ trace_migrate_device_pages_start(src_pfns, dst_pfns, npages);
+
+ for (i = 0; i < npages;) {
struct page *newpage = migrate_pfn_to_page(dst_pfns[i]);
struct page *page = migrate_pfn_to_page(src_pfns[i]);
struct address_space *mapping;
@@ -1257,6 +1291,8 @@ static void __migrate_device_pages(unsigned long *src_pfns,
if (notified)
mmu_notifier_invalidate_range_end(&range);
+
+ trace_migrate_device_pages_done(src_pfns, dst_pfns, npages);
}
/**
@@ -1285,7 +1321,9 @@ EXPORT_SYMBOL(migrate_device_pages);
*/
void migrate_vma_pages(struct migrate_vma *migrate)
{
+ trace_migrate_vma_pages_start(migrate);
__migrate_device_pages(migrate->src, migrate->dst, migrate->npages, migrate);
+ trace_migrate_vma_pages_done(migrate);
}
EXPORT_SYMBOL(migrate_vma_pages);
@@ -1298,11 +1336,24 @@ static void __migrate_device_finalize(unsigned long *src_pfns,
page_folio(fault_page) : NULL;
unsigned long i;
+ trace_migrate_device_finalize_start(src_pfns, dst_pfns, npages);
+
for (i = 0; i < npages; i++) {
struct folio *dst = NULL, *src = NULL;
struct page *newpage = migrate_pfn_to_page(dst_pfns[i]);
struct page *page = migrate_pfn_to_page(src_pfns[i]);
+ if (trace_migrate_device_folio_finalize_enabled() &&
+ (page || newpage)) {
+ unsigned long nr_pages = page ?
+ folio_nr_pages(page_folio(page)) :
+ folio_nr_pages(page_folio(newpage));
+
+ trace_migrate_device_folio_finalize(i, nr_pages,
+ src_pfns[i],
+ dst_pfns[i]);
+ }
+
if (newpage)
dst = page_folio(newpage);
@@ -1339,6 +1390,8 @@ static void __migrate_device_finalize(unsigned long *src_pfns,
folio_put(dst);
}
}
+
+ trace_migrate_device_finalize_done(src_pfns, dst_pfns, npages);
}
/*
@@ -1371,8 +1424,10 @@ EXPORT_SYMBOL(migrate_device_finalize);
*/
void migrate_vma_finalize(struct migrate_vma *migrate)
{
+ trace_migrate_vma_finalize_start(migrate);
__migrate_device_finalize(migrate->src, migrate->dst, migrate->npages,
migrate->fault_page);
+ trace_migrate_vma_finalize_done(migrate);
}
EXPORT_SYMBOL(migrate_vma_finalize);
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] mm/migrate_device: add class-based migration trace events
2026-08-28 4:08 [PATCH v2] mm/migrate_device: add class-based migration trace events Balbir Singh
@ 2026-08-28 4:13 ` Matthew Brost
2026-08-28 4:16 ` Balbir Singh
0 siblings, 1 reply; 3+ messages in thread
From: Matthew Brost @ 2026-08-28 4:13 UTC (permalink / raw)
To: Balbir Singh
Cc: linux-mm, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Andrew Morton, David Hildenbrand, Zi Yan,
Joshua Hahn, Rakie Kim, Byungchul Park, Gregory Price,
Ying Huang, Alistair Popple
On Fri, Aug 28, 2026 at 02:08:17PM +1000, Balbir Singh wrote:
> Device-private migrations currently require ad-hoc instrumentation to
> understand selection, unmap, copy, and finalization behavior.
>
> Add reusable ftrace event classes for migrate_vma ranges, device
> batches, and individual folios. Emit start, done traces with
> ranges, flags, page counts, success/failure summaries, PFNs,
> and migrate flags.
>
> Enabling all traces can make the migration traces verbose, when used
> they can also help debug issues with device migration.
>
> Signed-off-by: Balbir Singh <balbirs@nvidia.com>
> Suggested-by: Steven Rostedt <rostedt@goodmis.org>
This seems like a pretty good adding tracepoints - anytime I've had to
debug issues in migrate_device.c it has basically start adding printks to
figure out what is going on. Hopefully witth the right tracepoints, this
would now be turn on ftrace.
So +1 on the idea.
In the middle of a few other things, but will circle back with an in
depth look at individual tracepoints added here when a bit more time.
Matt
> Assisted-by: Codex:gpt-5.6-sol
> Link: https://lore.kernel.org/linux-mm/20251110161954.47d88433@gandalf.local.home/
>
> Cc: Steven Rostedt <rostedt@goodmis.org>
> CC: Masami Hiramatsu <mhiramat@kernel.org>
> CC: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> CC: Andrew Morton <akpm@linux-foundation.org>
> CC: David Hildenbrand <david@kernel.org>
> CC: Zi Yan <ziy@nvidia.com>
> CC: Matthew Brost <matthew.brost@intel.com>
> CC: Joshua Hahn <joshua.hahnjy@gmail.com>
> CC: Rakie Kim <rakie.kim@sk.com>
> CC: Byungchul Park <byungchul@sk.com>
> CC: Gregory Price <gourry@gourry.net>
> CC: Ying Huang <ying.huang@linux.alibaba.com>
> CC: Alistair Popple <apopple@nvidia.com>
> ---
> include/trace/events/migrate_device.h | 324 ++++++++++++++++++++++++++
> mm/migrate_device.c | 79 ++++++-
> 2 files changed, 391 insertions(+), 12 deletions(-)
> create mode 100644 include/trace/events/migrate_device.h
>
> diff --git a/include/trace/events/migrate_device.h b/include/trace/events/migrate_device.h
> new file mode 100644
> index 000000000000..46582c919dc2
> --- /dev/null
> +++ b/include/trace/events/migrate_device.h
> @@ -0,0 +1,324 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
> + */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM migrate_device
> +
> +#if !defined(_TRACE_MIGRATE_DEVICE_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_MIGRATE_DEVICE_H
> +
> +#include <linux/migrate.h>
> +#include <linux/tracepoint.h>
> +
> +/*
> + * Define enums for tracing information.
> + */
> +#ifndef __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY
> +#define __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY
> +
> +/*
> + * Why a page table walk callback classified the range the way it did.
> + * Several call sites collect the same kind of range, so the reason is
> + * what distinguishes them in a trace.
> + */
> +enum migrate_vma_walk_reason {
> + MIGRATE_VMA_WALK_HOLE,
> + MIGRATE_VMA_WALK_HOLE_COMPOUND,
> + MIGRATE_VMA_WALK_HUGE_PMD,
> + MIGRATE_VMA_WALK_SKIP_NOT_ANON,
> + MIGRATE_VMA_WALK_SKIP_NOT_SELECTED,
> + MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH,
> + MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED,
> + MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL,
> + MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED,
> +};
> +
> +#endif /* __MIGRATE_DEVICE_DECLARE_TRACE_ENUMS_ONCE_ONLY */
> +
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_SYSTEM);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_DEVICE_PRIVATE);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_DEVICE_COHERENT);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_SELECT_COMPOUND);
> +
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HOLE);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HOLE_COMPOUND);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_HUGE_PMD);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_NOT_ANON);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
> +TRACE_DEFINE_ENUM(MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
> +
> +#define show_walk_reason(reason) \
> + __print_symbolic(reason, \
> + { MIGRATE_VMA_WALK_HOLE, "HOLE" }, \
> + { MIGRATE_VMA_WALK_HOLE_COMPOUND, "HOLE_COMPOUND" }, \
> + { MIGRATE_VMA_WALK_HUGE_PMD, "HUGE_PMD" }, \
> + { MIGRATE_VMA_WALK_SKIP_NOT_ANON, "NOT_ANON" }, \
> + { MIGRATE_VMA_WALK_SKIP_NOT_SELECTED, "NOT_SELECTED" }, \
> + { MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH, "OWNER_MISMATCH" }, \
> + { MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED, "LOCK_CONTENDED" }, \
> + { MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL, "COMPOUND_TAIL" }, \
> + { MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED, "SPLIT_FAILED" })
> +
> +#define show_migrate_vma_flags(flags) \
> + __print_flags(flags, "|", \
> + { MIGRATE_VMA_SELECT_SYSTEM, "SYSTEM" }, \
> + { MIGRATE_VMA_SELECT_DEVICE_PRIVATE, "DEVICE_PRIVATE" }, \
> + { MIGRATE_VMA_SELECT_DEVICE_COHERENT, "DEVICE_COHERENT" }, \
> + { MIGRATE_VMA_SELECT_COMPOUND, "COMPOUND" })
> +
> +DECLARE_EVENT_CLASS(migrate_vma_range_class,
> +
> + TP_PROTO(const struct migrate_vma *migrate),
> +
> + TP_ARGS(migrate),
> +
> + TP_STRUCT__entry(
> + __field(void *, vma)
> + __field(void *, mm)
> + __field(void *, src)
> + __field(void *, dst)
> + __field(void *, pgmap_owner)
> + __field(void *, fault_page)
> + __field(unsigned long, start)
> + __field(unsigned long, end)
> + __field(unsigned long, npages)
> + __field(unsigned long, cpages)
> + __field(unsigned long, flags)
> + ),
> +
> + TP_fast_assign(
> + __entry->vma = migrate->vma;
> + __entry->mm = migrate->vma ? migrate->vma->vm_mm : NULL;
> + __entry->src = migrate->src;
> + __entry->dst = migrate->dst;
> + __entry->pgmap_owner = migrate->pgmap_owner;
> + __entry->fault_page = migrate->fault_page;
> + __entry->start = migrate->start;
> + __entry->end = migrate->end;
> + __entry->npages = migrate->npages;
> + __entry->cpages = migrate->cpages;
> + __entry->flags = migrate->flags;
> + ),
> +
> + TP_printk("mm=%p vma=%p range=%#lx-%#lx bytes=%lu npages=%lu cpages=%lu flags=%s src=%p dst=%p owner=%p fault_page=%p",
> + __entry->mm, __entry->vma, __entry->start, __entry->end,
> + __entry->end - __entry->start, __entry->npages,
> + __entry->cpages,
> + show_migrate_vma_flags(__entry->flags),
> + __entry->src, __entry->dst, __entry->pgmap_owner,
> + __entry->fault_page)
> +);
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_setup_start,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_setup_done,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_collect_start,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_collect_done,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_pages_start,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_pages_done,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_finalize_start,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +DEFINE_EVENT(migrate_vma_range_class, migrate_vma_finalize_done,
> + TP_PROTO(const struct migrate_vma *migrate),
> + TP_ARGS(migrate));
> +
> +/*
> + * Page table walk callbacks act on a sub-range of the migration and can be
> + * reached from several call sites, so they report the range they were handed
> + * and why, in addition to the migrate-wide state.
> + */
> +DECLARE_EVENT_CLASS(migrate_vma_walk_class,
> +
> + TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
> + unsigned long end, enum migrate_vma_walk_reason reason),
> +
> + TP_ARGS(migrate, start, end, reason),
> +
> + TP_STRUCT__entry(
> + __field(void *, vma)
> + __field(void *, mm)
> + __field(void *, src)
> + __field(void *, dst)
> + __field(void *, pgmap_owner)
> + __field(void *, fault_page)
> + __field(unsigned long, walk_start)
> + __field(unsigned long, walk_end)
> + __field(unsigned long, start)
> + __field(unsigned long, end)
> + __field(unsigned long, npages)
> + __field(unsigned long, cpages)
> + __field(unsigned long, flags)
> + __field(unsigned int, reason)
> + ),
> +
> + TP_fast_assign(
> + __entry->vma = migrate->vma;
> + __entry->mm = migrate->vma ? migrate->vma->vm_mm : NULL;
> + __entry->src = migrate->src;
> + __entry->dst = migrate->dst;
> + __entry->pgmap_owner = migrate->pgmap_owner;
> + __entry->fault_page = migrate->fault_page;
> + __entry->walk_start = start;
> + __entry->walk_end = end;
> + __entry->start = migrate->start;
> + __entry->end = migrate->end;
> + __entry->npages = migrate->npages;
> + __entry->cpages = migrate->cpages;
> + __entry->flags = migrate->flags;
> + __entry->reason = reason;
> + ),
> +
> + TP_printk("mm=%p vma=%p walk=%#lx-%#lx bytes=%lu reason=%s range=%#lx-%#lx npages=%lu cpages=%lu flags=%s src=%p dst=%p owner=%p fault_page=%p",
> + __entry->mm, __entry->vma,
> + __entry->walk_start, __entry->walk_end,
> + __entry->walk_end - __entry->walk_start,
> + show_walk_reason(__entry->reason),
> + __entry->start, __entry->end, __entry->npages,
> + __entry->cpages,
> + show_migrate_vma_flags(__entry->flags),
> + __entry->src, __entry->dst, __entry->pgmap_owner,
> + __entry->fault_page)
> +);
> +
> +DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_skip,
> + TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
> + unsigned long end, enum migrate_vma_walk_reason reason),
> + TP_ARGS(migrate, start, end, reason));
> +
> +DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_hole,
> + TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
> + unsigned long end, enum migrate_vma_walk_reason reason),
> + TP_ARGS(migrate, start, end, reason));
> +
> +DEFINE_EVENT(migrate_vma_walk_class, migrate_vma_collect_huge_pmd,
> + TP_PROTO(const struct migrate_vma *migrate, unsigned long start,
> + unsigned long end, enum migrate_vma_walk_reason reason),
> + TP_ARGS(migrate, start, end, reason));
> +
> +DECLARE_EVENT_CLASS(migrate_device_batch_class,
> +
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> +
> + TP_ARGS(src, dst, npages),
> +
> + TP_STRUCT__entry(
> + __field(void *, src)
> + __field(void *, dst)
> + __field(unsigned long, src_head)
> + __field(unsigned long, dst_head)
> + __field(unsigned long, npages)
> + ),
> +
> + TP_fast_assign(
> + __entry->src = (void *)src;
> + __entry->dst = (void *)dst;
> + __entry->src_head = npages && src ? src[0] : 0;
> + __entry->dst_head = npages && dst ? dst[0] : 0;
> + __entry->npages = npages;
> + ),
> +
> + TP_printk("src=%p dst=%p npages=%lu src_head=%#lx dst_head=%#lx",
> + __entry->src, __entry->dst, __entry->npages,
> + __entry->src_head, __entry->dst_head)
> +);
> +
> +DEFINE_EVENT(migrate_device_batch_class, migrate_device_unmap_done,
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> + TP_ARGS(src, dst, npages));
> +
> +DEFINE_EVENT(migrate_device_batch_class, migrate_device_pages_start,
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> + TP_ARGS(src, dst, npages));
> +
> +DEFINE_EVENT(migrate_device_batch_class, migrate_device_pages_done,
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> + TP_ARGS(src, dst, npages));
> +
> +DEFINE_EVENT(migrate_device_batch_class, migrate_device_finalize_start,
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> + TP_ARGS(src, dst, npages));
> +
> +DEFINE_EVENT(migrate_device_batch_class, migrate_device_finalize_done,
> + TP_PROTO(const unsigned long *src, const unsigned long *dst,
> + unsigned long npages),
> + TP_ARGS(src, dst, npages));
> +
> +DECLARE_EVENT_CLASS(migrate_device_folio_class,
> +
> + TP_PROTO(unsigned long index, unsigned long nr_pages,
> + unsigned long src, unsigned long dst),
> +
> + TP_ARGS(index, nr_pages, src, dst),
> +
> + TP_STRUCT__entry(
> + __field(unsigned long, index)
> + __field(unsigned long, nr_pages)
> + __field(unsigned long, src)
> + __field(unsigned long, dst)
> + __field(unsigned long, src_pfn)
> + __field(unsigned long, dst_pfn)
> + ),
> +
> + TP_fast_assign(
> + __entry->index = index;
> + __entry->nr_pages = nr_pages;
> + __entry->src = src;
> + __entry->dst = dst;
> + __entry->src_pfn = src >> MIGRATE_PFN_SHIFT;
> + __entry->dst_pfn = dst >> MIGRATE_PFN_SHIFT;
> + ),
> +
> + TP_printk("index=%lu nr_pages=%lu src=%#lx src_pfn=%#lx src_flags=%s dst=%#lx dst_pfn=%#lx dst_flags=%s",
> + __entry->index, __entry->nr_pages, __entry->src,
> + __entry->src_pfn,
> + __print_flags(__entry->src & ((1UL << MIGRATE_PFN_SHIFT) - 1),
> + "|",
> + { MIGRATE_PFN_VALID, "VALID" },
> + { MIGRATE_PFN_MIGRATE, "MIGRATE" },
> + { MIGRATE_PFN_WRITE, "WRITE" },
> + { MIGRATE_PFN_COMPOUND, "COMPOUND" }),
> + __entry->dst, __entry->dst_pfn,
> + __print_flags(__entry->dst & ((1UL << MIGRATE_PFN_SHIFT) - 1),
> + "|",
> + { MIGRATE_PFN_VALID, "VALID" },
> + { MIGRATE_PFN_MIGRATE, "MIGRATE" },
> + { MIGRATE_PFN_WRITE, "WRITE" },
> + { MIGRATE_PFN_COMPOUND, "COMPOUND" }))
> +);
> +
> +DEFINE_EVENT(migrate_device_folio_class, migrate_device_folio_finalize,
> + TP_PROTO(unsigned long index, unsigned long nr_pages,
> + unsigned long src, unsigned long dst),
> + TP_ARGS(index, nr_pages, src, dst));
> +
> +#endif /* _TRACE_MIGRATE_DEVICE_H */
> +
> +#include <trace/define_trace.h>
> diff --git a/mm/migrate_device.c b/mm/migrate_device.c
> index 009bfa8b212d..cf2a78d2e18e 100644
> --- a/mm/migrate_device.c
> +++ b/mm/migrate_device.c
> @@ -16,11 +16,16 @@
> #include <linux/leafops.h>
> #include <linux/pgalloc.h>
> #include <asm/tlbflush.h>
> +
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/migrate_device.h>
> +
> #include "internal.h"
>
> static int migrate_vma_collect_skip(unsigned long start,
> unsigned long end,
> - struct mm_walk *walk)
> + struct mm_walk *walk,
> + enum migrate_vma_walk_reason reason)
> {
> struct migrate_vma *migrate = walk->private;
> unsigned long addr;
> @@ -30,6 +35,7 @@ static int migrate_vma_collect_skip(unsigned long start,
> migrate->src[migrate->npages++] = 0;
> }
>
> + trace_migrate_vma_collect_skip(migrate, start, end, reason);
> return 0;
> }
>
> @@ -43,7 +49,8 @@ static int migrate_vma_collect_hole(unsigned long start,
>
> /* Only allow populating anonymous memory. */
> if (!vma_is_anonymous(walk->vma))
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_NOT_ANON);
>
> if (thp_migration_supported() &&
> (migrate->flags & MIGRATE_VMA_SELECT_COMPOUND) &&
> @@ -55,11 +62,15 @@ static int migrate_vma_collect_hole(unsigned long start,
> migrate->npages++;
> migrate->cpages++;
>
> + trace_migrate_vma_collect_hole(migrate, start, end,
> + MIGRATE_VMA_WALK_HOLE_COMPOUND);
> +
> /*
> * Collect the remaining entries as holes, in case we
> * need to split later
> */
> - return migrate_vma_collect_skip(start + PAGE_SIZE, end, walk);
> + return migrate_vma_collect_skip(start + PAGE_SIZE, end, walk,
> + MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
> }
>
> for (addr = start; addr < end; addr += PAGE_SIZE) {
> @@ -69,6 +80,8 @@ static int migrate_vma_collect_hole(unsigned long start,
> migrate->cpages++;
> }
>
> + trace_migrate_vma_collect_hole(migrate, start, end,
> + MIGRATE_VMA_WALK_HOLE);
> return 0;
> }
>
> @@ -154,7 +167,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
> if (pmd_trans_huge(*pmdp)) {
> if (!(migrate->flags & MIGRATE_VMA_SELECT_SYSTEM)) {
> spin_unlock(ptl);
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
> }
>
> folio = pmd_folio(*pmdp);
> @@ -170,13 +184,15 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
> if (!softleaf_is_device_private(entry) ||
> !(migrate->flags & MIGRATE_VMA_SELECT_DEVICE_PRIVATE)) {
> spin_unlock(ptl);
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_NOT_SELECTED);
> }
>
> folio = softleaf_to_folio(entry);
> if (folio->pgmap->owner != migrate->pgmap_owner) {
> spin_unlock(ptl);
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_OWNER_MISMATCH);
> }
>
> if (softleaf_is_device_private_write(entry))
> @@ -190,7 +206,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
> if (folio != fault_folio && unlikely(!folio_trylock(folio))) {
> spin_unlock(ptl);
> folio_put(folio);
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_LOCK_CONTENDED);
> }
>
> if (thp_migration_supported() &&
> @@ -220,8 +237,12 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
> migrate->dst[migrate->npages] = 0;
> goto fallback;
> }
> - migrate_vma_collect_skip(start + PAGE_SIZE, end, walk);
> + migrate_vma_collect_skip(start + PAGE_SIZE, end, walk,
> + MIGRATE_VMA_WALK_SKIP_COMPOUND_TAIL);
> spin_unlock(ptl);
> +
> + trace_migrate_vma_collect_huge_pmd(migrate, start, end,
> + MIGRATE_VMA_WALK_HUGE_PMD);
> return 0;
> }
>
> @@ -234,7 +255,8 @@ static int migrate_vma_collect_huge_pmd(pmd_t *pmdp, unsigned long start,
> folio_unlock(folio);
> folio_put(folio);
> if (ret)
> - return migrate_vma_collect_skip(start, end, walk);
> + return migrate_vma_collect_skip(start, end, walk,
> + MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
> if (pmd_none(pmdp_get_lockless(pmdp)))
> return migrate_vma_collect_hole(start, end, -1, walk);
>
> @@ -323,7 +345,9 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
> if (unmapped)
> flush_tlb_range(walk->vma, start, end);
>
> - return migrate_vma_collect_skip(addr, end, walk);
> + return migrate_vma_collect_skip(addr,
> + end, walk,
> + MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
> }
>
> goto again;
> @@ -369,7 +393,9 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,
> if (unmapped)
> flush_tlb_range(walk->vma, start, end);
>
> - return migrate_vma_collect_skip(addr, end, walk);
> + return migrate_vma_collect_skip(addr,
> + end, walk,
> + MIGRATE_VMA_WALK_SKIP_SPLIT_FAILED);
> }
>
> goto again;
> @@ -514,6 +540,8 @@ static void migrate_vma_collect(struct migrate_vma *migrate)
> {
> struct mmu_notifier_range range;
>
> + trace_migrate_vma_collect_start(migrate);
> +
> /*
> * Note that the pgmap_owner is passed to the mmu notifier callback so
> * that the registered device driver can skip invalidating device
> @@ -529,6 +557,7 @@ static void migrate_vma_collect(struct migrate_vma *migrate)
>
> mmu_notifier_invalidate_range_end(&range);
> migrate->end = migrate->start + (migrate->npages << PAGE_SHIFT);
> + trace_migrate_vma_collect_done(migrate);
> }
>
> /*
> @@ -653,6 +682,7 @@ static unsigned long migrate_device_unmap(unsigned long *src_pfns,
> restore--;
> }
>
> + trace_migrate_device_unmap_done(src_pfns, NULL, npages);
> return unmapped;
> }
>
> @@ -763,11 +793,13 @@ int migrate_vma_setup(struct migrate_vma *args)
> memset(args->src, 0, sizeof(*args->src) * nr_pages);
> args->cpages = 0;
> args->npages = 0;
> + trace_migrate_vma_setup_start(args);
>
> migrate_vma_collect(args);
>
> if (args->cpages)
> migrate_vma_unmap(args);
> + trace_migrate_vma_setup_done(args);
>
> /*
> * At this point pages are locked and unmapped, and thus they have
> @@ -1115,7 +1147,9 @@ static void __migrate_device_pages(unsigned long *src_pfns,
> bool notified = false;
> unsigned long addr;
>
> - for (i = 0; i < npages; ) {
> + trace_migrate_device_pages_start(src_pfns, dst_pfns, npages);
> +
> + for (i = 0; i < npages;) {
> struct page *newpage = migrate_pfn_to_page(dst_pfns[i]);
> struct page *page = migrate_pfn_to_page(src_pfns[i]);
> struct address_space *mapping;
> @@ -1257,6 +1291,8 @@ static void __migrate_device_pages(unsigned long *src_pfns,
>
> if (notified)
> mmu_notifier_invalidate_range_end(&range);
> +
> + trace_migrate_device_pages_done(src_pfns, dst_pfns, npages);
> }
>
> /**
> @@ -1285,7 +1321,9 @@ EXPORT_SYMBOL(migrate_device_pages);
> */
> void migrate_vma_pages(struct migrate_vma *migrate)
> {
> + trace_migrate_vma_pages_start(migrate);
> __migrate_device_pages(migrate->src, migrate->dst, migrate->npages, migrate);
> + trace_migrate_vma_pages_done(migrate);
> }
> EXPORT_SYMBOL(migrate_vma_pages);
>
> @@ -1298,11 +1336,24 @@ static void __migrate_device_finalize(unsigned long *src_pfns,
> page_folio(fault_page) : NULL;
> unsigned long i;
>
> + trace_migrate_device_finalize_start(src_pfns, dst_pfns, npages);
> +
> for (i = 0; i < npages; i++) {
> struct folio *dst = NULL, *src = NULL;
> struct page *newpage = migrate_pfn_to_page(dst_pfns[i]);
> struct page *page = migrate_pfn_to_page(src_pfns[i]);
>
> + if (trace_migrate_device_folio_finalize_enabled() &&
> + (page || newpage)) {
> + unsigned long nr_pages = page ?
> + folio_nr_pages(page_folio(page)) :
> + folio_nr_pages(page_folio(newpage));
> +
> + trace_migrate_device_folio_finalize(i, nr_pages,
> + src_pfns[i],
> + dst_pfns[i]);
> + }
> +
> if (newpage)
> dst = page_folio(newpage);
>
> @@ -1339,6 +1390,8 @@ static void __migrate_device_finalize(unsigned long *src_pfns,
> folio_put(dst);
> }
> }
> +
> + trace_migrate_device_finalize_done(src_pfns, dst_pfns, npages);
> }
>
> /*
> @@ -1371,8 +1424,10 @@ EXPORT_SYMBOL(migrate_device_finalize);
> */
> void migrate_vma_finalize(struct migrate_vma *migrate)
> {
> + trace_migrate_vma_finalize_start(migrate);
> __migrate_device_finalize(migrate->src, migrate->dst, migrate->npages,
> migrate->fault_page);
> + trace_migrate_vma_finalize_done(migrate);
> }
> EXPORT_SYMBOL(migrate_vma_finalize);
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] mm/migrate_device: add class-based migration trace events
2026-08-28 4:13 ` Matthew Brost
@ 2026-08-28 4:16 ` Balbir Singh
0 siblings, 0 replies; 3+ messages in thread
From: Balbir Singh @ 2026-08-28 4:16 UTC (permalink / raw)
To: Matthew Brost
Cc: linux-mm, linux-kernel, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Andrew Morton, David Hildenbrand, Zi Yan,
Joshua Hahn, Rakie Kim, Byungchul Park, Gregory Price,
Ying Huang, Alistair Popple
On 8/28/26 2:13 PM, Matthew Brost wrote:
> On Fri, Aug 28, 2026 at 02:08:17PM +1000, Balbir Singh wrote:
>> Device-private migrations currently require ad-hoc instrumentation to
>> understand selection, unmap, copy, and finalization behavior.
>>
>> Add reusable ftrace event classes for migrate_vma ranges, device
>> batches, and individual folios. Emit start, done traces with
>> ranges, flags, page counts, success/failure summaries, PFNs,
>> and migrate flags.
>>
>> Enabling all traces can make the migration traces verbose, when used
>> they can also help debug issues with device migration.
>>
>> Signed-off-by: Balbir Singh <balbirs@nvidia.com>
>> Suggested-by: Steven Rostedt <rostedt@goodmis.org>
>
> This seems like a pretty good adding tracepoints - anytime I've had to
> debug issues in migrate_device.c it has basically start adding printks to
> figure out what is going on. Hopefully witth the right tracepoints, this
> would now be turn on ftrace.
>
> So +1 on the idea.
>
> In the middle of a few other things, but will circle back with an in
> depth look at individual tracepoints added here when a bit more time.
Thanks, I am hoping to add more tests and use these for debugging
corner cases and hopefully get easier debug for issues reported.
Balbir
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-28 4:16 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-28 4:08 [PATCH v2] mm/migrate_device: add class-based migration trace events Balbir Singh
2026-08-28 4:13 ` Matthew Brost
2026-08-28 4:16 ` Balbir Singh
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®