* [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check
@ 2026-03-09 1:00 Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 1/6] mm/damon: reintroduce damon_operations->cleanup() Akinobu Mita
` (6 more replies)
0 siblings, 7 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
DAMON currently only provides PTE accessed-bit based access check, this
patch series adds a new perf event based access check.
Since perf event-based access checks do not require modifying the PTE
accessed-bit for pages representing each damon region, it reduces the
overhead of monitoring at a fixed granularity of the page size.
Furthermore, this patch series also includes a feature that allows you
to set a minimum region size for each target, enabling you to monitor
at any fixed size greater than the page size for each target.
Using these features also requires modifications to damo, but these are
not included in this patch series and are currently under development in
the following branch:
* https://github.com/mita/damo/tree/damo-perf-for-v3.1.8
Any feedback or advice on the patch set would be greatly appreciated.
* v2
- reintroduce damon_operations->cleanup()
- introduce struct damon_access_report
- use struct damon_access_report instead of introducing struct
damon_perf_record
- remove maximum region size setting
* v1
- https://lore.kernel.org/damon/20260123021014.26915-1-akinobu.mita@gmail.com/T/
* TODO
- Currently, it is possible to unintentionally specify a perf_event
that cannot obtain either PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR
- Check if it works in a virtual environment using vPMU
Akinobu Mita (6):
mm/damon: reintroduce damon_operations->cleanup()
mm/damon/core: introduce struct damon_access_report
mm/damon/core: add common code for perf event based access check
mm/damon/vaddr: support perf event based access check
mm/damon/paddr: support perf event based access check
mm/damon: allow user to set min size of region
.../ABI/testing/sysfs-kernel-mm-damon | 10 +
include/linux/damon.h | 63 ++
mm/damon/core.c | 77 ++-
mm/damon/ops-common.h | 39 ++
mm/damon/paddr.c | 105 ++-
mm/damon/sysfs-common.c | 11 +
mm/damon/sysfs-common.h | 1 +
mm/damon/sysfs.c | 399 ++++++++++-
mm/damon/tests/sysfs-kunit.h | 2 +
mm/damon/tests/vaddr-kunit.h | 5 +-
mm/damon/vaddr.c | 654 +++++++++++++++++-
11 files changed, 1332 insertions(+), 34 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 1/6] mm/damon: reintroduce damon_operations->cleanup()
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report Akinobu Mita
` (5 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
Commit 50962b16c0d6 ("mm/damon: remove damon_operations->cleanup()")
removed damon_operations->cleanup(). However in order to support perf
event based access checks for physical and virtual address space
monitoring, damon_operations->cleanup() is required to release
perf events created by damon_operations->init().
---
include/linux/damon.h | 3 +++
mm/damon/core.c | 2 ++
mm/damon/paddr.c | 1 +
mm/damon/vaddr.c | 1 +
4 files changed, 7 insertions(+)
diff --git a/include/linux/damon.h b/include/linux/damon.h
index a4fea23da857..49d2983af4a8 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -607,6 +607,7 @@ enum damon_ops_id {
* @apply_scheme: Apply a DAMON-based operation scheme.
* @target_valid: Determine if the target is valid.
* @cleanup_target: Clean up each target before deallocation.
+ * @cleanup: Clean up the context.
*
* DAMON can be extended for various address spaces and usages. For this,
* users should register the low level operations for their target address
@@ -639,6 +640,7 @@ enum damon_ops_id {
* @target_valid should check whether the target is still valid for the
* monitoring.
* @cleanup_target is called before the target will be deallocated.
+ * @cleanup is called from @kdamond just before its termination.
*/
struct damon_operations {
enum damon_ops_id id;
@@ -654,6 +656,7 @@ struct damon_operations {
struct damos *scheme, unsigned long *sz_filter_passed);
bool (*target_valid)(struct damon_target *t);
void (*cleanup_target)(struct damon_target *t);
+ void (*cleanup)(struct damon_ctx *context);
};
/*
diff --git a/mm/damon/core.c b/mm/damon/core.c
index adfc52fee9dc..d0d9659658bd 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -2841,6 +2841,8 @@ static int kdamond_fn(void *data)
done:
damon_destroy_targets(ctx);
+ if (ctx->ops.cleanup)
+ ctx->ops.cleanup(ctx);
kfree(ctx->regions_score_histogram);
kdamond_call(ctx, true);
damos_walk_cancel(ctx);
diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c
index 9bfe48826840..4c2c935d82d6 100644
--- a/mm/damon/paddr.c
+++ b/mm/damon/paddr.c
@@ -373,6 +373,7 @@ static int __init damon_pa_initcall(void)
.prepare_access_checks = damon_pa_prepare_access_checks,
.check_accesses = damon_pa_check_accesses,
.target_valid = NULL,
+ .cleanup = NULL,
.apply_scheme = damon_pa_apply_scheme,
.get_scheme_score = damon_pa_scheme_score,
};
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 729b7ffd3565..8a58aa912112 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -1013,6 +1013,7 @@ static int __init damon_va_initcall(void)
.check_accesses = damon_va_check_accesses,
.target_valid = damon_va_target_valid,
.cleanup_target = damon_va_cleanup_target,
+ .cleanup = NULL,
.apply_scheme = damon_va_apply_scheme,
.get_scheme_score = damon_va_scheme_score,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 1/6] mm/damon: reintroduce damon_operations->cleanup() Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-09 15:19 ` Ian Rogers
2026-03-09 1:00 ` [RFC PATCH v2 3/6] mm/damon/core: add common code for perf event based access check Akinobu Mita
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
The struct damon_access_report is borrowed from SeongJae Park's
DAMON extension project [1].
[1] https://lore.kernel.org/linux-mm/20251208062943.68824-6-sj@kernel.org/T/
---
include/linux/damon.h | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/include/linux/damon.h b/include/linux/damon.h
index 49d2983af4a8..5f2870ae02ba 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -110,6 +110,30 @@ struct damon_target {
bool obsolete;
};
+/**
+ * struct damon_access_report - Represent single acces report information.
+ * @paddr: Start physical address of the accessed address range.
+ * @vaddr: Start virtual address of the accessed address range.
+ * @size: The size of the accessed address range.
+ * @cpu: The id of the CPU that made the access.
+ * @tid: The task id of the task that made the access.
+ * @is_write: Whether the access is write.
+ *
+ * Any DAMON API callers that notified access events can report the information
+ * to DAMON using damon_report_access(). This struct contains the reporting
+ * infomration. Refer to damon_report_access() for more details.
+ */
+struct damon_access_report {
+ unsigned long paddr;
+ unsigned long vaddr;
+ unsigned long size;
+ unsigned int cpu;
+ pid_t tid;
+ bool is_write;
+/* private: */
+ unsigned long report_jiffies; /* when this report is made */
+};
+
/**
* enum damos_action - Represents an action of a Data Access Monitoring-based
* Operation Scheme.
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 3/6] mm/damon/core: add common code for perf event based access check
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 1/6] mm/damon: reintroduce damon_operations->cleanup() Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 4/6] mm/damon/vaddr: support " Akinobu Mita
` (3 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
This patch creates the necessary infrastructure (structure definitions,
functions, sysfs interfaces) to support perf event based access checks
for both physical and virtual address space monitoring.
Perf events can be added through the following sysfs interface:
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/nr_perf_events
Description: Writing a number 'N' to this file creates the number of
directories for controlling each perf event named '0' to 'N-1' under the
perf_events/ directory.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/type
Description: Writing to and reading from this file sets and gets the
"type" field of struct perf_event_attr for perf event based access
check.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config
Description: Same as above.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config1
Description: Same as above.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config2
Description: Same as above.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/sample_freq
Description: Same as above.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/sample_phys_addr
Description: Writing to and reading from this file sets and gets the
PERF_SAMPLE_PHYS_ADDR bitfield in the "sample_type" field of struct
perf_event_attr for perf event based access check.
The perf event that can be specified for the perf event based access
check must be able to obtain the data source address corresponding to
the sample.
One way to find such perf event settings is to run `perf mem record -vv`
and look for a dump of the perf_event_attr structure given to the
perf_event_open() system call in the debug messages output.
If at least one perf event is specified, the perf event based access
check will be enabled instead of the existing PTE accessed-bit based
access check. In other words, if no perf event is specified, the
existing PTE accessed-bit based access check will be used.
Add the following common functions required for both physical and
virtual address space monitoring:
damon_ops_init()
This is the function to call in the damon_operations->init callback.
Each perf event specified by the sysfs interface is initialized with
perf_event_create_kernel_counter().
damon_ops_cleanup()
This is the function to call in the damon_operations->cleanup callback.
Release each initialized perf event with perf_event_release_kernel().
damon_perf_prepare_access_checks()
This is the function used to implement the
damon_operations->prepare_access_checks callback. Enable each
initialized perf event with perf_event_enable().
---
.../ABI/testing/sysfs-kernel-mm-damon | 8 +
include/linux/damon.h | 34 ++
mm/damon/core.c | 52 +++
mm/damon/ops-common.h | 21 +
mm/damon/paddr.c | 4 +-
mm/damon/sysfs.c | 373 +++++++++++++++++-
mm/damon/vaddr.c | 208 +++++++++-
7 files changed, 692 insertions(+), 8 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon
index f2af2ddedd32..405a1a7f1eb6 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-damon
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon
@@ -570,3 +570,11 @@ Contact: SeongJae Park <sj@kernel.org>
Description: Reading this file returns the size of the memory in the region
that passed DAMON operations layer-handled filters of the
scheme in bytes.
+
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/nr_perf_events
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/type
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config1
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config2
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/sample_phys_addr
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/sample_freq
diff --git a/include/linux/damon.h b/include/linux/damon.h
index 5f2870ae02ba..c1cf3b5b990b 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -54,6 +54,7 @@ struct damon_size_range {
* each sampling interval.
* @list: List head for siblings.
* @age: Age of this region.
+ * @accessed: Whether the region has accessed during last sampling interval.
*
* @nr_accesses is reset to zero for every &damon_attrs->aggr_interval and be
* increased for every &damon_attrs->sample_interval if an access to the region
@@ -83,6 +84,7 @@ struct damon_region {
unsigned int age;
/* private: Internal value for age calculation. */
unsigned int last_nr_accesses;
+ bool accessed;
};
/**
@@ -777,6 +779,36 @@ struct damon_attrs {
unsigned long aggr_samples;
};
+/**
+ * struct damon_perf_event_attr - raw PMU event attr for access check
+ *
+ * @type: raw PMU event type for access check
+ * @config: raw PMU event config for access check
+ * @config1: raw PMU event config1 for access check
+ * @config2: raw PMU event config2 for access check
+ * @sample_phys_addr: raw PMU event PERF_SAMPLE_PHYS_ADDR in sample_type for access check
+ * @sample_freq: raw PMU event sample_freq for access check
+ */
+struct damon_perf_event_attr {
+ u32 type;
+ u64 config;
+ u64 config1;
+ u64 config2;
+ bool sample_phys_addr;
+ u64 sample_freq;
+};
+
+/**
+ * struct damon_perf_event - perf event for access check
+ *
+ * @priv: Monitoring operations-specific data
+ */
+struct damon_perf_event {
+ struct damon_perf_event_attr attr;
+ void *priv;
+ struct list_head list;
+};
+
/**
* struct damon_ctx - Represents a context for each monitoring. This is the
* main interface that allows users to set the attributes and get the results
@@ -801,6 +833,7 @@ struct damon_attrs {
* @ops: Set of monitoring operations for given use cases.
* @addr_unit: Scale factor for core to ops address conversion.
* @min_region_sz: Minimum region size.
+ * @perf_events: Head of perf events (&damon_perf_event) list.
* @adaptive_targets: Head of monitoring targets (&damon_target) list.
* @schemes: Head of schemes (&damos) list.
*/
@@ -847,6 +880,7 @@ struct damon_ctx {
unsigned long addr_unit;
unsigned long min_region_sz;
+ struct list_head perf_events;
struct list_head adaptive_targets;
struct list_head schemes;
};
diff --git a/mm/damon/core.c b/mm/damon/core.c
index d0d9659658bd..f8e4db2e80d6 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -555,6 +555,7 @@ struct damon_ctx *damon_new_ctx(void)
ctx->addr_unit = 1;
ctx->min_region_sz = DAMON_MIN_REGION_SZ;
+ INIT_LIST_HEAD(&ctx->perf_events);
INIT_LIST_HEAD(&ctx->adaptive_targets);
INIT_LIST_HEAD(&ctx->schemes);
@@ -578,6 +579,18 @@ void damon_destroy_ctx(struct damon_ctx *ctx)
damon_for_each_scheme_safe(s, next_s, ctx)
damon_destroy_scheme(s);
+ if (ctx->ops.cleanup)
+ ctx->ops.cleanup(ctx);
+
+ while (!list_empty(&ctx->perf_events)) {
+ struct damon_perf_event *event =
+ list_first_entry(&ctx->perf_events, typeof(*event), list);
+
+ WARN_ON(event->priv);
+ list_del(&event->list);
+ kfree(event);
+ }
+
kfree(ctx);
}
@@ -1235,6 +1248,42 @@ static int damon_commit_targets(
return 0;
}
+static int damon_commit_perf_events(struct damon_ctx *dst, struct damon_ctx *src)
+{
+ struct damon_perf_event *dst_event, *src_event, *new_event;
+
+ if (dst->ops.cleanup)
+ dst->ops.cleanup(dst);
+
+ while (!list_empty(&dst->perf_events)) {
+ dst_event = list_first_entry(&dst->perf_events, typeof(*dst_event), list);
+
+ WARN_ON(dst_event->priv);
+ list_del(&dst_event->list);
+ kfree(dst_event);
+ }
+
+ list_for_each_entry(src_event, &src->perf_events, list) {
+ new_event = kzalloc(sizeof(*new_event), GFP_KERNEL);
+ if (!new_event)
+ return -ENOMEM;
+
+ new_event->attr.type = src_event->attr.type;
+ new_event->attr.config = src_event->attr.config;
+ new_event->attr.config1 = src_event->attr.config1;
+ new_event->attr.config2 = src_event->attr.config2;
+ new_event->attr.sample_phys_addr = src_event->attr.sample_phys_addr;
+ new_event->attr.sample_freq = src_event->attr.sample_freq;
+
+ list_add_tail(&new_event->list, &dst->perf_events);
+ }
+
+ if (src->ops.init)
+ src->ops.init(dst);
+
+ return 0;
+}
+
/**
* damon_commit_ctx() - Commit parameters of a DAMON context to another.
* @dst: The commit destination DAMON context.
@@ -1259,6 +1308,9 @@ int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src)
if (err)
return err;
err = damon_commit_targets(dst, src);
+ if (err)
+ return err;
+ err = damon_commit_perf_events(dst, src);
if (err)
return err;
/*
diff --git a/mm/damon/ops-common.h b/mm/damon/ops-common.h
index 5efa5b5970de..395af11b91fb 100644
--- a/mm/damon/ops-common.h
+++ b/mm/damon/ops-common.h
@@ -23,3 +23,24 @@ bool damos_folio_filter_match(struct damos_filter *filter, struct folio *folio);
unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid);
bool damos_ops_has_filter(struct damos *s);
+
+#ifdef CONFIG_PERF_EVENTS
+
+void damon_perf_prepare_access_checks(struct damon_ctx *ctx, struct damon_perf_event *event);
+
+struct damon_perf {
+ struct perf_event * __percpu *event;
+ struct damon_perf_buffer __percpu *buffer;
+};
+
+#else /* CONFIG_PERF_EVENTS */
+
+static inline void damon_perf_prepare_access_checks(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+}
+
+#endif /* CONFIG_PERF_EVENTS */
+
+void damon_ops_init(struct damon_ctx *ctx);
+void damon_ops_cleanup(struct damon_ctx *ctx);
diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c
index 4c2c935d82d6..68dcde5d423f 100644
--- a/mm/damon/paddr.c
+++ b/mm/damon/paddr.c
@@ -368,12 +368,12 @@ static int __init damon_pa_initcall(void)
{
struct damon_operations ops = {
.id = DAMON_OPS_PADDR,
- .init = NULL,
+ .init = damon_ops_init,
.update = NULL,
.prepare_access_checks = damon_pa_prepare_access_checks,
.check_accesses = damon_pa_check_accesses,
.target_valid = NULL,
- .cleanup = NULL,
+ .cleanup = damon_ops_cleanup,
.apply_scheme = damon_pa_apply_scheme,
.get_scheme_score = damon_pa_scheme_score,
};
diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c
index 576d1ddd736b..040b669d0f49 100644
--- a/mm/damon/sysfs.c
+++ b/mm/damon/sysfs.c
@@ -747,6 +747,309 @@ static const struct kobj_type damon_sysfs_intervals_ktype = {
.default_groups = damon_sysfs_intervals_groups,
};
+/*
+ * perf_event_attr directory
+ */
+
+struct damon_sysfs_perf_event_attr {
+ struct kobject kobj;
+ u32 type;
+ u64 config;
+ u64 config1;
+ u64 config2;
+ bool sample_phys_addr;
+ u64 sample_freq;
+};
+
+static struct damon_sysfs_perf_event_attr *damon_sysfs_perf_event_attr_alloc(void)
+{
+ return kzalloc(sizeof(struct damon_sysfs_perf_event_attr), GFP_KERNEL);
+}
+
+static ssize_t type_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%x\n", perf_event_attr->type);
+}
+
+static ssize_t type_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou32(buf, 0, &perf_event_attr->type);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config);
+}
+
+static ssize_t config_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config1);
+}
+
+static ssize_t config1_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config1);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t config2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "0x%llx\n", perf_event_attr->config2);
+}
+
+static ssize_t config2_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->config2);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static ssize_t sample_phys_addr_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%d\n", perf_event_attr->sample_phys_addr);
+}
+
+static ssize_t sample_phys_addr_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ bool sample_phys_addr;
+ int err = kstrtobool(buf, &sample_phys_addr);
+
+ if (err)
+ return -EINVAL;
+
+ perf_event_attr->sample_phys_addr = sample_phys_addr;
+ return count;
+}
+
+static ssize_t sample_freq_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+
+ return sysfs_emit(buf, "%llu\n", perf_event_attr->sample_freq);
+}
+
+static ssize_t sample_freq_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_event_attr *perf_event_attr = container_of(kobj,
+ struct damon_sysfs_perf_event_attr, kobj);
+ int err = kstrtou64(buf, 0, &perf_event_attr->sample_freq);
+
+ if (err)
+ return -EINVAL;
+ return count;
+}
+
+static void damon_sysfs_perf_event_attr_release(struct kobject *kobj)
+{
+ kfree(container_of(kobj, struct damon_sysfs_perf_event_attr, kobj));
+}
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_type_attr =
+ __ATTR_RW_MODE(type, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config_attr =
+ __ATTR_RW_MODE(config, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config1_attr =
+ __ATTR_RW_MODE(config1, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_config2_attr =
+ __ATTR_RW_MODE(config2, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_sample_phys_addr_attr =
+ __ATTR_RW_MODE(sample_phys_addr, 0600);
+
+static struct kobj_attribute damon_sysfs_perf_event_attr_sample_freq_attr =
+ __ATTR_RW_MODE(sample_freq, 0600);
+
+static struct attribute *damon_sysfs_perf_event_attr_attrs[] = {
+ &damon_sysfs_perf_event_attr_type_attr.attr,
+ &damon_sysfs_perf_event_attr_config_attr.attr,
+ &damon_sysfs_perf_event_attr_config1_attr.attr,
+ &damon_sysfs_perf_event_attr_config2_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_phys_addr_attr.attr,
+ &damon_sysfs_perf_event_attr_sample_freq_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(damon_sysfs_perf_event_attr);
+
+static const struct kobj_type damon_sysfs_perf_event_attr_ktype = {
+ .release = damon_sysfs_perf_event_attr_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = damon_sysfs_perf_event_attr_groups,
+};
+
+/*
+ * perf_events directory
+ */
+
+struct damon_sysfs_perf_events {
+ struct kobject kobj;
+ struct damon_sysfs_perf_event_attr **attrs_arr;
+ int nr;
+};
+
+static struct damon_sysfs_perf_events *damon_sysfs_perf_events_alloc(void)
+{
+ return kzalloc(sizeof(struct damon_sysfs_perf_events), GFP_KERNEL);
+}
+
+static void damon_sysfs_perf_events_rm_dirs(struct damon_sysfs_perf_events *events)
+{
+ struct damon_sysfs_perf_event_attr **attrs_arr = events->attrs_arr;
+ int i;
+
+ for (i = 0; i < events->nr; i++)
+ kobject_put(&attrs_arr[i]->kobj);
+ events->nr = 0;
+ kfree(attrs_arr);
+ events->attrs_arr = NULL;
+}
+
+static int damon_sysfs_perf_events_add_dirs(struct damon_sysfs_perf_events *events,
+ int nr_events)
+{
+ struct damon_sysfs_perf_event_attr **attrs_arr, *attr;
+ int err, i;
+
+ damon_sysfs_perf_events_rm_dirs(events);
+ if (!nr_events)
+ return 0;
+
+ attrs_arr = kmalloc_array(nr_events, sizeof(*attrs_arr), GFP_KERNEL);
+ if (!attrs_arr)
+ return -ENOMEM;
+ events->attrs_arr = attrs_arr;
+
+ for (i = 0; i < nr_events; i++) {
+ attr = damon_sysfs_perf_event_attr_alloc();
+ if (!attr) {
+ damon_sysfs_perf_events_rm_dirs(events);
+ return -ENOMEM;
+ }
+
+ err = kobject_init_and_add(&attr->kobj,
+ &damon_sysfs_perf_event_attr_ktype, &events->kobj,
+ "%d", i);
+ if (err) {
+ kobject_put(&attr->kobj);
+ damon_sysfs_perf_events_rm_dirs(events);
+ return err;
+ }
+ attrs_arr[i] = attr;
+ events->nr++;
+ }
+ return 0;
+}
+
+static ssize_t nr_perf_events_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct damon_sysfs_perf_events *events = container_of(kobj,
+ struct damon_sysfs_perf_events, kobj);
+
+ return sysfs_emit(buf, "%d\n", events->nr);
+}
+
+static ssize_t nr_perf_events_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct damon_sysfs_perf_events *events;
+ int nr, err = kstrtoint(buf, 0, &nr);
+
+ if (err)
+ return err;
+ if (nr < 0)
+ return -EINVAL;
+
+ events = container_of(kobj, struct damon_sysfs_perf_events, kobj);
+
+ if (!mutex_trylock(&damon_sysfs_lock))
+ return -EBUSY;
+ err = damon_sysfs_perf_events_add_dirs(events, nr);
+ mutex_unlock(&damon_sysfs_lock);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static void damon_sysfs_perf_events_release(struct kobject *kobj)
+{
+ kfree(container_of(kobj, struct damon_sysfs_perf_events, kobj));
+}
+
+static struct kobj_attribute damon_sysfs_perf_events_nr_attr =
+ __ATTR_RW_MODE(nr_perf_events, 0600);
+
+static struct attribute *damon_sysfs_perf_events_attrs[] = {
+ &damon_sysfs_perf_events_nr_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(damon_sysfs_perf_events);
+
+static const struct kobj_type damon_sysfs_perf_events_ktype = {
+ .release = damon_sysfs_perf_events_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = damon_sysfs_perf_events_groups,
+};
+
/*
* monitoring_attrs directory
*/
@@ -864,6 +1167,7 @@ struct damon_sysfs_context {
enum damon_ops_id ops_id;
unsigned long addr_unit;
struct damon_sysfs_attrs *attrs;
+ struct damon_sysfs_perf_events *perf_events;
struct damon_sysfs_targets *targets;
struct damon_sysfs_schemes *schemes;
};
@@ -903,6 +1207,23 @@ static int damon_sysfs_context_set_attrs(struct damon_sysfs_context *context)
return err;
}
+static int damon_sysfs_context_set_perf_events(struct damon_sysfs_context *context)
+{
+ struct damon_sysfs_perf_events *events = damon_sysfs_perf_events_alloc();
+ int err;
+
+ if (!events)
+ return -ENOMEM;
+ err = kobject_init_and_add(&events->kobj, &damon_sysfs_perf_events_ktype,
+ &context->kobj, "perf_events");
+ if (err) {
+ kobject_put(&events->kobj);
+ return err;
+ }
+ context->perf_events = events;
+ return 0;
+}
+
static int damon_sysfs_context_set_targets(struct damon_sysfs_context *context)
{
struct damon_sysfs_targets *targets = damon_sysfs_targets_alloc();
@@ -945,18 +1266,25 @@ static int damon_sysfs_context_add_dirs(struct damon_sysfs_context *context)
if (err)
return err;
- err = damon_sysfs_context_set_targets(context);
+ err = damon_sysfs_context_set_perf_events(context);
if (err)
goto rmdir_put_attrs_out;
+ err = damon_sysfs_context_set_targets(context);
+ if (err)
+ goto put_perf_events_out;
+
err = damon_sysfs_context_set_schemes(context);
if (err)
- goto put_targets_attrs_out;
+ goto put_targets_out;
return 0;
-put_targets_attrs_out:
+put_targets_out:
kobject_put(&context->targets->kobj);
context->targets = NULL;
+put_perf_events_out:
+ kobject_put(&context->perf_events->kobj);
+ context->perf_events = NULL;
rmdir_put_attrs_out:
damon_sysfs_attrs_rm_dirs(context->attrs);
kobject_put(&context->attrs->kobj);
@@ -970,6 +1298,8 @@ static void damon_sysfs_context_rm_dirs(struct damon_sysfs_context *context)
kobject_put(&context->attrs->kobj);
damon_sysfs_targets_rm_dirs(context->targets);
kobject_put(&context->targets->kobj);
+ damon_sysfs_perf_events_rm_dirs(context->perf_events);
+ kobject_put(&context->perf_events->kobj);
damon_sysfs_schemes_rm_dirs(context->schemes);
kobject_put(&context->schemes->kobj);
}
@@ -1430,6 +1760,40 @@ static int damon_sysfs_add_targets(struct damon_ctx *ctx,
return 0;
}
+static int damon_sysfs_add_perf_event(struct damon_sysfs_perf_event_attr *sys_attr,
+ struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event = kzalloc(sizeof(*event), GFP_KERNEL);
+
+ if (!event)
+ return -ENOMEM;
+
+ event->attr.type = sys_attr->type;
+ event->attr.config = sys_attr->config;
+ event->attr.config1 = sys_attr->config1;
+ event->attr.config2 = sys_attr->config2;
+ event->attr.sample_phys_addr = sys_attr->sample_phys_addr;
+ event->attr.sample_freq = sys_attr->sample_freq;
+
+ list_add_tail(&event->list, &ctx->perf_events);
+ return 0;
+}
+
+static int damon_sysfs_add_perf_events(struct damon_ctx *ctx,
+ struct damon_sysfs_perf_events *sysfs_perf_events)
+{
+ int i, err;
+
+ for (i = 0; i < sysfs_perf_events->nr; i++) {
+ struct damon_sysfs_perf_event_attr *attr = sysfs_perf_events->attrs_arr[i];
+
+ err = damon_sysfs_add_perf_event(attr, ctx);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
/*
* damon_sysfs_upd_schemes_stats() - Update schemes stats sysfs files.
* @data: The kobject wrapper that associated to the kdamond thread.
@@ -1471,6 +1835,9 @@ static int damon_sysfs_apply_inputs(struct damon_ctx *ctx,
ctx->min_region_sz = max(
DAMON_MIN_REGION_SZ / sys_ctx->addr_unit, 1);
err = damon_sysfs_set_attrs(ctx, sys_ctx->attrs);
+ if (err)
+ return err;
+ err = damon_sysfs_add_perf_events(ctx, sys_ctx->perf_events);
if (err)
return err;
err = damon_sysfs_add_targets(ctx, sys_ctx->targets);
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 8a58aa912112..4eb81cc1e8cd 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -15,6 +15,9 @@
#include <linux/pagewalk.h>
#include <linux/sched/mm.h>
+#include <linux/circ_buf.h>
+#include <linux/perf_event.h>
+
#include "../internal.h"
#include "ops-common.h"
@@ -277,7 +280,7 @@ static void __damon_va_init_regions(struct damon_ctx *ctx,
}
/* Initialize '->regions_list' of every target (task) */
-static void damon_va_init(struct damon_ctx *ctx)
+static void damon_va_init_regions(struct damon_ctx *ctx)
{
struct damon_target *t;
@@ -1003,6 +1006,206 @@ static int damon_va_scheme_score(struct damon_ctx *context,
return DAMOS_MAX_SCORE;
}
+#ifdef CONFIG_PERF_EVENTS
+
+struct damon_perf_buffer {
+ struct damon_access_report *reports;
+ unsigned long head;
+ unsigned long tail;
+ unsigned long size;
+};
+
+static void damon_perf_overflow(struct perf_event *perf_event, struct perf_sample_data *data,
+ struct pt_regs *regs)
+{
+ struct damon_perf_event *event = perf_event->overflow_handler_context;
+ struct damon_perf *perf = event->priv;
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, smp_processor_id());
+ unsigned long head = buffer->head;
+ unsigned long tail = READ_ONCE(buffer->tail);
+
+ if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
+ struct damon_access_report *report = &buffer->reports[head];
+
+ report->tid = task_pid_nr(current);
+ report->vaddr = data->addr;
+ report->paddr = data->phys_addr;
+
+ smp_store_release(&buffer->head, (head + 1) & (buffer->size - 1));
+ }
+}
+
+#define DAMON_PERF_MAX_RECORDS (1UL << 20)
+#define DAMON_PERF_INIT_RECORDS (1UL << 15)
+
+static int damon_perf_init(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf;
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_RAW,
+ .size = sizeof(attr),
+ .type = event->attr.type,
+ .config = event->attr.config,
+ .config1 = event->attr.config1,
+ .config2 = event->attr.config2,
+ .sample_freq = event->attr.sample_freq,
+ .freq = 1,
+ .sample_type = PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR |
+ PERF_SAMPLE_PERIOD | PERF_SAMPLE_DATA_SRC |
+ (event->attr.sample_phys_addr ? PERF_SAMPLE_PHYS_ADDR : 0) |
+ PERF_SAMPLE_WEIGHT_STRUCT,
+ .precise_ip = 3,
+ .pinned = 1,
+ .disabled = 1,
+ };
+ int cpu;
+ int err = -ENOMEM;
+ bool found = false;
+
+ perf = kzalloc(sizeof(*perf), GFP_KERNEL);
+ if (!perf)
+ return -ENOMEM;
+
+ perf->event = alloc_percpu(typeof(*perf->event));
+ if (!perf->event)
+ goto free_percpu;
+
+ perf->buffer = alloc_percpu(typeof(*perf->buffer));
+ if (!perf->buffer)
+ goto free_percpu;
+
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event;
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, cpu);
+
+ perf_event = perf_event_create_kernel_counter(&attr, cpu, NULL,
+ damon_perf_overflow, event);
+ if (IS_ERR(perf_event)) {
+ err = PTR_ERR(perf_event);
+ if (err == -ENODEV)
+ continue;
+ pr_err("perf event create on CPU %d failed with %d\n", cpu, err);
+ goto free_for_each_cpu;
+ }
+ found = true;
+ *per_cpu_ptr(perf->event, cpu) = perf_event;
+
+ buffer->size = DAMON_PERF_INIT_RECORDS;
+ buffer->reports = kvcalloc_node(buffer->size, sizeof(buffer->reports[0]),
+ GFP_KERNEL, cpu_to_node(cpu));
+ if (!buffer->reports)
+ goto free_for_each_cpu;
+ }
+ event->priv = perf;
+
+ return found ? 0 : -ENODEV;
+
+free_for_each_cpu:
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event = per_cpu(*perf->event, cpu);
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, cpu);
+
+ if (perf_event)
+ perf_event_release_kernel(perf_event);
+ kvfree(buffer->reports);
+ }
+free_percpu:
+ free_percpu(perf->event);
+ free_percpu(perf->buffer);
+ kfree(perf);
+
+ return err;
+}
+
+static void damon_perf_cleanup(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ int cpu;
+
+ if (!perf)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event = per_cpu(*perf->event, cpu);
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, cpu);
+
+ if (!perf_event)
+ continue;
+ perf_event_disable(perf_event);
+ perf_event_release_kernel(perf_event);
+ kvfree(buffer->reports);
+ }
+ free_percpu(perf->event);
+ free_percpu(perf->buffer);
+ kfree(perf);
+ event->priv = NULL;
+}
+
+void damon_perf_prepare_access_checks(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ int cpu;
+
+ if (!perf)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event = per_cpu(*perf->event, cpu);
+
+ if (perf_event)
+ perf_event_enable(perf_event);
+ }
+}
+
+#else
+
+static inline int damon_perf_init(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ return 0;
+}
+
+static inline void damon_perf_cleanup(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+}
+
+static void damon_va_perf_check_accesses(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+}
+
+#endif /* CONFIG_PERF_EVENTS */
+
+void damon_ops_init(struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event, *next;
+ int err = 0;
+
+ list_for_each_entry_safe(event, next, &ctx->perf_events, list) {
+ err = damon_perf_init(ctx, event);
+ if (err) {
+ list_del(&event->list);
+ kfree(event);
+ }
+ }
+}
+
+static void damon_va_init(struct damon_ctx *ctx)
+{
+ damon_ops_init(ctx);
+
+ if (ctx->ops.id == DAMON_OPS_VADDR)
+ damon_va_init_regions(ctx);
+}
+
+void damon_ops_cleanup(struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event;
+
+ list_for_each_entry(event, &ctx->perf_events, list) {
+ damon_perf_cleanup(ctx, event);
+ }
+}
+
static int __init damon_va_initcall(void)
{
struct damon_operations ops = {
@@ -1013,7 +1216,7 @@ static int __init damon_va_initcall(void)
.check_accesses = damon_va_check_accesses,
.target_valid = damon_va_target_valid,
.cleanup_target = damon_va_cleanup_target,
- .cleanup = NULL,
+ .cleanup = damon_ops_cleanup,
.apply_scheme = damon_va_apply_scheme,
.get_scheme_score = damon_va_scheme_score,
};
@@ -1023,7 +1226,6 @@ static int __init damon_va_initcall(void)
/* Don't set the monitoring target regions for the entire mapping */
ops_fvaddr.id = DAMON_OPS_FVADDR;
- ops_fvaddr.init = NULL;
ops_fvaddr.update = NULL;
err = damon_register_ops(&ops);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 4/6] mm/damon/vaddr: support perf event based access check
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
` (2 preceding siblings ...)
2026-03-09 1:00 ` [RFC PATCH v2 3/6] mm/damon/core: add common code for perf event based access check Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 5/6] mm/damon/paddr: " Akinobu Mita
` (2 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
This patch adds perf event based access check for virtual address space monitoring.
The perf event that can be specified for perf event-based access check must be able
to obtain the data source address corresponding to the sample.
In other words, PERF_SAMPLE_DATA_SRC must be able to be specified in perf_event_attr.sample_type
The perf event based access check is performed as follows:
1. During the sampling interval, samples of the perf event are stored in a sampling buffer.
2. After the sampling interval ends, during the access check, a histogram showing the number of accesses
to each memory address is generated based on each sample contained in the sampling buffer.
If the sampling buffer is full during a sampling interval,
the sampling buffer is expanded to prepare for the next sampling interval.
If you configure the memory addresses corresponding to the perf event samples to be obtained
as virtual addresses, the histogram is implemented as a two-dimensional xarray indexed
by pid and virtual memory address.
If you configure perf event samples to have their corresponding memory addresses retrieved
by physical addresses, the histogram is implemented using an xarray indexed by physical addresses.
3. For each monitoring target region, the number of accesses is updated based on the histogram.
---
mm/damon/ops-common.h | 18 ++
mm/damon/vaddr.c | 406 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 418 insertions(+), 6 deletions(-)
diff --git a/mm/damon/ops-common.h b/mm/damon/ops-common.h
index 395af11b91fb..7791e3fa0b7f 100644
--- a/mm/damon/ops-common.h
+++ b/mm/damon/ops-common.h
@@ -28,11 +28,29 @@ bool damos_ops_has_filter(struct damos *s);
void damon_perf_prepare_access_checks(struct damon_ctx *ctx, struct damon_perf_event *event);
+struct damon_vaddr_histogram {
+ struct xarray targets;
+};
+
+struct damon_paddr_histogram {
+ struct xarray accesses;
+};
+
struct damon_perf {
struct perf_event * __percpu *event;
struct damon_perf_buffer __percpu *buffer;
+ union {
+ struct damon_vaddr_histogram vaddr_histogram;
+ struct damon_paddr_histogram paddr_histogram;
+ };
};
+void damon_paddr_histogram_init(struct damon_paddr_histogram *histogram);
+unsigned long damon_paddr_histogram_count(struct damon_paddr_histogram *histogram, u64 paddr);
+void damon_paddr_histogram_destroy(struct damon_paddr_histogram *histogram);
+
+void damon_perf_populate_paddr_histogram(struct damon_ctx *ctx, struct damon_perf_event *event);
+
#else /* CONFIG_PERF_EVENTS */
static inline void damon_perf_prepare_access_checks(struct damon_ctx *ctx,
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 4eb81cc1e8cd..a9398c319331 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -17,6 +17,7 @@
#include <linux/circ_buf.h>
#include <linux/perf_event.h>
+#include <linux/xarray.h>
#include "../internal.h"
#include "ops-common.h"
@@ -401,7 +402,7 @@ static void damon_va_mkold(struct mm_struct *mm, unsigned long addr)
* Functions for the access checking of the regions
*/
-static void __damon_va_prepare_access_check(struct mm_struct *mm,
+static void __damon_va_basic_prepare_access_check(struct mm_struct *mm,
struct damon_region *r)
{
r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
@@ -409,7 +410,7 @@ static void __damon_va_prepare_access_check(struct mm_struct *mm,
damon_va_mkold(mm, r->sampling_addr);
}
-static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
+static void damon_va_basic_prepare_access_checks(struct damon_ctx *ctx)
{
struct damon_target *t;
struct mm_struct *mm;
@@ -420,7 +421,7 @@ static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
if (!mm)
continue;
damon_for_each_region(r, t)
- __damon_va_prepare_access_check(mm, r);
+ __damon_va_basic_prepare_access_check(mm, r);
mmput(mm);
}
}
@@ -539,7 +540,7 @@ static bool damon_va_young(struct mm_struct *mm, unsigned long addr,
* mm 'mm_struct' for the given virtual address space
* r the region to be checked
*/
-static void __damon_va_check_access(struct mm_struct *mm,
+static void __damon_va_basic_check_access(struct mm_struct *mm,
struct damon_region *r, bool same_target,
struct damon_attrs *attrs)
{
@@ -565,7 +566,7 @@ static void __damon_va_check_access(struct mm_struct *mm,
last_addr = r->sampling_addr;
}
-static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
+static unsigned int damon_va_basic_check_accesses(struct damon_ctx *ctx)
{
struct damon_target *t;
struct mm_struct *mm;
@@ -577,7 +578,7 @@ static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
mm = damon_get_mm(t);
same_target = false;
damon_for_each_region(r, t) {
- __damon_va_check_access(mm, r, same_target,
+ __damon_va_basic_check_access(mm, r, same_target,
&ctx->attrs);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
same_target = true;
@@ -1015,6 +1016,63 @@ struct damon_perf_buffer {
unsigned long size;
};
+struct damon_vaddr_histogram_per_target {
+ struct xarray accesses;
+};
+
+static void damon_vaddr_histogram_init(struct damon_vaddr_histogram *histogram)
+{
+ xa_init(&histogram->targets);
+}
+
+static void damon_vaddr_histogram_add(struct damon_vaddr_histogram *histogram, u32 pid,
+ u64 vaddr)
+{
+ struct damon_vaddr_histogram_per_target *target;
+ unsigned long nr_accesses;
+
+ while (!(target = xa_load(&histogram->targets, pid))) {
+ target = kmalloc(sizeof(*target), GFP_KERNEL);
+ if (!target)
+ return;
+
+ xa_init(&target->accesses);
+
+ if (xa_err(xa_store(&histogram->targets, pid, target, GFP_KERNEL))) {
+ pr_warn_once("Failed to store target histogram\n");
+ kfree(target);
+ return;
+ }
+ }
+
+ nr_accesses = xa_to_value(xa_load(&target->accesses, vaddr));
+ xa_store(&target->accesses, vaddr, xa_mk_value(nr_accesses + 1), GFP_KERNEL);
+}
+
+static unsigned long damon_vaddr_histogram_count(struct damon_vaddr_histogram *histogram,
+ u32 pid, u64 vaddr)
+{
+ struct damon_vaddr_histogram_per_target *target;
+ unsigned long nr_accesses = 0;
+
+ target = xa_load(&histogram->targets, pid);
+ if (target)
+ nr_accesses = xa_to_value(xa_load(&target->accesses, vaddr));
+
+ return nr_accesses;
+}
+
+static void damon_vaddr_histogram_destroy(struct damon_vaddr_histogram *histogram)
+{
+ unsigned long index;
+ struct damon_vaddr_histogram_per_target *target;
+
+ xa_for_each(&histogram->targets, index, target)
+ xa_destroy(&target->accesses);
+
+ xa_destroy(&histogram->targets);
+}
+
static void damon_perf_overflow(struct perf_event *perf_event, struct perf_sample_data *data,
struct pt_regs *regs)
{
@@ -1158,6 +1216,298 @@ void damon_perf_prepare_access_checks(struct damon_ctx *ctx,
}
}
+static void damon_va_perf_check_accesses_by_vaddr(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ struct damon_target *t;
+ int cpu;
+ unsigned int tidx = 0;
+
+ if (!perf)
+ return;
+
+ damon_vaddr_histogram_init(&perf->vaddr_histogram);
+
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event = per_cpu(*perf->event, cpu);
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, cpu);
+ unsigned long head, tail, count, i;
+
+ if (!perf_event)
+ continue;
+
+ perf_event_disable(perf_event);
+
+ head = smp_load_acquire(&buffer->head);
+ tail = buffer->tail;
+ count = CIRC_CNT(head, tail, buffer->size);
+
+ for (i = 0; i < count; i++) {
+ struct damon_access_report *report =
+ &buffer->reports[(tail + i) & (buffer->size - 1)];
+
+ damon_vaddr_histogram_add(&perf->vaddr_histogram, report->tid,
+ report->vaddr & PAGE_MASK);
+ }
+ smp_store_release(&buffer->tail, (tail + count) & (buffer->size - 1));
+
+ if ((count == buffer->size - 1) && (buffer->size < DAMON_PERF_MAX_RECORDS)) {
+ void *new_reports = kvcalloc_node(buffer->size * 2,
+ sizeof(buffer->reports[0]), GFP_KERNEL,
+ cpu_to_node(cpu));
+
+ if (new_reports) {
+ kvfree(buffer->reports);
+ buffer->reports = new_reports;
+ buffer->head = 0;
+ buffer->tail = 0;
+ buffer->size *= 2;
+ }
+ }
+ }
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+ u32 pid = pid_nr(t->pid);
+ unsigned int nr_accessed = 0;
+
+ damon_for_each_region(r, t) {
+ unsigned long addr;
+
+ if (r->accessed)
+ continue;
+
+ for (addr = r->ar.start; addr < r->ar.end; addr += PAGE_SIZE) {
+ if (damon_vaddr_histogram_count(&perf->vaddr_histogram, pid,
+ addr & PAGE_MASK)) {
+ r->accessed = true;
+ nr_accessed++;
+ break;
+ }
+ }
+ }
+ tidx++;
+ }
+
+ damon_vaddr_histogram_destroy(&perf->vaddr_histogram);
+}
+
+struct damon_paddr_walk {
+ struct damon_paddr_histogram *histogram;
+ bool accessed;
+};
+
+static void damon_paddr_histogram_add(struct damon_paddr_histogram *histogram, u64 paddr);
+static const struct mm_walk_ops damon_paddr_ops;
+
+void damon_perf_populate_paddr_histogram(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ int cpu;
+
+ if (!perf)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct perf_event *perf_event = per_cpu(*perf->event, cpu);
+ struct damon_perf_buffer *buffer = per_cpu_ptr(perf->buffer, cpu);
+ unsigned long head, tail, count, i;
+
+ if (!perf_event)
+ continue;
+
+ perf_event_disable(perf_event);
+
+ head = smp_load_acquire(&buffer->head);
+ tail = buffer->tail;
+ count = CIRC_CNT(head, tail, buffer->size);
+
+ for (i = 0; i < count; i++) {
+ struct damon_access_report *report =
+ &buffer->reports[(tail + i) & (buffer->size - 1)];
+
+ damon_paddr_histogram_add(&perf->paddr_histogram,
+ report->paddr & PAGE_MASK);
+ }
+ smp_store_release(&buffer->tail, (tail + count) & (buffer->size - 1));
+
+ if ((count == buffer->size - 1) && (buffer->size < DAMON_PERF_MAX_RECORDS)) {
+ void *new_reports = kvcalloc_node(buffer->size * 2,
+ sizeof(buffer->reports[0]), GFP_KERNEL,
+ cpu_to_node(cpu));
+
+ if (new_reports) {
+ kvfree(buffer->reports);
+ buffer->reports = new_reports;
+ buffer->head = 0;
+ buffer->tail = 0;
+ buffer->size *= 2;
+ }
+ }
+ }
+}
+
+static void damon_va_perf_check_accesses_by_paddr(struct damon_ctx *ctx,
+ struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ struct damon_target *t;
+ unsigned int tidx = 0;
+
+ if (!perf)
+ return;
+
+ damon_paddr_histogram_init(&perf->paddr_histogram);
+
+ damon_perf_populate_paddr_histogram(ctx, event);
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+ struct mm_struct *mm = damon_get_mm(t);
+ unsigned int nr_accessed = 0;
+
+ if (!mm)
+ continue;
+
+ mmap_read_lock(mm);
+ damon_for_each_region(r, t) {
+ struct damon_paddr_walk walk_private = {
+ .histogram = &perf->paddr_histogram,
+ .accessed = false,
+ };
+
+ if (r->accessed)
+ continue;
+
+ walk_page_range(mm, r->ar.start, r->ar.end, &damon_paddr_ops,
+ &walk_private);
+ if (walk_private.accessed) {
+ r->accessed = true;
+ nr_accessed++;
+ }
+ }
+ mmap_read_unlock(mm);
+ mmput(mm);
+ tidx++;
+ }
+
+ damon_paddr_histogram_destroy(&perf->paddr_histogram);
+}
+
+static void damon_va_perf_check_accesses(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ if (event->attr.sample_phys_addr)
+ return damon_va_perf_check_accesses_by_paddr(ctx, event);
+ else
+ return damon_va_perf_check_accesses_by_vaddr(ctx, event);
+}
+
+void damon_paddr_histogram_init(struct damon_paddr_histogram *histogram)
+{
+ xa_init(&histogram->accesses);
+}
+
+static void damon_paddr_histogram_add(struct damon_paddr_histogram *histogram,
+ u64 paddr)
+{
+ unsigned long nr_accesses;
+
+ nr_accesses = xa_to_value(xa_load(&histogram->accesses, paddr));
+ xa_store(&histogram->accesses, paddr, xa_mk_value(nr_accesses + 1), GFP_KERNEL);
+}
+
+unsigned long damon_paddr_histogram_count(struct damon_paddr_histogram *histogram, u64 paddr)
+{
+ return xa_to_value(xa_load(&histogram->accesses, paddr));
+}
+
+void damon_paddr_histogram_destroy(struct damon_paddr_histogram *histogram)
+{
+ xa_destroy(&histogram->accesses);
+}
+
+static int damon_paddr_pmd_entry(pmd_t *pmd, unsigned long addr,
+ unsigned long next, struct mm_walk *walk)
+{
+ pte_t *pte;
+ spinlock_t *ptl;
+ struct damon_paddr_walk *paddr_walk = walk->private;
+
+ ptl = pmd_trans_huge_lock(pmd, walk->vma);
+ if (ptl) {
+ pmd_t pmde = pmdp_get(pmd);
+
+ if (pmd_present(pmde)) {
+ for (; addr < next && !paddr_walk->accessed; addr += PAGE_SIZE) {
+ u64 frame = pmd_pfn(pmde) +
+ ((addr & ~HPAGE_PMD_MASK) >> PAGE_SHIFT);
+
+ if (damon_paddr_histogram_count(paddr_walk->histogram,
+ PFN_PHYS(frame))) {
+ paddr_walk->accessed = true;
+ break;
+ }
+ }
+ }
+ spin_unlock(ptl);
+ return paddr_walk->accessed ? 1 : 0;
+ }
+
+ pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
+ if (!pte) {
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
+
+ for (; addr < next && !paddr_walk->accessed; pte++, addr += PAGE_SIZE) {
+ pte_t ptent = ptep_get(pte);
+
+ if (pte_present(ptent)) {
+ if (damon_paddr_histogram_count(paddr_walk->histogram,
+ PFN_PHYS(pte_pfn(ptent)))) {
+ paddr_walk->accessed = true;
+ }
+ }
+ }
+
+ pte_unmap_unlock(pte - 1, ptl);
+
+ return paddr_walk->accessed ? 1 : 0;
+}
+
+#ifdef CONFIG_HUGETLB_PAGE
+static int damon_paddr_hugetlb_entry(pte_t *pte, unsigned long hmask,
+ unsigned long addr, unsigned long end,
+ struct mm_walk *walk)
+{
+ struct damon_paddr_walk *paddr_walk = walk->private;
+ pte_t entry = huge_ptep_get(walk->mm, addr, pte);
+
+ if (pte_present(entry)) {
+ for (; addr < end; addr += PAGE_SIZE) {
+ u64 frame = pte_pfn(entry) + ((addr & ~hmask) >> PAGE_SHIFT);
+
+ if (damon_paddr_histogram_count(paddr_walk->histogram,
+ PFN_PHYS(frame))) {
+ paddr_walk->accessed = true;
+ break;
+ }
+ }
+ }
+
+ return paddr_walk->accessed ? 1 : 0;
+}
+#else
+#define damon_perf_hugetlb_entry NULL
+#endif /* CONFIG_HUGETLB_PAGE */
+
+static const struct mm_walk_ops damon_paddr_ops = {
+ .pmd_entry = damon_paddr_pmd_entry,
+ .hugetlb_entry = damon_paddr_hugetlb_entry,
+ .walk_lock = PGWALK_RDLOCK,
+};
+
#else
static inline int damon_perf_init(struct damon_ctx *ctx, struct damon_perf_event *event)
@@ -1197,6 +1547,50 @@ static void damon_va_init(struct damon_ctx *ctx)
damon_va_init_regions(ctx);
}
+static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event;
+
+ if (list_empty(&ctx->perf_events))
+ return damon_va_basic_prepare_access_checks(ctx);
+
+ list_for_each_entry(event, &ctx->perf_events, list) {
+ damon_perf_prepare_access_checks(ctx, event);
+ }
+}
+
+static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
+{
+ struct damon_target *t;
+ struct damon_perf_event *event;
+ unsigned int max_nr_accesses = 0;
+
+ if (list_empty(&ctx->perf_events))
+ return damon_va_basic_check_accesses(ctx);
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ damon_for_each_region(r, t)
+ r->accessed = false;
+ }
+
+ list_for_each_entry(event, &ctx->perf_events, list) {
+ damon_va_perf_check_accesses(ctx, event);
+ }
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ damon_for_each_region(r, t) {
+ damon_update_region_access_rate(r, r->accessed, &ctx->attrs);
+ max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
+ }
+ }
+
+ return max_nr_accesses;
+}
+
void damon_ops_cleanup(struct damon_ctx *ctx)
{
struct damon_perf_event *event;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 5/6] mm/damon/paddr: support perf event based access check
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
` (3 preceding siblings ...)
2026-03-09 1:00 ` [RFC PATCH v2 4/6] mm/damon/vaddr: support " Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 6/6] mm/damon: allow user to set min size of region Akinobu Mita
2026-03-11 0:51 ` [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check SeongJae Park
6 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
This patch adds perf event based access checks for physical address spaces monitoring.
The implementation is very similar to that described in perf event based access check
for virtual address space monitoring.
However, for perf events that can be specified with physical address spaces monitoring,
the data source address corresponding to the sample must be obtainable as a physical address.
In other words, PERF_SAMPLE_DATA_SRC and PERF_SAMPLE_PHYS_ADDR must be specifiable
in perf_event_attr.sample_type.
---
mm/damon/paddr.c | 102 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 96 insertions(+), 6 deletions(-)
diff --git a/mm/damon/paddr.c b/mm/damon/paddr.c
index 68dcde5d423f..a5293af870fd 100644
--- a/mm/damon/paddr.c
+++ b/mm/damon/paddr.c
@@ -48,7 +48,7 @@ static void damon_pa_mkold(phys_addr_t paddr)
folio_put(folio);
}
-static void __damon_pa_prepare_access_check(struct damon_region *r,
+static void __damon_pa_basic_prepare_access_check(struct damon_region *r,
unsigned long addr_unit)
{
r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
@@ -56,14 +56,14 @@ static void __damon_pa_prepare_access_check(struct damon_region *r,
damon_pa_mkold(damon_pa_phys_addr(r->sampling_addr, addr_unit));
}
-static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
+static void damon_pa_basic_prepare_access_checks(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t)
- __damon_pa_prepare_access_check(r, ctx->addr_unit);
+ __damon_pa_basic_prepare_access_check(r, ctx->addr_unit);
}
}
@@ -81,7 +81,7 @@ static bool damon_pa_young(phys_addr_t paddr, unsigned long *folio_sz)
return accessed;
}
-static void __damon_pa_check_access(struct damon_region *r,
+static void __damon_pa_basic_check_access(struct damon_region *r,
struct damon_attrs *attrs, unsigned long addr_unit)
{
static phys_addr_t last_addr;
@@ -103,7 +103,7 @@ static void __damon_pa_check_access(struct damon_region *r,
last_addr = sampling_addr;
}
-static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
+static unsigned int damon_pa_basic_check_accesses(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
@@ -111,7 +111,7 @@ static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t) {
- __damon_pa_check_access(
+ __damon_pa_basic_check_access(
r, &ctx->attrs, ctx->addr_unit);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
}
@@ -364,6 +364,96 @@ static int damon_pa_scheme_score(struct damon_ctx *context,
return DAMOS_MAX_SCORE;
}
+#ifdef CONFIG_PERF_EVENTS
+
+static void damon_pa_perf_check_accesses(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+ struct damon_perf *perf = event->priv;
+ struct damon_target *t;
+ unsigned int tidx = 0;
+
+ if (!perf)
+ return;
+
+ damon_paddr_histogram_init(&perf->paddr_histogram);
+
+ damon_perf_populate_paddr_histogram(ctx, event);
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+ unsigned int nr_accessed = 0;
+
+ damon_for_each_region(r, t) {
+ unsigned long addr;
+
+ if (r->accessed)
+ continue;
+
+ for (addr = r->ar.start; addr < r->ar.end; addr += PAGE_SIZE) {
+ if (damon_paddr_histogram_count(&perf->paddr_histogram,
+ addr & PAGE_MASK)) {
+ r->accessed = true;
+ nr_accessed++;
+ break;
+ }
+ }
+ }
+ tidx++;
+ }
+
+ damon_paddr_histogram_destroy(&perf->paddr_histogram);
+}
+
+#else /* CONFIG_PERF_EVENTS */
+
+static void damon_pa_perf_check_accesses(struct damon_ctx *ctx, struct damon_perf_event *event)
+{
+}
+
+#endif /* CONFIG_PERF_EVENTS */
+
+static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
+{
+ struct damon_perf_event *event;
+
+ if (list_empty(&ctx->perf_events))
+ return damon_pa_basic_prepare_access_checks(ctx);
+
+ list_for_each_entry(event, &ctx->perf_events, list)
+ damon_perf_prepare_access_checks(ctx, event);
+}
+
+static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
+{
+ struct damon_target *t;
+ struct damon_perf_event *event;
+ unsigned int max_nr_accesses = 0;
+
+ if (list_empty(&ctx->perf_events))
+ return damon_pa_basic_check_accesses(ctx);
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ damon_for_each_region(r, t)
+ r->accessed = false;
+ }
+
+ list_for_each_entry(event, &ctx->perf_events, list)
+ damon_pa_perf_check_accesses(ctx, event);
+
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ damon_for_each_region(r, t) {
+ damon_update_region_access_rate(r, r->accessed, &ctx->attrs);
+ max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
+ }
+ }
+
+ return max_nr_accesses;
+}
+
static int __init damon_pa_initcall(void)
{
struct damon_operations ops = {
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v2 6/6] mm/damon: allow user to set min size of region
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
` (4 preceding siblings ...)
2026-03-09 1:00 ` [RFC PATCH v2 5/6] mm/damon/paddr: " Akinobu Mita
@ 2026-03-09 1:00 ` Akinobu Mita
2026-03-11 0:51 ` [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check SeongJae Park
6 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-09 1:00 UTC (permalink / raw)
To: damon; +Cc: linux-perf-users, sj, akinobu.mita
Currently, the region size is limited to a page size or more,
but this patch makes it possible to set the lower limit using
the following sysfs interfaces:
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/targets/<T>/region_sz/min
Description: Writing a value to this file sets the minimum size of monitoring regions of
the DAMON target as the value. Reading this file returns the value.
You can monitor fixed region size granularity by setting the ``min_nr_regions``
attribute greater than the working set size divided by the minimum size.
---
.../ABI/testing/sysfs-kernel-mm-damon | 2 +
include/linux/damon.h | 2 +
mm/damon/core.c | 23 ++++++++++-
mm/damon/sysfs-common.c | 11 +++++
mm/damon/sysfs-common.h | 1 +
mm/damon/sysfs.c | 26 +++++++++++-
mm/damon/tests/sysfs-kunit.h | 2 +
mm/damon/tests/vaddr-kunit.h | 5 ++-
mm/damon/vaddr.c | 41 +++++++++++++------
9 files changed, 97 insertions(+), 16 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon
index 405a1a7f1eb6..21ab1b8d4008 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-damon
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon
@@ -571,6 +571,8 @@ Description: Reading this file returns the size of the memory in the region
that passed DAMON operations layer-handled filters of the
scheme in bytes.
+What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/targets/<T>/region_sz/min
+
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/nr_perf_events
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/type
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/perf_events/<P>/config
diff --git a/include/linux/damon.h b/include/linux/damon.h
index c1cf3b5b990b..21d5deca9b11 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -91,6 +91,7 @@ struct damon_region {
* struct damon_target - Represents a monitoring target.
* @pid: The PID of the virtual address space to monitor.
* @nr_regions: Number of monitoring target regions of this target.
+ * @min_region_sz: The minimum size of adaptive monitoring regions.
* @regions_list: Head of the monitoring target regions of this target.
* @list: List head for siblings.
* @obsolete: Whether the commit destination target is obsolete.
@@ -107,6 +108,7 @@ struct damon_region {
struct damon_target {
struct pid *pid;
unsigned int nr_regions;
+ unsigned long min_region_sz;
struct list_head regions_list;
struct list_head list;
bool obsolete;
diff --git a/mm/damon/core.c b/mm/damon/core.c
index f8e4db2e80d6..8a2e7bdfe904 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -217,6 +217,8 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
unsigned int i;
int err;
+ min_region_sz = max(min_region_sz, t->min_region_sz);
+
/* Remove regions which are not in the new ranges */
damon_for_each_region_safe(r, next, t) {
for (i = 0; i < nr_ranges; i++) {
@@ -479,6 +481,7 @@ struct damon_target *damon_new_target(void)
t->pid = NULL;
t->nr_regions = 0;
+ t->min_region_sz = 0;
INIT_LIST_HEAD(&t->regions_list);
INIT_LIST_HEAD(&t->list);
t->obsolete = false;
@@ -1192,6 +1195,13 @@ static int damon_commit_target(
if (src_has_pid)
get_pid(src->pid);
dst->pid = src->pid;
+ dst->min_region_sz = src->min_region_sz;
+ if (dst->min_region_sz) {
+ if (dst->min_region_sz < DAMON_MIN_REGION_SZ || !is_power_of_2(dst->min_region_sz)) {
+ pr_debug("invalid min_region_sz=%lu\n", dst->min_region_sz);
+ dst->min_region_sz = 0;
+ }
+ }
return 0;
}
@@ -1776,6 +1786,8 @@ static bool damos_skip_charged_region(struct damon_target *t,
struct damos_quota *quota = &s->quota;
unsigned long sz_to_skip;
+ min_region_sz = max(min_region_sz, t->min_region_sz);
+
/* Skip previously charged regions */
if (quota->charge_target_from) {
if (t != quota->charge_target_from)
@@ -1829,6 +1841,8 @@ static bool damos_filter_match(struct damon_ctx *ctx, struct damon_target *t,
int target_idx = 0;
unsigned long start, end;
+ min_region_sz = max(min_region_sz, t->min_region_sz);
+
switch (filter->type) {
case DAMOS_FILTER_TYPE_TARGET:
damon_for_each_target(ti, ctx) {
@@ -1978,6 +1992,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
struct damos_quota *quota = &s->quota;
unsigned long sz = damon_sz_region(r);
struct timespec64 begin, end;
+ unsigned long min_region_sz = max(c->min_region_sz, t->min_region_sz);
unsigned long sz_applied = 0;
unsigned long sz_ops_filter_passed = 0;
/*
@@ -2011,7 +2026,7 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
if (c->ops.apply_scheme) {
if (quota->esz && quota->charged_sz + sz > quota->esz) {
sz = ALIGN_DOWN(quota->esz - quota->charged_sz,
- c->min_region_sz);
+ min_region_sz);
if (!sz)
goto update_stat;
damon_split_region_at(t, r, sz);
@@ -2517,7 +2532,9 @@ static void kdamond_merge_regions(struct damon_ctx *c, unsigned int threshold,
do {
nr_regions = 0;
damon_for_each_target(t, c) {
- damon_merge_regions_of(t, threshold, sz_limit);
+ unsigned long target_sz_limit = max(sz_limit, t->min_region_sz);
+
+ damon_merge_regions_of(t, threshold, target_sz_limit);
nr_regions += damon_nr_regions(t);
}
threshold = max(1, threshold * 2);
@@ -2558,6 +2575,8 @@ static void damon_split_regions_of(struct damon_target *t, int nr_subs,
unsigned long sz_region, sz_sub = 0;
int i;
+ min_region_sz = max(min_region_sz, t->min_region_sz);
+
damon_for_each_region_safe(r, next, t) {
sz_region = damon_sz_region(r);
diff --git a/mm/damon/sysfs-common.c b/mm/damon/sysfs-common.c
index 83e24a9b5a0d..d21649ac2589 100644
--- a/mm/damon/sysfs-common.c
+++ b/mm/damon/sysfs-common.c
@@ -104,3 +104,14 @@ const struct kobj_type damon_sysfs_ul_range_ktype = {
.default_groups = damon_sysfs_ul_range_groups,
};
+static struct attribute *damon_sysfs_ul_min_attrs[] = {
+ &damon_sysfs_ul_range_min_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(damon_sysfs_ul_min);
+
+const struct kobj_type damon_sysfs_ul_min_ktype = {
+ .release = damon_sysfs_ul_range_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = damon_sysfs_ul_min_groups,
+};
diff --git a/mm/damon/sysfs-common.h b/mm/damon/sysfs-common.h
index 2099adee11d0..71520dc97268 100644
--- a/mm/damon/sysfs-common.h
+++ b/mm/damon/sysfs-common.h
@@ -22,6 +22,7 @@ struct damon_sysfs_ul_range *damon_sysfs_ul_range_alloc(
void damon_sysfs_ul_range_release(struct kobject *kobj);
extern const struct kobj_type damon_sysfs_ul_range_ktype;
+extern const struct kobj_type damon_sysfs_ul_min_ktype;
/*
* schemes directory
diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c
index 040b669d0f49..aa4a8f0a36d9 100644
--- a/mm/damon/sysfs.c
+++ b/mm/damon/sysfs.c
@@ -211,6 +211,7 @@ static const struct kobj_type damon_sysfs_regions_ktype = {
struct damon_sysfs_target {
struct kobject kobj;
struct damon_sysfs_regions *regions;
+ struct damon_sysfs_ul_range *region_sz_range;
int pid;
bool obsolete;
};
@@ -223,6 +224,7 @@ static struct damon_sysfs_target *damon_sysfs_target_alloc(void)
static int damon_sysfs_target_add_dirs(struct damon_sysfs_target *target)
{
struct damon_sysfs_regions *regions = damon_sysfs_regions_alloc();
+ struct damon_sysfs_ul_range *region_sz_range;
int err;
if (!regions)
@@ -231,14 +233,35 @@ static int damon_sysfs_target_add_dirs(struct damon_sysfs_target *target)
err = kobject_init_and_add(®ions->kobj, &damon_sysfs_regions_ktype,
&target->kobj, "regions");
if (err)
- kobject_put(®ions->kobj);
+ goto put_regions_out;
else
target->regions = regions;
+
+ region_sz_range = damon_sysfs_ul_range_alloc(0, 0);
+ if (!region_sz_range) {
+ err = -ENOMEM;
+ goto put_regions_out;
+ }
+
+ err = kobject_init_and_add(®ion_sz_range->kobj,
+ &damon_sysfs_ul_min_ktype, &target->kobj, "region_sz");
+ if (err)
+ goto put_region_sz_out;
+ target->region_sz_range = region_sz_range;
+ return 0;
+
+put_region_sz_out:
+ kobject_put(®ion_sz_range->kobj);
+ target->region_sz_range = NULL;
+put_regions_out:
+ kobject_put(®ions->kobj);
+ target->regions = NULL;
return err;
}
static void damon_sysfs_target_rm_dirs(struct damon_sysfs_target *target)
{
+ kobject_put(&target->region_sz_range->kobj);
damon_sysfs_regions_rm_dirs(target->regions);
kobject_put(&target->regions->kobj);
}
@@ -1737,6 +1760,7 @@ static int damon_sysfs_add_target(struct damon_sysfs_target *sys_target,
return -EINVAL;
}
t->obsolete = sys_target->obsolete;
+ t->min_region_sz = sys_target->region_sz_range->min;
return damon_sysfs_set_regions(t, sys_target->regions,
ctx->min_region_sz);
}
diff --git a/mm/damon/tests/sysfs-kunit.h b/mm/damon/tests/sysfs-kunit.h
index f9ec5e795b34..e558ec116c04 100644
--- a/mm/damon/tests/sysfs-kunit.h
+++ b/mm/damon/tests/sysfs-kunit.h
@@ -70,6 +70,7 @@ static void damon_sysfs_test_add_targets(struct kunit *test)
kunit_skip(test, "sysfs_regions alloc fail");
}
+ sysfs_target->region_sz_range = damon_sysfs_ul_range_alloc(0, 0);
sysfs_targets->targets_arr[0] = sysfs_target;
ctx = damon_new_ctx();
@@ -93,6 +94,7 @@ static void damon_sysfs_test_add_targets(struct kunit *test)
kfree(sysfs_targets->targets_arr);
kfree(sysfs_targets);
kfree(sysfs_target->regions);
+ kfree(sysfs_target->region_sz_range);
kfree(sysfs_target);
}
diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h
index cfae870178bf..263e6d026714 100644
--- a/mm/damon/tests/vaddr-kunit.h
+++ b/mm/damon/tests/vaddr-kunit.h
@@ -65,6 +65,7 @@ static int __link_vmas(struct maple_tree *mt, struct vm_area_struct *vmas,
*/
static void damon_test_three_regions_in_vmas(struct kunit *test)
{
+ struct damon_target *t = damon_new_target();
static struct mm_struct mm;
struct damon_addr_range regions[3] = {0};
/* 10-20-25, 200-210-220, 300-305, 307-330 */
@@ -81,7 +82,7 @@ static void damon_test_three_regions_in_vmas(struct kunit *test)
if (__link_vmas(&mm.mm_mt, vmas, ARRAY_SIZE(vmas)))
kunit_skip(test, "Failed to create VMA tree");
- __damon_va_three_regions(&mm, regions);
+ __damon_va_three_regions(t, &mm, regions);
KUNIT_EXPECT_EQ(test, 10ul, regions[0].start);
KUNIT_EXPECT_EQ(test, 25ul, regions[0].end);
@@ -89,6 +90,8 @@ static void damon_test_three_regions_in_vmas(struct kunit *test)
KUNIT_EXPECT_EQ(test, 220ul, regions[1].end);
KUNIT_EXPECT_EQ(test, 300ul, regions[2].start);
KUNIT_EXPECT_EQ(test, 330ul, regions[2].end);
+
+ damon_free_target(t);
}
static struct damon_region *__nth_region_of(struct damon_target *t, int idx)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index a9398c319331..d8c78d34c346 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -73,6 +73,7 @@ static int damon_va_evenly_split_region(struct damon_target *t,
struct damon_region *n = NULL, *next;
unsigned long start;
unsigned int i;
+ unsigned long min_region_sz = max(DAMON_MIN_REGION_SZ, t->min_region_sz);
if (!r || !nr_pieces)
return -EINVAL;
@@ -82,7 +83,7 @@ static int damon_va_evenly_split_region(struct damon_target *t,
orig_end = r->ar.end;
sz_orig = damon_sz_region(r);
- sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION_SZ);
+ sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, min_region_sz);
if (!sz_piece)
return -EINVAL;
@@ -121,12 +122,13 @@ static unsigned long sz_range(struct damon_addr_range *r)
*
* Returns 0 if success, or negative error code otherwise.
*/
-static int __damon_va_three_regions(struct mm_struct *mm,
+static int __damon_va_three_regions(struct damon_target *t, struct mm_struct *mm,
struct damon_addr_range regions[3])
{
struct damon_addr_range first_gap = {0}, second_gap = {0};
VMA_ITERATOR(vmi, mm, 0);
struct vm_area_struct *vma, *prev = NULL;
+ unsigned long min_region_sz = max(DAMON_MIN_REGION_SZ, t->min_region_sz);
unsigned long start;
/*
@@ -157,20 +159,35 @@ static int __damon_va_three_regions(struct mm_struct *mm,
}
rcu_read_unlock();
- if (!sz_range(&second_gap) || !sz_range(&first_gap))
+ if (!sz_range(&second_gap) || !sz_range(&first_gap)) {
+ pr_warn_once("The size of the first and second gaps are %lu and %lu\n",
+ sz_range(&first_gap), sz_range(&second_gap));
return -EINVAL;
+ }
/* Sort the two biggest gaps by address */
if (first_gap.start > second_gap.start)
swap(first_gap, second_gap);
/* Store the result */
- regions[0].start = ALIGN(start, DAMON_MIN_REGION_SZ);
- regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION_SZ);
- regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION_SZ);
- regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION_SZ);
- regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION_SZ);
- regions[2].end = ALIGN(prev->vm_end, DAMON_MIN_REGION_SZ);
+ regions[0].start = ALIGN_DOWN(start, min_region_sz);
+ regions[0].end = ALIGN(first_gap.start, min_region_sz);
+ regions[1].start = ALIGN_DOWN(first_gap.end, min_region_sz);
+ regions[1].end = ALIGN(second_gap.start, min_region_sz);
+ regions[2].start = ALIGN_DOWN(second_gap.end, min_region_sz);
+ regions[2].end = ALIGN(prev->vm_end, min_region_sz);
+
+ for (int i = 0; i < 3; i++) {
+ if (!sz_range(®ions[i])) {
+ pr_warn_once("The size of the %dth range is %lu\n",
+ i, sz_range(®ions[i]));
+ return -EINVAL;
+ }
+ if (i > 0 && regions[i - 1].end >= regions[i].start) {
+ pr_warn_once("%dth and %dth regions overlap\n", i - 1, i);
+ return -EINVAL;
+ }
+ }
return 0;
}
@@ -191,7 +208,7 @@ static int damon_va_three_regions(struct damon_target *t,
return -EINVAL;
mmap_read_lock(mm);
- rc = __damon_va_three_regions(mm, regions);
+ rc = __damon_va_three_regions(t, mm, regions);
mmap_read_unlock(mm);
mmput(mm);
@@ -246,6 +263,7 @@ static void __damon_va_init_regions(struct damon_ctx *ctx,
struct damon_target *ti;
struct damon_region *r;
struct damon_addr_range regions[3];
+ unsigned long min_region_sz = max(DAMON_MIN_REGION_SZ, t->min_region_sz);
unsigned long sz = 0, nr_pieces;
int i, tidx = 0;
@@ -263,8 +281,7 @@ static void __damon_va_init_regions(struct damon_ctx *ctx,
sz += regions[i].end - regions[i].start;
if (ctx->attrs.min_nr_regions)
sz /= ctx->attrs.min_nr_regions;
- if (sz < DAMON_MIN_REGION_SZ)
- sz = DAMON_MIN_REGION_SZ;
+ sz = max(sz, min_region_sz);
/* Set the initial three regions of the target */
for (i = 0; i < 3; i++) {
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report
2026-03-09 1:00 ` [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report Akinobu Mita
@ 2026-03-09 15:19 ` Ian Rogers
2026-03-10 1:23 ` SeongJae Park
0 siblings, 1 reply; 13+ messages in thread
From: Ian Rogers @ 2026-03-09 15:19 UTC (permalink / raw)
To: Akinobu Mita; +Cc: damon, linux-perf-users, sj
On Sun, Mar 8, 2026 at 6:00 PM Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> The struct damon_access_report is borrowed from SeongJae Park's
> DAMON extension project [1].
>
> [1] https://lore.kernel.org/linux-mm/20251208062943.68824-6-sj@kernel.org/T/
> ---
> include/linux/damon.h | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/include/linux/damon.h b/include/linux/damon.h
> index 49d2983af4a8..5f2870ae02ba 100644
> --- a/include/linux/damon.h
> +++ b/include/linux/damon.h
> @@ -110,6 +110,30 @@ struct damon_target {
> bool obsolete;
> };
>
> +/**
> + * struct damon_access_report - Represent single acces report information.
nit: typo s/acces/access/
> + * @paddr: Start physical address of the accessed address range.
> + * @vaddr: Start virtual address of the accessed address range.
> + * @size: The size of the accessed address range.
> + * @cpu: The id of the CPU that made the access.
> + * @tid: The task id of the task that made the access.
> + * @is_write: Whether the access is write.
> + *
> + * Any DAMON API callers that notified access events can report the information
> + * to DAMON using damon_report_access(). This struct contains the reporting
> + * infomration. Refer to damon_report_access() for more details.
nit: typo s/infomration/information/
Thanks,
Ian
> + */
> +struct damon_access_report {
> + unsigned long paddr;
> + unsigned long vaddr;
> + unsigned long size;
> + unsigned int cpu;
> + pid_t tid;
> + bool is_write;
> +/* private: */
> + unsigned long report_jiffies; /* when this report is made */
> +};
> +
> /**
> * enum damos_action - Represents an action of a Data Access Monitoring-based
> * Operation Scheme.
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report
2026-03-09 15:19 ` Ian Rogers
@ 2026-03-10 1:23 ` SeongJae Park
0 siblings, 0 replies; 13+ messages in thread
From: SeongJae Park @ 2026-03-10 1:23 UTC (permalink / raw)
To: Ian Rogers; +Cc: SeongJae Park, Akinobu Mita, damon, linux-perf-users
On Mon, 9 Mar 2026 08:19:53 -0700 Ian Rogers <irogers@google.com> wrote:
> On Sun, Mar 8, 2026 at 6:00 PM Akinobu Mita <akinobu.mita@gmail.com> wrote:
> >
> > The struct damon_access_report is borrowed from SeongJae Park's
> > DAMON extension project [1].
> >
> > [1] https://lore.kernel.org/linux-mm/20251208062943.68824-6-sj@kernel.org/T/
> > ---
> > include/linux/damon.h | 24 ++++++++++++++++++++++++
> > 1 file changed, 24 insertions(+)
> >
> > diff --git a/include/linux/damon.h b/include/linux/damon.h
> > index 49d2983af4a8..5f2870ae02ba 100644
> > --- a/include/linux/damon.h
> > +++ b/include/linux/damon.h
> > @@ -110,6 +110,30 @@ struct damon_target {
> > bool obsolete;
> > };
> >
> > +/**
> > + * struct damon_access_report - Represent single acces report information.
>
> nit: typo s/acces/access/
>
> > + * @paddr: Start physical address of the accessed address range.
> > + * @vaddr: Start virtual address of the accessed address range.
> > + * @size: The size of the accessed address range.
> > + * @cpu: The id of the CPU that made the access.
> > + * @tid: The task id of the task that made the access.
> > + * @is_write: Whether the access is write.
> > + *
> > + * Any DAMON API callers that notified access events can report the information
> > + * to DAMON using damon_report_access(). This struct contains the reporting
> > + * infomration. Refer to damon_report_access() for more details.
>
> nit: typo s/infomration/information/
As Akinobu kindly commented above, this change is borrowed from my tree, and
these typos also came from there. Thank you for catching these, Ian! I fixed
these in my tree.
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
` (5 preceding siblings ...)
2026-03-09 1:00 ` [RFC PATCH v2 6/6] mm/damon: allow user to set min size of region Akinobu Mita
@ 2026-03-11 0:51 ` SeongJae Park
2026-03-13 7:35 ` Akinobu Mita
6 siblings, 1 reply; 13+ messages in thread
From: SeongJae Park @ 2026-03-11 0:51 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon, linux-perf-users
On Mon, 9 Mar 2026 10:00:03 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> DAMON currently only provides PTE accessed-bit based access check, this
> patch series adds a new perf event based access check.
>
> Since perf event-based access checks do not require modifying the PTE
> accessed-bit for pages representing each damon region, it reduces the
> overhead of monitoring at a fixed granularity of the page size.
Based on my understanding of this patch sereis that I was enlightened from the
conversation with you on the RFC v1 and kind comments from perf community, I
agree to the points of this patch series. Extending DAMON with h/w features
like AMD IBS and Intel PEBS is what multiple people shown interests. I believe
it can benefit multiple users.
I hope this work to be continued until it is landed on the mainline, and I will
be happy to help. Probably not exactly in the current shape, though. I think
this is still in an early stage. And for the reason, I will comment on only
high level things here, rather than diving deep into each patch.
> Furthermore, this patch series also includes a feature that allows you
> to set a minimum region size for each target, enabling you to monitor
> at any fixed size greater than the page size for each target.
I'm not very sure if this is really required to be a part of this series. Can
you further share more details about what is the benefit of this feeature, and
if it should be coupled with this patch series? It would be great if you can
share some test results proving the arguments.
If this is really beneficial but there is no reason to couple with this series,
I think merging this separately first or later could also be an option.
>
> Using these features also requires modifications to damo, but these are
> not included in this patch series and are currently under development in
> the following branch:
>
> * https://github.com/mita/damo/tree/damo-perf-for-v3.1.8
Sounds good. No rush, but also feel free to send patches or PRs for this. It
is common to support DAMON features on DAMON user-space tool even before the
DAMON features are merged into the mainline.
>
> Any feedback or advice on the patch set would be greatly appreciated.
>
> * v2
Let's keep using "RFC" here.
> - reintroduce damon_operations->cleanup()
> - introduce struct damon_access_report
> - use struct damon_access_report instead of introducing struct
> damon_perf_record
Thank you for doing this. I hope using not only the struct but the underlying
infrastructure, though. Let me comment this more detail below.
> - remove maximum region size setting
>
> * v1
Ditto. Let's keep using "RFC".
> - https://lore.kernel.org/damon/20260123021014.26915-1-akinobu.mita@gmail.com/T/
Thank you for sharing the link. This is helpful for me at reviewing.
>
> * TODO
> - Currently, it is possible to unintentionally specify a perf_event
> that cannot obtain either PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR
> - Check if it works in a virtual environment using vPMU
The above two TODOs make sense to me.
I'd like to request below things as main TODOs.
First, I hope the benefit of this change is described in more detail. The
current description mentions it is more lightweight since it doesn't need to
update the accessed bit. But, PMU-based monitoring should also have its own
additional work. It would be nice if you can describe what additional works
for PMU-based monitoring induce, and why it is generally lower than that of
access bit modification.
Having a more clear motivation, or expected use case would be nice. You don't
neeed to share real details of your use case. You can anonymize sensitive
things if needed. But giving a reasonable and realistic use case will be nice
to understand where this can really be beneficial.
Sharing a test results showing the benefit will also be very nice. It doesn't
need to be huge real world test, but some reasonable amount of data that
collected on a realistic test setup could be very helpful.
Second, I hope this patch series to reuse the DAMON primitives extension
infrastructure if possible, to reduce possible future duplications. I'll add
more details about this below.
>
> Akinobu Mita (6):
> mm/damon: reintroduce damon_operations->cleanup()
Thank you for doing this :)
> mm/damon/core: introduce struct damon_access_report
Again, thank you for doing this. I understand this is an answer to my previous
question to the RFC v1 of this series. I was asking if you considered the
infrastructure part of my page faults based DAMON extension series, and you
borrowed the main data structure of the series here. But, I hope us to reuse
more of the code, since it is more generic from my point of view.
> mm/damon/core: add common code for perf event based access check
This patch is introducing a few of sysfs files under DAMON sysfs context
directory, namely 'perf_events/' and its underlying files. The files is for
registering perf events for the access monitoring. I don't dislike the basic
concept, but concerned if it is too specific for only perf events and therefore
cannot be easily extended in future.
The faults-based DAMON extension patch series [1] implements files for similar
but more general concept. It defines a concept called sampling primitives. It
is the low level primitives for access check. It could be access bit checking,
page fault event, or something new, like perf events. It adds
monitoring_attrs/sample/primitives/ sysfs directory, which can be used for
selecting what primitives the user wants to use. I think perf events can be
defined as another access check primitive, and the sysfs files can be added
under here.
I was also bit concerned at the fact that the filees are effectively exposing
perf_event_attr struct. That is, I was concerning about stability of the
struct. I now see it is a part of linux user api, so I assume it is a stable
api. Could you please confirm this? Also, adding more clarification about
that fact on the patch would be nice.
> mm/damon/vaddr: support perf event based access check
> mm/damon/paddr: support perf event based access check
My understanding of the above two patches is like below.
1. The perf event samples are added to a circular buffer whenever it is
generated.
2. At the end of each sampling interval, the ops reads each sample in the
buffer, find a matching DAMON region for the sample, and updte the
'nr_accesses' of the region.
For this, this patch series implements the circular buffer for storing samples,
and xarrays for finding the matching DAMON region for a given sample.
The page fault based DAMON extension patch series is doing something similar.
The first patch of the series implements the buffer for storing access samples.
I think the perf event samples can be stored here.
The seventh patch of the series implements the logic for finding the matching
DAMON region for each of the samples in the samples buffer, and update the
'nr_accesses' of the DAMON region.
Because the page fault based extension's infrastructure is quite general for
any future types of the primitives, I think it would be nice to reuse it.
The infrastructure is quite simple and not optimized for performance. But
optimizations can be made later. I'm more interested in the long term
maintenance of the interface. And I think the page fault based extension's
infrastructure is easier for long term maintenance. Of course I might be
biased. Please let me know what you think.
If it helps and if you don't mind, maybe I can try to implement this series on
the page fault based infrastructure and share it as another RFC patch series.
> mm/damon: allow user to set min size of region
As I mentioned abovely, I'm not sure if this is really need to be coupled with
this entire series.
>
> .../ABI/testing/sysfs-kernel-mm-damon | 10 +
> include/linux/damon.h | 63 ++
> mm/damon/core.c | 77 ++-
> mm/damon/ops-common.h | 39 ++
> mm/damon/paddr.c | 105 ++-
> mm/damon/sysfs-common.c | 11 +
> mm/damon/sysfs-common.h | 1 +
> mm/damon/sysfs.c | 399 ++++++++++-
> mm/damon/tests/sysfs-kunit.h | 2 +
> mm/damon/tests/vaddr-kunit.h | 5 +-
> mm/damon/vaddr.c | 654 +++++++++++++++++-
> 11 files changed, 1332 insertions(+), 34 deletions(-)
>
> --
> 2.43.0
[1] https://lore.kernel.org/20251208062943.68824-1-sj@kernel.org/
Thanks,
SJ
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check
2026-03-11 0:51 ` [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check SeongJae Park
@ 2026-03-13 7:35 ` Akinobu Mita
2026-03-14 1:31 ` SeongJae Park
0 siblings, 1 reply; 13+ messages in thread
From: Akinobu Mita @ 2026-03-13 7:35 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon, linux-perf-users
2026年3月11日(水) 9:51 SeongJae Park <sj@kernel.org>:
>
> On Mon, 9 Mar 2026 10:00:03 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > DAMON currently only provides PTE accessed-bit based access check, this
> > patch series adds a new perf event based access check.
> >
> > Since perf event-based access checks do not require modifying the PTE
> > accessed-bit for pages representing each damon region, it reduces the
> > overhead of monitoring at a fixed granularity of the page size.
>
> Based on my understanding of this patch sereis that I was enlightened from the
> conversation with you on the RFC v1 and kind comments from perf community, I
> agree to the points of this patch series. Extending DAMON with h/w features
> like AMD IBS and Intel PEBS is what multiple people shown interests. I believe
> it can benefit multiple users.
>
> I hope this work to be continued until it is landed on the mainline, and I will
> be happy to help. Probably not exactly in the current shape, though. I think
> this is still in an early stage. And for the reason, I will comment on only
> high level things here, rather than diving deep into each patch.
Thank you for your helpful comments.
I will continue to work on this project.
> > Furthermore, this patch series also includes a feature that allows you
> > to set a minimum region size for each target, enabling you to monitor
> > at any fixed size greater than the page size for each target.
>
> I'm not very sure if this is really required to be a part of this series. Can
> you further share more details about what is the benefit of this feeature, and
> if it should be coupled with this patch series? It would be great if you can
> share some test results proving the arguments.
>
> If this is really beneficial but there is no reason to couple with this series,
> I think merging this separately first or later could also be an option.
Indeed, this patch can be submitted separately.
The purpose of access monitoring with a fixed granularity greater than
the page size is to reduce overhead when the total size of the memory
region being tracked is large.
Also, we want to be able to track hugepage sizes for some targets, so a minimum
region size setting is provided for each target.
For example, when memcached is started with the --enable-largepages option,
it will attempt to use THP.
> >
> > Using these features also requires modifications to damo, but these are
> > not included in this patch series and are currently under development in
> > the following branch:
> >
> > * https://github.com/mita/damo/tree/damo-perf-for-v3.1.8
>
> Sounds good. No rush, but also feel free to send patches or PRs for this. It
> is common to support DAMON features on DAMON user-space tool even before the
> DAMON features are merged into the mainline.
>
> >
> > Any feedback or advice on the patch set would be greatly appreciated.
> >
> > * v2
>
> Let's keep using "RFC" here.
OK.
> > - reintroduce damon_operations->cleanup()
> > - introduce struct damon_access_report
> > - use struct damon_access_report instead of introducing struct
> > damon_perf_record
>
> Thank you for doing this. I hope using not only the struct but the underlying
> infrastructure, though. Let me comment this more detail below.
>
> > - remove maximum region size setting
> >
> > * v1
>
> Ditto. Let's keep using "RFC".
>
> > - https://lore.kernel.org/damon/20260123021014.26915-1-akinobu.mita@gmail.com/T/
>
> Thank you for sharing the link. This is helpful for me at reviewing.
>
> >
> > * TODO
> > - Currently, it is possible to unintentionally specify a perf_event
> > that cannot obtain either PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR
> > - Check if it works in a virtual environment using vPMU
>
> The above two TODOs make sense to me.
>
> I'd like to request below things as main TODOs.
>
> First, I hope the benefit of this change is described in more detail. The
> current description mentions it is more lightweight since it doesn't need to
> update the accessed bit. But, PMU-based monitoring should also have its own
> additional work. It would be nice if you can describe what additional works
> for PMU-based monitoring induce, and why it is generally lower than that of
> access bit modification.
>
> Having a more clear motivation, or expected use case would be nice. You don't
> neeed to share real details of your use case. You can anonymize sensitive
> things if needed. But giving a reasonable and realistic use case will be nice
> to understand where this can really be beneficial.
>
> Sharing a test results showing the benefit will also be very nice. It doesn't
> need to be huge real world test, but some reasonable amount of data that
> collected on a realistic test setup could be very helpful.
All of the TODOs you suggested are useful. I will try to share the test
results and comparisons with access bit based cases.
> Second, I hope this patch series to reuse the DAMON primitives extension
> infrastructure if possible, to reduce possible future duplications. I'll add
> more details about this below.
>
> >
> > Akinobu Mita (6):
> > mm/damon: reintroduce damon_operations->cleanup()
>
> Thank you for doing this :)
>
> > mm/damon/core: introduce struct damon_access_report
>
> Again, thank you for doing this. I understand this is an answer to my previous
> question to the RFC v1 of this series. I was asking if you considered the
> infrastructure part of my page faults based DAMON extension series, and you
> borrowed the main data structure of the series here. But, I hope us to reuse
> more of the code, since it is more generic from my point of view.
The plan was to start by reusing structures and gradually make use of
infrastructure. Now, I have a better understanding of your DAMON
extension series
and have no major concerns about migrating to it.
> > mm/damon/core: add common code for perf event based access check
>
> This patch is introducing a few of sysfs files under DAMON sysfs context
> directory, namely 'perf_events/' and its underlying files. The files is for
> registering perf events for the access monitoring. I don't dislike the basic
> concept, but concerned if it is too specific for only perf events and therefore
> cannot be easily extended in future.
>
> The faults-based DAMON extension patch series [1] implements files for similar
> but more general concept. It defines a concept called sampling primitives. It
> is the low level primitives for access check. It could be access bit checking,
> page fault event, or something new, like perf events. It adds
> monitoring_attrs/sample/primitives/ sysfs directory, which can be used for
> selecting what primitives the user wants to use. I think perf events can be
> defined as another access check primitive, and the sysfs files can be added
> under here.
I agree.
> I was also bit concerned at the fact that the filees are effectively exposing
> perf_event_attr struct. That is, I was concerning about stability of the
> struct. I now see it is a part of linux user api, so I assume it is a stable
> api. Could you please confirm this? Also, adding more clarification about
> that fact on the patch would be nice.
Since `perf_event_attr` itself is an argument to the `perf_event_open()` system
call, I think future compatibility will be maintained, but I also have some
concerns about whether the interface provided by this patch is optimal.
Also, as mentioned in the TODO section, there are currently almost no
restrictions on the PMUs that can be specified, so it is possible to register
PMUs that are completely useless for perf event-based access checks.
I have one idea under consideration, and I plan to address it when I send out
the next patch series.
> > mm/damon/vaddr: support perf event based access check
> > mm/damon/paddr: support perf event based access check
>
> My understanding of the above two patches is like below.
>
> 1. The perf event samples are added to a circular buffer whenever it is
> generated.
> 2. At the end of each sampling interval, the ops reads each sample in the
> buffer, find a matching DAMON region for the sample, and updte the
> 'nr_accesses' of the region.
>
> For this, this patch series implements the circular buffer for storing samples,
> and xarrays for finding the matching DAMON region for a given sample.
>
> The page fault based DAMON extension patch series is doing something similar.
> The first patch of the series implements the buffer for storing access samples.
> I think the perf event samples can be stored here.
>
> The seventh patch of the series implements the logic for finding the matching
> DAMON region for each of the samples in the samples buffer, and update the
> 'nr_accesses' of the DAMON region.
>
> Because the page fault based extension's infrastructure is quite general for
> any future types of the primitives, I think it would be nice to reuse it.
>
> The infrastructure is quite simple and not optimized for performance. But
> optimizations can be made later. I'm more interested in the long term
> maintenance of the interface. And I think the page fault based extension's
> infrastructure is easier for long term maintenance. Of course I might be
> biased. Please let me know what you think.
>
> If it helps and if you don't mind, maybe I can try to implement this series on
> the page fault based infrastructure and share it as another RFC patch series.
I'm starting to get a vision of how to incorporate this as sampling primitives,
but if you try implementing it and share your results, it would be extremely
helpful.
> > mm/damon: allow user to set min size of region
>
> As I mentioned abovely, I'm not sure if this is really need to be coupled with
> this entire series.
I'm happy with this patch, as it's much simpler than the last one for RFC v1.
Next time, I'll send them separately.
> > .../ABI/testing/sysfs-kernel-mm-damon | 10 +
> > include/linux/damon.h | 63 ++
> > mm/damon/core.c | 77 ++-
> > mm/damon/ops-common.h | 39 ++
> > mm/damon/paddr.c | 105 ++-
> > mm/damon/sysfs-common.c | 11 +
> > mm/damon/sysfs-common.h | 1 +
> > mm/damon/sysfs.c | 399 ++++++++++-
> > mm/damon/tests/sysfs-kunit.h | 2 +
> > mm/damon/tests/vaddr-kunit.h | 5 +-
> > mm/damon/vaddr.c | 654 +++++++++++++++++-
> > 11 files changed, 1332 insertions(+), 34 deletions(-)
> >
> > --
> > 2.43.0
>
> [1] https://lore.kernel.org/20251208062943.68824-1-sj@kernel.org/
>
>
> Thanks,
> SJ
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check
2026-03-13 7:35 ` Akinobu Mita
@ 2026-03-14 1:31 ` SeongJae Park
2026-03-16 4:42 ` Akinobu Mita
0 siblings, 1 reply; 13+ messages in thread
From: SeongJae Park @ 2026-03-14 1:31 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon, linux-perf-users
On Fri, 13 Mar 2026 16:35:55 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年3月11日(水) 9:51 SeongJae Park <sj@kernel.org>:
> >
> > On Mon, 9 Mar 2026 10:00:03 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> >
> > > DAMON currently only provides PTE accessed-bit based access check, this
> > > patch series adds a new perf event based access check.
> > >
> > > Since perf event-based access checks do not require modifying the PTE
> > > accessed-bit for pages representing each damon region, it reduces the
> > > overhead of monitoring at a fixed granularity of the page size.
> >
> > Based on my understanding of this patch sereis that I was enlightened from the
> > conversation with you on the RFC v1 and kind comments from perf community, I
> > agree to the points of this patch series. Extending DAMON with h/w features
> > like AMD IBS and Intel PEBS is what multiple people shown interests. I believe
> > it can benefit multiple users.
> >
> > I hope this work to be continued until it is landed on the mainline, and I will
> > be happy to help. Probably not exactly in the current shape, though. I think
> > this is still in an early stage. And for the reason, I will comment on only
> > high level things here, rather than diving deep into each patch.
>
> Thank you for your helpful comments.
> I will continue to work on this project.
Glad to hear that :)
>
> > > Furthermore, this patch series also includes a feature that allows you
> > > to set a minimum region size for each target, enabling you to monitor
> > > at any fixed size greater than the page size for each target.
> >
> > I'm not very sure if this is really required to be a part of this series. Can
> > you further share more details about what is the benefit of this feeature, and
> > if it should be coupled with this patch series? It would be great if you can
> > share some test results proving the arguments.
> >
> > If this is really beneficial but there is no reason to couple with this series,
> > I think merging this separately first or later could also be an option.
>
> Indeed, this patch can be submitted separately.
>
> The purpose of access monitoring with a fixed granularity greater than
> the page size is to reduce overhead when the total size of the memory
> region being tracked is large.
This sounds bit strange to me, because DAMON's upper-bound monitoring overhead
is proportional to the user-set maximum number of regions, not the total
monitoring memory size.
>
> Also, we want to be able to track hugepage sizes for some targets, so a minimum
> region size setting is provided for each target.
> For example, when memcached is started with the --enable-largepages option,
> it will attempt to use THP.
This is again sounds not very completely easy to understand to me, since
adaptive regions adjustment will converge to work in THP granularity in the
case.
Maybe you want to monitor virtual addresses in fixed granularity, but use
different granularity per target? You could do the fixed granularity
monitoring by setting the min_nr_regions and max_nr_regions same to the total
size of monitoring memory divided by the desired granularity size. But maybe
this is challenging for your use case, because you use 'vaddr', which
dynamically changes the total monitoring memory size depending on the process's
memory mapping status?
You can also do different granularity monitoring for different virtual address
space by using different DAMON contexts per the target process. But I guess
you're saying it is not a good user experience?
If so, that makes more sense to me. But, I think user-space helpers could help
you overhauling the challenges. I'm curious if you tried that, and found it is
really uncomfortable or too suboptimal. If so, sharing the experience and/or
test results in more detail will be very helpful. Maybe we can do this discuss
in more detail on the separated patch, though.
>
> > >
> > > Using these features also requires modifications to damo, but these are
> > > not included in this patch series and are currently under development in
> > > the following branch:
> > >
> > > * https://github.com/mita/damo/tree/damo-perf-for-v3.1.8
> >
> > Sounds good. No rush, but also feel free to send patches or PRs for this. It
> > is common to support DAMON features on DAMON user-space tool even before the
> > DAMON features are merged into the mainline.
> >
> > >
> > > Any feedback or advice on the patch set would be greatly appreciated.
> > >
> > > * v2
> >
> > Let's keep using "RFC" here.
>
> OK.
>
> > > - reintroduce damon_operations->cleanup()
> > > - introduce struct damon_access_report
> > > - use struct damon_access_report instead of introducing struct
> > > damon_perf_record
> >
> > Thank you for doing this. I hope using not only the struct but the underlying
> > infrastructure, though. Let me comment this more detail below.
> >
> > > - remove maximum region size setting
> > >
> > > * v1
> >
> > Ditto. Let's keep using "RFC".
> >
> > > - https://lore.kernel.org/damon/20260123021014.26915-1-akinobu.mita@gmail.com/T/
> >
> > Thank you for sharing the link. This is helpful for me at reviewing.
> >
> > >
> > > * TODO
> > > - Currently, it is possible to unintentionally specify a perf_event
> > > that cannot obtain either PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR
> > > - Check if it works in a virtual environment using vPMU
> >
> > The above two TODOs make sense to me.
> >
> > I'd like to request below things as main TODOs.
> >
> > First, I hope the benefit of this change is described in more detail. The
> > current description mentions it is more lightweight since it doesn't need to
> > update the accessed bit. But, PMU-based monitoring should also have its own
> > additional work. It would be nice if you can describe what additional works
> > for PMU-based monitoring induce, and why it is generally lower than that of
> > access bit modification.
> >
> > Having a more clear motivation, or expected use case would be nice. You don't
> > neeed to share real details of your use case. You can anonymize sensitive
> > things if needed. But giving a reasonable and realistic use case will be nice
> > to understand where this can really be beneficial.
> >
> > Sharing a test results showing the benefit will also be very nice. It doesn't
> > need to be huge real world test, but some reasonable amount of data that
> > collected on a realistic test setup could be very helpful.
>
> All of the TODOs you suggested are useful. I will try to share the test
> results and comparisons with access bit based cases.
Great, looking forward to!
>
> > Second, I hope this patch series to reuse the DAMON primitives extension
> > infrastructure if possible, to reduce possible future duplications. I'll add
> > more details about this below.
> >
> > >
> > > Akinobu Mita (6):
> > > mm/damon: reintroduce damon_operations->cleanup()
> >
> > Thank you for doing this :)
> >
> > > mm/damon/core: introduce struct damon_access_report
> >
> > Again, thank you for doing this. I understand this is an answer to my previous
> > question to the RFC v1 of this series. I was asking if you considered the
> > infrastructure part of my page faults based DAMON extension series, and you
> > borrowed the main data structure of the series here. But, I hope us to reuse
> > more of the code, since it is more generic from my point of view.
>
> The plan was to start by reusing structures and gradually make use of
> infrastructure. Now, I have a better understanding of your DAMON
> extension series
> and have no major concerns about migrating to it.
Great. Nonetheless, I expect you might find devils in the details while coding
it. Please bear in mind and don't hesitate at asking my help. I tend to be
wrong a lot and learn from mistakes. :)
>
> > > mm/damon/core: add common code for perf event based access check
> >
> > This patch is introducing a few of sysfs files under DAMON sysfs context
> > directory, namely 'perf_events/' and its underlying files. The files is for
> > registering perf events for the access monitoring. I don't dislike the basic
> > concept, but concerned if it is too specific for only perf events and therefore
> > cannot be easily extended in future.
> >
> > The faults-based DAMON extension patch series [1] implements files for similar
> > but more general concept. It defines a concept called sampling primitives. It
> > is the low level primitives for access check. It could be access bit checking,
> > page fault event, or something new, like perf events. It adds
> > monitoring_attrs/sample/primitives/ sysfs directory, which can be used for
> > selecting what primitives the user wants to use. I think perf events can be
> > defined as another access check primitive, and the sysfs files can be added
> > under here.
>
> I agree.
>
> > I was also bit concerned at the fact that the filees are effectively exposing
> > perf_event_attr struct. That is, I was concerning about stability of the
> > struct. I now see it is a part of linux user api, so I assume it is a stable
> > api. Could you please confirm this? Also, adding more clarification about
> > that fact on the patch would be nice.
>
> Since `perf_event_attr` itself is an argument to the `perf_event_open()` system
> call, I think future compatibility will be maintained, but I also have some
> concerns about whether the interface provided by this patch is optimal.
>
> Also, as mentioned in the TODO section, there are currently almost no
> restrictions on the PMUs that can be specified, so it is possible to register
> PMUs that are completely useless for perf event-based access checks.
I agree to this concern.
> I have one idea under consideration, and I plan to address it when I send out
> the next patch series.
Sounds good, looking forward to the next version.
>
> > > mm/damon/vaddr: support perf event based access check
> > > mm/damon/paddr: support perf event based access check
> >
> > My understanding of the above two patches is like below.
> >
> > 1. The perf event samples are added to a circular buffer whenever it is
> > generated.
> > 2. At the end of each sampling interval, the ops reads each sample in the
> > buffer, find a matching DAMON region for the sample, and updte the
> > 'nr_accesses' of the region.
> >
> > For this, this patch series implements the circular buffer for storing samples,
> > and xarrays for finding the matching DAMON region for a given sample.
> >
> > The page fault based DAMON extension patch series is doing something similar.
> > The first patch of the series implements the buffer for storing access samples.
> > I think the perf event samples can be stored here.
> >
> > The seventh patch of the series implements the logic for finding the matching
> > DAMON region for each of the samples in the samples buffer, and update the
> > 'nr_accesses' of the DAMON region.
> >
> > Because the page fault based extension's infrastructure is quite general for
> > any future types of the primitives, I think it would be nice to reuse it.
> >
> > The infrastructure is quite simple and not optimized for performance. But
> > optimizations can be made later. I'm more interested in the long term
> > maintenance of the interface. And I think the page fault based extension's
> > infrastructure is easier for long term maintenance. Of course I might be
> > biased. Please let me know what you think.
> >
> > If it helps and if you don't mind, maybe I can try to implement this series on
> > the page fault based infrastructure and share it as another RFC patch series.
>
> I'm starting to get a vision of how to incorporate this as sampling primitives,
> but if you try implementing it and share your results, it would be extremely
> helpful.
Ok, I will try to make it in a dirty but fast way and share asap, probably
within 2-3 weeks.
>
> > > mm/damon: allow user to set min size of region
> >
> > As I mentioned abovely, I'm not sure if this is really need to be coupled with
> > this entire series.
>
> I'm happy with this patch, as it's much simpler than the last one for RFC v1.
> Next time, I'll send them separately.
Nice, looking forward to it!
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check
2026-03-14 1:31 ` SeongJae Park
@ 2026-03-16 4:42 ` Akinobu Mita
0 siblings, 0 replies; 13+ messages in thread
From: Akinobu Mita @ 2026-03-16 4:42 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon, linux-perf-users
2026年3月14日(土) 10:31 SeongJae Park <sj@kernel.org>:
>
> On Fri, 13 Mar 2026 16:35:55 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > 2026年3月11日(水) 9:51 SeongJae Park <sj@kernel.org>:
> > >
> > > On Mon, 9 Mar 2026 10:00:03 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > >
> > > > DAMON currently only provides PTE accessed-bit based access check, this
> > > > patch series adds a new perf event based access check.
> > > >
> > > > Since perf event-based access checks do not require modifying the PTE
> > > > accessed-bit for pages representing each damon region, it reduces the
> > > > overhead of monitoring at a fixed granularity of the page size.
> > >
> > > Based on my understanding of this patch sereis that I was enlightened from the
> > > conversation with you on the RFC v1 and kind comments from perf community, I
> > > agree to the points of this patch series. Extending DAMON with h/w features
> > > like AMD IBS and Intel PEBS is what multiple people shown interests. I believe
> > > it can benefit multiple users.
> > >
> > > I hope this work to be continued until it is landed on the mainline, and I will
> > > be happy to help. Probably not exactly in the current shape, though. I think
> > > this is still in an early stage. And for the reason, I will comment on only
> > > high level things here, rather than diving deep into each patch.
> >
> > Thank you for your helpful comments.
> > I will continue to work on this project.
>
> Glad to hear that :)
>
> >
> > > > Furthermore, this patch series also includes a feature that allows you
> > > > to set a minimum region size for each target, enabling you to monitor
> > > > at any fixed size greater than the page size for each target.
> > >
> > > I'm not very sure if this is really required to be a part of this series. Can
> > > you further share more details about what is the benefit of this feeature, and
> > > if it should be coupled with this patch series? It would be great if you can
> > > share some test results proving the arguments.
> > >
> > > If this is really beneficial but there is no reason to couple with this series,
> > > I think merging this separately first or later could also be an option.
> >
> > Indeed, this patch can be submitted separately.
> >
> > The purpose of access monitoring with a fixed granularity greater than
> > the page size is to reduce overhead when the total size of the memory
> > region being tracked is large.
>
> This sounds bit strange to me, because DAMON's upper-bound monitoring overhead
> is proportional to the user-set maximum number of regions, not the total
> monitoring memory size.
>
> >
> > Also, we want to be able to track hugepage sizes for some targets, so a minimum
> > region size setting is provided for each target.
> > For example, when memcached is started with the --enable-largepages option,
> > it will attempt to use THP.
>
> This is again sounds not very completely easy to understand to me, since
> adaptive regions adjustment will converge to work in THP granularity in the
> case.
>
> Maybe you want to monitor virtual addresses in fixed granularity, but use
> different granularity per target? You could do the fixed granularity
> monitoring by setting the min_nr_regions and max_nr_regions same to the total
> size of monitoring memory divided by the desired granularity size. But maybe
> this is challenging for your use case, because you use 'vaddr', which
> dynamically changes the total monitoring memory size depending on the process's
> memory mapping status?
Yes, the virtual addresses of multiple target processes are monitored
at a fixed size granularity, and only some processes are monitored
with a hugepage size, while other processes are monitored with a size
such as 64KB.
In my use case, I periodically detect the creation of new target
processes, so if I wanted to monitor all target processes at a single,
fixed granularity, I could obtain their total memory size and set
min_nr_regions and max_nr_regions to match that target fixed size
without introducing any new parameters.
> You can also do different granularity monitoring for different virtual address
> space by using different DAMON contexts per the target process. But I guess
> you're saying it is not a good user experience?
I've only evaluated it in one damon context so far, so I'll try to
see if it works in multiple contexts.
> If so, that makes more sense to me. But, I think user-space helpers could help
> you overhauling the challenges. I'm curious if you tried that, and found it is
> really uncomfortable or too suboptimal. If so, sharing the experience and/or
> test results in more detail will be very helpful. Maybe we can do this discuss
> in more detail on the separated patch, though.
Sounds good.
> >
> > > >
> > > > Using these features also requires modifications to damo, but these are
> > > > not included in this patch series and are currently under development in
> > > > the following branch:
> > > >
> > > > * https://github.com/mita/damo/tree/damo-perf-for-v3.1.8
> > >
> > > Sounds good. No rush, but also feel free to send patches or PRs for this. It
> > > is common to support DAMON features on DAMON user-space tool even before the
> > > DAMON features are merged into the mainline.
> > >
> > > >
> > > > Any feedback or advice on the patch set would be greatly appreciated.
> > > >
> > > > * v2
> > >
> > > Let's keep using "RFC" here.
> >
> > OK.
> >
> > > > - reintroduce damon_operations->cleanup()
> > > > - introduce struct damon_access_report
> > > > - use struct damon_access_report instead of introducing struct
> > > > damon_perf_record
> > >
> > > Thank you for doing this. I hope using not only the struct but the underlying
> > > infrastructure, though. Let me comment this more detail below.
> > >
> > > > - remove maximum region size setting
> > > >
> > > > * v1
> > >
> > > Ditto. Let's keep using "RFC".
> > >
> > > > - https://lore.kernel.org/damon/20260123021014.26915-1-akinobu.mita@gmail.com/T/
> > >
> > > Thank you for sharing the link. This is helpful for me at reviewing.
> > >
> > > >
> > > > * TODO
> > > > - Currently, it is possible to unintentionally specify a perf_event
> > > > that cannot obtain either PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR
> > > > - Check if it works in a virtual environment using vPMU
> > >
> > > The above two TODOs make sense to me.
> > >
> > > I'd like to request below things as main TODOs.
> > >
> > > First, I hope the benefit of this change is described in more detail. The
> > > current description mentions it is more lightweight since it doesn't need to
> > > update the accessed bit. But, PMU-based monitoring should also have its own
> > > additional work. It would be nice if you can describe what additional works
> > > for PMU-based monitoring induce, and why it is generally lower than that of
> > > access bit modification.
> > >
> > > Having a more clear motivation, or expected use case would be nice. You don't
> > > neeed to share real details of your use case. You can anonymize sensitive
> > > things if needed. But giving a reasonable and realistic use case will be nice
> > > to understand where this can really be beneficial.
> > >
> > > Sharing a test results showing the benefit will also be very nice. It doesn't
> > > need to be huge real world test, but some reasonable amount of data that
> > > collected on a realistic test setup could be very helpful.
> >
> > All of the TODOs you suggested are useful. I will try to share the test
> > results and comparisons with access bit based cases.
>
> Great, looking forward to!
>
> >
> > > Second, I hope this patch series to reuse the DAMON primitives extension
> > > infrastructure if possible, to reduce possible future duplications. I'll add
> > > more details about this below.
> > >
> > > >
> > > > Akinobu Mita (6):
> > > > mm/damon: reintroduce damon_operations->cleanup()
> > >
> > > Thank you for doing this :)
> > >
> > > > mm/damon/core: introduce struct damon_access_report
> > >
> > > Again, thank you for doing this. I understand this is an answer to my previous
> > > question to the RFC v1 of this series. I was asking if you considered the
> > > infrastructure part of my page faults based DAMON extension series, and you
> > > borrowed the main data structure of the series here. But, I hope us to reuse
> > > more of the code, since it is more generic from my point of view.
> >
> > The plan was to start by reusing structures and gradually make use of
> > infrastructure. Now, I have a better understanding of your DAMON
> > extension series
> > and have no major concerns about migrating to it.
>
> Great. Nonetheless, I expect you might find devils in the details while coding
> it. Please bear in mind and don't hesitate at asking my help. I tend to be
> wrong a lot and learn from mistakes. :)
Thank you for your cooperation.
> >
> > > > mm/damon/core: add common code for perf event based access check
> > >
> > > This patch is introducing a few of sysfs files under DAMON sysfs context
> > > directory, namely 'perf_events/' and its underlying files. The files is for
> > > registering perf events for the access monitoring. I don't dislike the basic
> > > concept, but concerned if it is too specific for only perf events and therefore
> > > cannot be easily extended in future.
> > >
> > > The faults-based DAMON extension patch series [1] implements files for similar
> > > but more general concept. It defines a concept called sampling primitives. It
> > > is the low level primitives for access check. It could be access bit checking,
> > > page fault event, or something new, like perf events. It adds
> > > monitoring_attrs/sample/primitives/ sysfs directory, which can be used for
> > > selecting what primitives the user wants to use. I think perf events can be
> > > defined as another access check primitive, and the sysfs files can be added
> > > under here.
> >
> > I agree.
> >
> > > I was also bit concerned at the fact that the filees are effectively exposing
> > > perf_event_attr struct. That is, I was concerning about stability of the
> > > struct. I now see it is a part of linux user api, so I assume it is a stable
> > > api. Could you please confirm this? Also, adding more clarification about
> > > that fact on the patch would be nice.
> >
> > Since `perf_event_attr` itself is an argument to the `perf_event_open()` system
> > call, I think future compatibility will be maintained, but I also have some
> > concerns about whether the interface provided by this patch is optimal.
> >
> > Also, as mentioned in the TODO section, there are currently almost no
> > restrictions on the PMUs that can be specified, so it is possible to register
> > PMUs that are completely useless for perf event-based access checks.
>
> I agree to this concern.
>
> > I have one idea under consideration, and I plan to address it when I send out
> > the next patch series.
>
> Sounds good, looking forward to the next version.
>
> >
> > > > mm/damon/vaddr: support perf event based access check
> > > > mm/damon/paddr: support perf event based access check
> > >
> > > My understanding of the above two patches is like below.
> > >
> > > 1. The perf event samples are added to a circular buffer whenever it is
> > > generated.
> > > 2. At the end of each sampling interval, the ops reads each sample in the
> > > buffer, find a matching DAMON region for the sample, and updte the
> > > 'nr_accesses' of the region.
> > >
> > > For this, this patch series implements the circular buffer for storing samples,
> > > and xarrays for finding the matching DAMON region for a given sample.
> > >
> > > The page fault based DAMON extension patch series is doing something similar.
> > > The first patch of the series implements the buffer for storing access samples.
> > > I think the perf event samples can be stored here.
> > >
> > > The seventh patch of the series implements the logic for finding the matching
> > > DAMON region for each of the samples in the samples buffer, and update the
> > > 'nr_accesses' of the DAMON region.
> > >
> > > Because the page fault based extension's infrastructure is quite general for
> > > any future types of the primitives, I think it would be nice to reuse it.
> > >
> > > The infrastructure is quite simple and not optimized for performance. But
> > > optimizations can be made later. I'm more interested in the long term
> > > maintenance of the interface. And I think the page fault based extension's
> > > infrastructure is easier for long term maintenance. Of course I might be
> > > biased. Please let me know what you think.
> > >
> > > If it helps and if you don't mind, maybe I can try to implement this series on
> > > the page fault based infrastructure and share it as another RFC patch series.
> >
> > I'm starting to get a vision of how to incorporate this as sampling primitives,
> > but if you try implementing it and share your results, it would be extremely
> > helpful.
>
> Ok, I will try to make it in a dirty but fast way and share asap, probably
> within 2-3 weeks.
That's wonderful!
Please don't overexert yourself.
> >
> > > > mm/damon: allow user to set min size of region
> > >
> > > As I mentioned abovely, I'm not sure if this is really need to be coupled with
> > > this entire series.
> >
> > I'm happy with this patch, as it's much simpler than the last one for RFC v1.
> > Next time, I'll send them separately.
>
> Nice, looking forward to it!
>
>
> Thanks,
> SJ
>
> [...]
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-03-16 4:42 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 1:00 [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 1/6] mm/damon: reintroduce damon_operations->cleanup() Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 2/6] mm/damon/core: introduce struct damon_access_report Akinobu Mita
2026-03-09 15:19 ` Ian Rogers
2026-03-10 1:23 ` SeongJae Park
2026-03-09 1:00 ` [RFC PATCH v2 3/6] mm/damon/core: add common code for perf event based access check Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 4/6] mm/damon/vaddr: support " Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 5/6] mm/damon/paddr: " Akinobu Mita
2026-03-09 1:00 ` [RFC PATCH v2 6/6] mm/damon: allow user to set min size of region Akinobu Mita
2026-03-11 0:51 ` [RFC PATCH v2 0/6] mm/damon: introduce perf event based access check SeongJae Park
2026-03-13 7:35 ` Akinobu Mita
2026-03-14 1:31 ` SeongJae Park
2026-03-16 4:42 ` Akinobu Mita
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox