* [RFC PATCH 0/4] mm/damon: introduce perf event based access check
@ 2026-01-23 2:10 Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 1/4] mm/damon/core: add common code for " Akinobu Mita
` (4 more replies)
0 siblings, 5 replies; 22+ messages in thread
From: Akinobu Mita @ 2026-01-23 2:10 UTC (permalink / raw)
To: damon; +Cc: 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, this patch series
also includes a feature that allows you to set upper and lower limits on
the damon region size to enable access checks with finer granularity.
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.0
Any feedback or advice on the patch set would be greatly appreciated.
Akinobu Mita (4):
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 and max size of region
.../ABI/testing/sysfs-kernel-mm-damon | 11 +
include/linux/damon.h | 42 +-
mm/damon/core.c | 202 ++++-
mm/damon/ops-common.h | 39 +
mm/damon/paddr.c | 106 ++-
mm/damon/sysfs.c | 402 +++++++++-
mm/damon/tests/core-kunit.h | 2 +-
mm/damon/tests/sysfs-kunit.h | 2 +
mm/damon/tests/vaddr-kunit.h | 7 +-
mm/damon/vaddr.c | 690 ++++++++++++++++--
10 files changed, 1425 insertions(+), 78 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH 1/4] mm/damon/core: add common code for perf event based access check
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
@ 2026-01-23 2:10 ` Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 2/4] mm/damon/vaddr: support " Akinobu Mita
` (3 subsequent siblings)
4 siblings, 0 replies; 22+ messages in thread
From: Akinobu Mita @ 2026-01-23 2:10 UTC (permalink / raw)
To: damon; +Cc: 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.
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 | 216 +++++++++-
7 files changed, 700 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 e6930d8574d3..c50c1beea5b2 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;
};
/**
@@ -753,6 +755,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
@@ -777,6 +809,7 @@ struct damon_attrs {
* @ops: Set of monitoring operations for given use cases.
* @addr_unit: Scale factor for core to ops address conversion.
* @min_sz_region: 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.
*/
@@ -823,6 +856,7 @@ struct damon_ctx {
unsigned long addr_unit;
unsigned long min_sz_region;
+ 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 81b998d32074..186c3bbf9534 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_sz_region = DAMON_MIN_REGION;
+ 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.
@@ -1256,6 +1305,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 4de25708b05a..1dc996f810bb 100644
--- a/mm/damon/sysfs.c
+++ b/mm/damon/sysfs.c
@@ -749,6 +749,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
*/
@@ -866,6 +1169,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;
};
@@ -906,6 +1210,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();
@@ -948,18 +1269,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);
@@ -973,6 +1301,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);
}
@@ -1431,6 +1761,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.
@@ -1472,6 +1836,9 @@ static int damon_sysfs_apply_inputs(struct damon_ctx *ctx,
ctx->min_sz_region = max(
DAMON_MIN_REGION / 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 23ed738a0bd6..dc9d5d12f42e 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;
@@ -1004,6 +1007,214 @@ static int damon_va_scheme_score(struct damon_ctx *context,
return DAMOS_MAX_SCORE;
}
+#ifdef CONFIG_PERF_EVENTS
+
+struct damon_perf_record {
+ u32 pid;
+ u32 tid;
+ u64 addr;
+ u64 phys_addr;
+};
+
+struct damon_perf_buffer {
+ struct damon_perf_record *records;
+ 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_perf_record *record = &buffer->records[head];
+
+ record->pid = task_tgid_nr(current);
+ record->tid = task_pid_nr(current);
+ record->addr = data->addr;
+ record->phys_addr = 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->records = kvcalloc_node(buffer->size, sizeof(buffer->records[0]),
+ GFP_KERNEL, cpu_to_node(cpu));
+ if (!buffer->records)
+ 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->records);
+ }
+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->records);
+ }
+ 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 = {
@@ -1014,7 +1225,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,
};
@@ -1024,7 +1235,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] 22+ messages in thread
* [RFC PATCH 2/4] mm/damon/vaddr: support perf event based access check
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 1/4] mm/damon/core: add common code for " Akinobu Mita
@ 2026-01-23 2:10 ` Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 3/4] mm/damon/paddr: " Akinobu Mita
` (2 subsequent siblings)
4 siblings, 0 replies; 22+ messages in thread
From: Akinobu Mita @ 2026-01-23 2:10 UTC (permalink / raw)
To: damon; +Cc: 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 dc9d5d12f42e..68f2bb844d06 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;
@@ -1023,6 +1024,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)
{
@@ -1167,6 +1225,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_perf_record *record =
+ &buffer->records[(tail + i) & (buffer->size - 1)];
+
+ damon_vaddr_histogram_add(&perf->vaddr_histogram, record->pid,
+ record->addr & 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_records = kvcalloc_node(buffer->size * 2,
+ sizeof(buffer->records[0]), GFP_KERNEL,
+ cpu_to_node(cpu));
+
+ if (new_records) {
+ kvfree(buffer->records);
+ buffer->records = new_records;
+ 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_perf_record *record =
+ &buffer->records[(tail + i) & (buffer->size - 1)];
+
+ damon_paddr_histogram_add(&perf->paddr_histogram,
+ record->phys_addr & 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_records = kvcalloc_node(buffer->size * 2,
+ sizeof(buffer->records[0]), GFP_KERNEL,
+ cpu_to_node(cpu));
+
+ if (new_records) {
+ kvfree(buffer->records);
+ buffer->records = new_records;
+ 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)
@@ -1206,6 +1556,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] 22+ messages in thread
* [RFC PATCH 3/4] mm/damon/paddr: support perf event based access check
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 1/4] mm/damon/core: add common code for " Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 2/4] mm/damon/vaddr: support " Akinobu Mita
@ 2026-01-23 2:10 ` Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 4/4] mm/damon: allow user to set min and max size of region Akinobu Mita
2026-01-24 2:39 ` [RFC PATCH 0/4] mm/damon: introduce perf event based access check SeongJae Park
4 siblings, 0 replies; 22+ messages in thread
From: Akinobu Mita @ 2026-01-23 2:10 UTC (permalink / raw)
To: damon; +Cc: 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] 22+ messages in thread
* [RFC PATCH 4/4] mm/damon: allow user to set min and max size of region
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
` (2 preceding siblings ...)
2026-01-23 2:10 ` [RFC PATCH 3/4] mm/damon/paddr: " Akinobu Mita
@ 2026-01-23 2:10 ` Akinobu Mita
2026-01-24 2:39 ` [RFC PATCH 0/4] mm/damon: introduce perf event based access check SeongJae Park
4 siblings, 0 replies; 22+ messages in thread
From: Akinobu Mita @ 2026-01-23 2:10 UTC (permalink / raw)
To: damon; +Cc: sj, akinobu.mita
Currently, the region size is limited to a page size or more,
but this patch makes it possible to set the upper and lower limits 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.
What: /sys/kernel/mm/damon/admin/kdamonds/<K>/contexts/<C>/targets/<T>/region_sz/max
Description: Writing a value to this file sets the maximum size of monitoring regions of
the DAMON target as the value. Reading this file returns the value.
By setting the minimum and maximum sizes of regions to the same value,
the region size can be kept constant (no region merging or splitting).
This patch allows you to set the minimum and maximum size of a region,
so it may not be possible to simultaneously satisfy the existing minimum and
maximum settings for the number of damon regions (min_regions, max_regions).
---
.../ABI/testing/sysfs-kernel-mm-damon | 3 +
include/linux/damon.h | 8 +-
mm/damon/core.c | 150 +++++++++++++++---
mm/damon/sysfs.c | 29 +++-
mm/damon/tests/core-kunit.h | 2 +-
mm/damon/tests/sysfs-kunit.h | 2 +
mm/damon/tests/vaddr-kunit.h | 7 +-
mm/damon/vaddr.c | 68 ++++----
8 files changed, 211 insertions(+), 58 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon
index 405a1a7f1eb6..5a49a65966b2 100644
--- a/Documentation/ABI/testing/sysfs-kernel-mm-damon
+++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon
@@ -571,6 +571,9 @@ 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>/targets/<T>/region_sz/max
+
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 c50c1beea5b2..00b4890a3ec2 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -91,6 +91,8 @@ 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.
+ * @max_region_sz: The maximum 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 +109,8 @@ struct damon_region {
struct damon_target {
struct pid *pid;
unsigned int nr_regions;
+ unsigned long min_region_sz;
+ unsigned long max_region_sz;
struct list_head regions_list;
struct list_head list;
bool obsolete;
@@ -944,7 +948,7 @@ static inline void damon_insert_region(struct damon_region *r,
void damon_add_region(struct damon_region *r, struct damon_target *t);
void damon_destroy_region(struct damon_region *r, struct damon_target *t);
int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
- unsigned int nr_ranges, unsigned long min_sz_region);
+ unsigned int nr_ranges, unsigned long min_sz_region, bool split);
void damon_update_region_access_rate(struct damon_region *r, bool accessed,
struct damon_attrs *attrs);
@@ -1013,6 +1017,8 @@ int damos_walk(struct damon_ctx *ctx, struct damos_walk_control *control);
int damon_set_region_biggest_system_ram_default(struct damon_target *t,
unsigned long *start, unsigned long *end,
unsigned long min_sz_region);
+int damon_evenly_split_region(struct damon_target *t,
+ struct damon_region *r, unsigned int nr_pieces, unsigned long sz_piece);
#endif /* CONFIG_DAMON */
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 186c3bbf9534..2e5795632865 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -178,9 +178,10 @@ static bool damon_intersect(struct damon_region *r,
* Fill holes in regions with new regions.
*/
static int damon_fill_regions_holes(struct damon_region *first,
- struct damon_region *last, struct damon_target *t)
+ struct damon_region *last, struct damon_target *t, bool split)
{
struct damon_region *r = first;
+ unsigned long max_region_sz = split ? t->max_region_sz : 0;
damon_for_each_region_from(r, t) {
struct damon_region *next, *newr;
@@ -193,6 +194,8 @@ static int damon_fill_regions_holes(struct damon_region *first,
if (!newr)
return -ENOMEM;
damon_insert_region(newr, r, next, t);
+ if (max_region_sz)
+ damon_evenly_split_region(t, newr, -1, max_region_sz);
}
}
return 0;
@@ -204,6 +207,7 @@ static int damon_fill_regions_holes(struct damon_region *first,
* @ranges: array of new monitoring target ranges.
* @nr_ranges: length of @ranges.
* @min_sz_region: minimum region size.
+ * @split: split a new region into small regions
*
* This function adds new regions to, or modify existing regions of a
* monitoring target to fit in specific ranges.
@@ -211,9 +215,12 @@ static int damon_fill_regions_holes(struct damon_region *first,
* Return: 0 if success, or negative error code otherwise.
*/
int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
- unsigned int nr_ranges, unsigned long min_sz_region)
+ unsigned int nr_ranges, unsigned long min_sz_region, bool split)
{
struct damon_region *r, *next;
+ unsigned long min_region_sz = max(min_sz_region, t->min_region_sz);
+ unsigned long max_region_sz = split ? t->max_region_sz : 0;
+ unsigned long orig_start, orig_end;
unsigned int i;
int err;
@@ -248,19 +255,34 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
/* no region intersects with this range */
newr = damon_new_region(
ALIGN_DOWN(range->start,
- min_sz_region),
- ALIGN(range->end, min_sz_region));
+ min_region_sz),
+ ALIGN(range->end, min_region_sz));
if (!newr)
return -ENOMEM;
damon_insert_region(newr, damon_prev_region(r), r, t);
+ if (max_region_sz)
+ damon_evenly_split_region(t, newr, -1, max_region_sz);
} else {
/* resize intersecting regions to fit in this range */
+ orig_start = first->ar.start;
first->ar.start = ALIGN_DOWN(range->start,
- min_sz_region);
- last->ar.end = ALIGN(range->end, min_sz_region);
+ min_region_sz);
+ orig_end = last->ar.end;
+ last->ar.end = ALIGN(range->end, min_region_sz);
+ if (first->ar.start < orig_start) {
+ if (max_region_sz)
+ damon_evenly_split_region(t, first, -1, max_region_sz);
+ if (first == last)
+ continue;
+ }
+
+ if (orig_end < last->ar.end) {
+ if (max_region_sz)
+ damon_evenly_split_region(t, last, -1, max_region_sz);
+ }
/* fill possible holes in the range */
- err = damon_fill_regions_holes(first, last, t);
+ err = damon_fill_regions_holes(first, last, t, split);
if (err)
return err;
}
@@ -479,6 +501,8 @@ struct damon_target *damon_new_target(void)
t->pid = NULL;
t->nr_regions = 0;
+ t->min_region_sz = 0;
+ t->max_region_sz = 0;
INIT_LIST_HEAD(&t->regions_list);
INIT_LIST_HEAD(&t->list);
t->obsolete = false;
@@ -1172,7 +1196,7 @@ static int damon_commit_target_regions(struct damon_target *dst,
i = 0;
damon_for_each_region(src_region, src)
ranges[i++] = src_region->ar;
- err = damon_set_regions(dst, ranges, i, src_min_sz_region);
+ err = damon_set_regions(dst, ranges, i, src_min_sz_region, true);
kfree(ranges);
return err;
}
@@ -1192,6 +1216,25 @@ 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;
+ dst->max_region_sz = src->max_region_sz;
+ if (dst->min_region_sz > dst->max_region_sz) {
+ pr_debug("invalid min_region_sz=%lu and max_region_sz=%lu\n",
+ dst->min_region_sz, dst->max_region_sz);
+ dst->min_region_sz = dst->max_region_sz = 0;
+ }
+ if (dst->min_region_sz) {
+ if (dst->min_region_sz < DAMON_MIN_REGION || !is_power_of_2(dst->min_region_sz)) {
+ pr_debug("invalid min_region_sz=%lu\n", dst->min_region_sz);
+ dst->min_region_sz = dst->max_region_sz = 0;
+ }
+ }
+ if (dst->max_region_sz) {
+ if (dst->max_region_sz < DAMON_MIN_REGION || !is_power_of_2(dst->max_region_sz)) {
+ pr_debug("invalid max_region_sz=%lu\n", dst->max_region_sz);
+ dst->min_region_sz = dst->max_region_sz = 0;
+ }
+ }
return 0;
}
@@ -1770,6 +1813,7 @@ static bool damos_skip_charged_region(struct damon_target *t,
{
struct damon_region *r = *rp;
struct damos_quota *quota = &s->quota;
+ unsigned long min_region_sz = max(min_sz_region, t->min_region_sz);
unsigned long sz_to_skip;
/* Skip previously charged regions */
@@ -1788,11 +1832,11 @@ static bool damos_skip_charged_region(struct damon_target *t,
if (quota->charge_addr_from && r->ar.start <
quota->charge_addr_from) {
sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
- r->ar.start, min_sz_region);
+ r->ar.start, min_region_sz);
if (!sz_to_skip) {
- if (damon_sz_region(r) <= min_sz_region)
+ if (damon_sz_region(r) <= min_region_sz)
return true;
- sz_to_skip = min_sz_region;
+ sz_to_skip = min_region_sz;
}
damon_split_region_at(t, r, sz_to_skip);
r = damon_next_region(r);
@@ -1823,6 +1867,7 @@ static bool damos_filter_match(struct damon_ctx *ctx, struct damon_target *t,
bool matched = false;
struct damon_target *ti;
int target_idx = 0;
+ unsigned long min_region_sz = max(min_sz_region, t->min_region_sz);
unsigned long start, end;
switch (filter->type) {
@@ -1835,8 +1880,8 @@ static bool damos_filter_match(struct damon_ctx *ctx, struct damon_target *t,
matched = target_idx == filter->target_idx;
break;
case DAMOS_FILTER_TYPE_ADDR:
- start = ALIGN_DOWN(filter->addr_range.start, min_sz_region);
- end = ALIGN_DOWN(filter->addr_range.end, min_sz_region);
+ start = ALIGN_DOWN(filter->addr_range.start, min_region_sz);
+ end = ALIGN_DOWN(filter->addr_range.end, min_region_sz);
/* inside the range */
if (start <= r->ar.start && r->ar.end <= end) {
@@ -1974,6 +2019,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_sz_region, t->min_region_sz);
unsigned long sz_applied = 0;
unsigned long sz_ops_filter_passed = 0;
/*
@@ -2007,7 +2053,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_sz_region);
+ min_region_sz);
if (!sz)
goto update_stat;
damon_split_region_at(t, r, sz);
@@ -2484,6 +2530,14 @@ static void damon_merge_regions_of(struct damon_target *t, unsigned int thres,
}
}
+static bool damon_adaptive_region_adjustment_is_enabled(struct damon_target *t)
+{
+ if (!t->min_region_sz || !t->max_region_sz)
+ return true;
+
+ return t->min_region_sz != t->max_region_sz;
+}
+
/*
* Merge adjacent regions having similar access frequencies
*
@@ -2507,17 +2561,28 @@ static void kdamond_merge_regions(struct damon_ctx *c, unsigned int threshold,
struct damon_target *t;
unsigned int nr_regions;
unsigned int max_thres;
+ bool might_merge = false;
max_thres = c->attrs.aggr_interval /
(c->attrs.sample_interval ? c->attrs.sample_interval : 1);
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);
+
+ if (t->max_region_sz)
+ target_sz_limit = min(sz_limit, t->max_region_sz);
+ if (damon_adaptive_region_adjustment_is_enabled(t))
+ might_merge = true;
+ else
+ target_sz_limit = 0;
+
+ damon_merge_regions_of(t, threshold, target_sz_limit);
nr_regions += damon_nr_regions(t);
}
threshold = max(1, threshold * 2);
} while (nr_regions > c->attrs.max_nr_regions &&
+ might_merge &&
threshold / 2 < max_thres);
}
@@ -2551,6 +2616,7 @@ static void damon_split_regions_of(struct damon_target *t, int nr_subs,
unsigned long min_sz_region)
{
struct damon_region *r, *next;
+ unsigned long min_region_sz = max(min_sz_region, t->min_region_sz);
unsigned long sz_region, sz_sub = 0;
int i;
@@ -2558,17 +2624,20 @@ static void damon_split_regions_of(struct damon_target *t, int nr_subs,
sz_region = damon_sz_region(r);
for (i = 0; i < nr_subs - 1 &&
- sz_region > 2 * min_sz_region; i++) {
+ sz_region > 2 * min_region_sz; i++) {
/*
* Randomly select size of left sub-region to be at
* least 10 percent and at most 90% of original region
*/
sz_sub = ALIGN_DOWN(damon_rand(1, 10) *
- sz_region / 10, min_sz_region);
+ sz_region / 10, min_region_sz);
/* Do not allow blank region */
if (sz_sub == 0 || sz_sub >= sz_region)
continue;
+ if (t->max_region_sz)
+ sz_sub = min(sz_sub, t->max_region_sz);
+
damon_split_region_at(t, r, sz_sub);
sz_region = sz_sub;
}
@@ -2603,8 +2672,11 @@ static void kdamond_split_regions(struct damon_ctx *ctx)
nr_regions < ctx->attrs.max_nr_regions / 3)
nr_subregions = 3;
- damon_for_each_target(t, ctx)
+ damon_for_each_target(t, ctx) {
+ if (!damon_adaptive_region_adjustment_is_enabled(t))
+ continue;
damon_split_regions_of(t, nr_subregions, ctx->min_sz_region);
+ }
last_nr_regions = nr_regions;
}
@@ -3005,7 +3077,7 @@ int damon_set_region_biggest_system_ram_default(struct damon_target *t,
addr_range.start = *start;
addr_range.end = *end;
- return damon_set_regions(t, &addr_range, 1, min_sz_region);
+ return damon_set_regions(t, &addr_range, 1, min_sz_region, true);
}
/*
@@ -3078,6 +3150,46 @@ void damon_update_region_access_rate(struct damon_region *r, bool accessed,
r->nr_accesses++;
}
+int damon_evenly_split_region(struct damon_target *t,
+ struct damon_region *r, unsigned int nr_pieces, unsigned long sz_piece)
+{
+ unsigned long sz_orig, orig_end;
+ struct damon_region *n = NULL, *next;
+ unsigned long start;
+ unsigned int i;
+
+ if (!r || !nr_pieces || !sz_piece)
+ return -EINVAL;
+
+ if (nr_pieces == 1)
+ return 0;
+
+ orig_end = r->ar.end;
+ sz_orig = damon_sz_region(r);
+
+ if (t->max_region_sz)
+ sz_piece = min(sz_piece, t->max_region_sz);
+
+ if (sz_orig <= sz_piece)
+ return 0;
+
+ r->ar.end = r->ar.start + sz_piece;
+ next = damon_next_region(r);
+ for (start = r->ar.end, i = 1; i < nr_pieces && start + sz_piece <= orig_end;
+ start += sz_piece, i++) {
+ n = damon_new_region(start, start + sz_piece);
+ if (!n)
+ return -ENOMEM;
+ damon_insert_region(n, r, next, t);
+ r = n;
+ }
+ /* complement last region for possible rounding error */
+ if (n)
+ n->ar.end = orig_end;
+
+ return 0;
+}
+
/**
* damon_initialized() - Return if DAMON is ready to be used.
*
diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c
index 1dc996f810bb..b48ffbcf8805 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_range_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);
}
@@ -1717,7 +1740,7 @@ static int damon_sysfs_set_regions(struct damon_target *t,
if (ranges[i - 1].end > ranges[i].start)
goto out;
}
- err = damon_set_regions(t, ranges, sysfs_regions->nr, min_sz_region);
+ err = damon_set_regions(t, ranges, sysfs_regions->nr, min_sz_region, true);
out:
kfree(ranges);
return err;
@@ -1739,6 +1762,8 @@ 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;
+ t->max_region_sz = sys_target->region_sz_range->max;
return damon_sysfs_set_regions(t, sys_target->regions, ctx->min_sz_region);
}
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index 92ea25e2dc9e..d873582b729c 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -399,7 +399,7 @@ static void damon_test_set_regions(struct kunit *test)
damon_add_region(r1, t);
damon_add_region(r2, t);
- damon_set_regions(t, &range, 1, 1);
+ damon_set_regions(t, &range, 1, 1, false);
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 3);
damon_for_each_region(r, t) {
diff --git a/mm/damon/tests/sysfs-kunit.h b/mm/damon/tests/sysfs-kunit.h
index 0c665ed255a3..2298bd63de5e 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 30dc5459f1d2..a898403cf879 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)
@@ -147,7 +150,7 @@ static void damon_do_test_apply_three_regions(struct kunit *test,
damon_add_region(r, t);
}
- damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION);
+ damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION, false);
for (i = 0; i < nr_expected / 2; i++) {
r = __nth_region_of(t, i);
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index 68f2bb844d06..c4dbc50c7baa 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -69,10 +69,8 @@ static struct mm_struct *damon_get_mm(struct damon_target *t)
static int damon_va_evenly_split_region(struct damon_target *t,
struct damon_region *r, unsigned int nr_pieces)
{
- unsigned long sz_orig, sz_piece, orig_end;
- struct damon_region *n = NULL, *next;
- unsigned long start;
- unsigned int i;
+ unsigned long sz_piece;
+ unsigned long min_region_sz = max(DAMON_MIN_REGION, t->min_region_sz);
if (!r || !nr_pieces)
return -EINVAL;
@@ -80,27 +78,12 @@ static int damon_va_evenly_split_region(struct damon_target *t,
if (nr_pieces == 1)
return 0;
- orig_end = r->ar.end;
- sz_orig = damon_sz_region(r);
- sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION);
+ sz_piece = ALIGN_DOWN(damon_sz_region(r) / nr_pieces, min_region_sz);
if (!sz_piece)
return -EINVAL;
- r->ar.end = r->ar.start + sz_piece;
- next = damon_next_region(r);
- for (start = r->ar.end, i = 1; i < nr_pieces; start += sz_piece, i++) {
- n = damon_new_region(start, start + sz_piece);
- if (!n)
- return -ENOMEM;
- damon_insert_region(n, r, next, t);
- r = n;
- }
- /* complement last region for possible rounding error */
- if (n)
- n->ar.end = orig_end;
-
- return 0;
+ return damon_evenly_split_region(t, r, -1, sz_piece);
}
static unsigned long sz_range(struct damon_addr_range *r)
@@ -121,12 +104,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, t->min_region_sz);
unsigned long start;
/*
@@ -157,20 +141,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);
- regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION);
- regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION);
- regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION);
- regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION);
- regions[2].end = ALIGN(prev->vm_end, DAMON_MIN_REGION);
+ 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 +190,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 +245,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, t->min_region_sz);
unsigned long sz = 0, nr_pieces;
int i, tidx = 0;
@@ -263,8 +263,10 @@ 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 = DAMON_MIN_REGION;
+ if (t->max_region_sz)
+ sz = clamp(sz, min_region_sz, t->max_region_sz);
+ else
+ sz = max(sz, min_region_sz);
/* Set the initial three regions of the target */
for (i = 0; i < 3; i++) {
@@ -303,7 +305,7 @@ static void damon_va_update(struct damon_ctx *ctx)
damon_for_each_target(t, ctx) {
if (damon_va_three_regions(t, three_regions))
continue;
- damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION);
+ damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION, true);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
` (3 preceding siblings ...)
2026-01-23 2:10 ` [RFC PATCH 4/4] mm/damon: allow user to set min and max size of region Akinobu Mita
@ 2026-01-24 2:39 ` SeongJae Park
2026-01-24 2:48 ` SeongJae Park
2026-01-27 1:29 ` Akinobu Mita
4 siblings, 2 replies; 22+ messages in thread
From: SeongJae Park @ 2026-01-24 2:39 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Fri, 23 Jan 2026 11:10:10 +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.
Very interesting patch series. Thank you for sharing this Akinobu!
I only took a glance on the patches, but my understanding is that this series
modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
utilize the sampled perf events in the perf events buffer as the source of
DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
type perf events makes the perf events buffer filled with memory access event
information with the access destination address. The event will be sampled
based on time (e.g., one access event per X milliseconds). And probably that
sample data could include more information includign the CPU and the process
that executing the sampled access? Please correct me if I'm wrong and add more
details I'm missing, as my understanding of perf event is very poor.
And one quick question. Can this work on virtual machines? I'm asking this
question the for following reason. I'm actuaally working on a similar project
that extends DAMON for page fault based access events sampling [1]. The
project aims to use page fault event rather than other h/w features such as AMD
IBS or Intel PEBS, because my understanding is that such h/w features are not
available on virtual machines.
>
> Since perf event-based access checks do not require modifying the PTE
> accessed-bit for pages representing each damon region, this patch series
> also includes a feature that allows you to set upper and lower limits on
> the damon region size to enable access checks with finer granularity.
I was also thinking about extending DAMON with AMD IBS or Intel PEBS like h/w
features for this kind of sub-page granularity access monitoring. So this
makes sense to me, and sounds useful!
>
> 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.0
>
> Any feedback or advice on the patch set would be greatly appreciated.
>
> Akinobu Mita (4):
> 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
I find your patches are introducing new infra code for this extension. It
seems bit specialized for perf event only, though. I'm concerned if future
extension for another access check primitives cannot reuse the infra.
My DAMON extension project [1] is for page fault based access monitoring, but
it also introduces a framework for general multiple access sampling primitives.
I'm wondering if perf event based extension can be implemented using the
general acces ssampling primitives infra code, and if you already considered
that but found it is not feasible.
> mm/damon: allow user to set min and max size of region
The min size setup makes sense. I understand the max size is for disabling the
regions adjustment (merge/split) mechanism. IOW, for fixed granularity
monitoring. Users can do that by setting min_nr_regions and max_nr_regions
same [2], though. So, max size setting seems not really needed.
Again, great RFC patch series, thank you for sharing! I'm looking forward to
your answers to above high level questions and comments.
>
> .../ABI/testing/sysfs-kernel-mm-damon | 11 +
> include/linux/damon.h | 42 +-
> mm/damon/core.c | 202 ++++-
> mm/damon/ops-common.h | 39 +
> mm/damon/paddr.c | 106 ++-
> mm/damon/sysfs.c | 402 +++++++++-
> mm/damon/tests/core-kunit.h | 2 +-
> mm/damon/tests/sysfs-kunit.h | 2 +
> mm/damon/tests/vaddr-kunit.h | 7 +-
> mm/damon/vaddr.c | 690 ++++++++++++++++--
> 10 files changed, 1425 insertions(+), 78 deletions(-)
>
> --
> 2.43.0
[1] https://lore.kernel.org/damon/20251208062943.68824-1-sj@kernel.org/
[2] https://origin.kernel.org/doc/html/latest/mm/damon/faq.html#can-i-simply-monitor-page-granularity
Thanks,
SJ
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-24 2:39 ` [RFC PATCH 0/4] mm/damon: introduce perf event based access check SeongJae Park
@ 2026-01-24 2:48 ` SeongJae Park
2026-02-23 8:08 ` Namhyung Kim
2026-01-27 1:29 ` Akinobu Mita
1 sibling, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-01-24 2:48 UTC (permalink / raw)
To: SeongJae Park; +Cc: Akinobu Mita, damon, linux-perf-users
Cc-ing linux-perf-users@ for any comment from perf people, about the use of
perf event from DAMON.
Thanks,
SJ
On Fri, 23 Jan 2026 18:39:20 SeongJae Park <sj@kernel.org> wrote:
> On Fri, 23 Jan 2026 11:10:10 +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.
>
> Very interesting patch series. Thank you for sharing this Akinobu!
>
> I only took a glance on the patches, but my understanding is that this series
> modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> utilize the sampled perf events in the perf events buffer as the source of
> DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> type perf events makes the perf events buffer filled with memory access event
> information with the access destination address. The event will be sampled
> based on time (e.g., one access event per X milliseconds). And probably that
> sample data could include more information includign the CPU and the process
> that executing the sampled access? Please correct me if I'm wrong and add more
> details I'm missing, as my understanding of perf event is very poor.
>
> And one quick question. Can this work on virtual machines? I'm asking this
> question the for following reason. I'm actuaally working on a similar project
> that extends DAMON for page fault based access events sampling [1]. The
> project aims to use page fault event rather than other h/w features such as AMD
> IBS or Intel PEBS, because my understanding is that such h/w features are not
> available on virtual machines.
>
> >
> > Since perf event-based access checks do not require modifying the PTE
> > accessed-bit for pages representing each damon region, this patch series
> > also includes a feature that allows you to set upper and lower limits on
> > the damon region size to enable access checks with finer granularity.
>
> I was also thinking about extending DAMON with AMD IBS or Intel PEBS like h/w
> features for this kind of sub-page granularity access monitoring. So this
> makes sense to me, and sounds useful!
>
> >
> > 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.0
> >
> > Any feedback or advice on the patch set would be greatly appreciated.
> >
> > Akinobu Mita (4):
> > 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
>
> I find your patches are introducing new infra code for this extension. It
> seems bit specialized for perf event only, though. I'm concerned if future
> extension for another access check primitives cannot reuse the infra.
>
> My DAMON extension project [1] is for page fault based access monitoring, but
> it also introduces a framework for general multiple access sampling primitives.
> I'm wondering if perf event based extension can be implemented using the
> general acces ssampling primitives infra code, and if you already considered
> that but found it is not feasible.
>
> > mm/damon: allow user to set min and max size of region
>
> The min size setup makes sense. I understand the max size is for disabling the
> regions adjustment (merge/split) mechanism. IOW, for fixed granularity
> monitoring. Users can do that by setting min_nr_regions and max_nr_regions
> same [2], though. So, max size setting seems not really needed.
>
> Again, great RFC patch series, thank you for sharing! I'm looking forward to
> your answers to above high level questions and comments.
>
> >
> > .../ABI/testing/sysfs-kernel-mm-damon | 11 +
> > include/linux/damon.h | 42 +-
> > mm/damon/core.c | 202 ++++-
> > mm/damon/ops-common.h | 39 +
> > mm/damon/paddr.c | 106 ++-
> > mm/damon/sysfs.c | 402 +++++++++-
> > mm/damon/tests/core-kunit.h | 2 +-
> > mm/damon/tests/sysfs-kunit.h | 2 +
> > mm/damon/tests/vaddr-kunit.h | 7 +-
> > mm/damon/vaddr.c | 690 ++++++++++++++++--
> > 10 files changed, 1425 insertions(+), 78 deletions(-)
> >
> > --
> > 2.43.0
>
> [1] https://lore.kernel.org/damon/20251208062943.68824-1-sj@kernel.org/
> [2] https://origin.kernel.org/doc/html/latest/mm/damon/faq.html#can-i-simply-monitor-page-granularity
>
>
> Thanks,
> SJ
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-24 2:39 ` [RFC PATCH 0/4] mm/damon: introduce perf event based access check SeongJae Park
2026-01-24 2:48 ` SeongJae Park
@ 2026-01-27 1:29 ` Akinobu Mita
2026-01-27 6:43 ` SeongJae Park
1 sibling, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-01-27 1:29 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon
2026年1月24日(土) 11:39 SeongJae Park <sj@kernel.org>:
>
> On Fri, 23 Jan 2026 11:10:10 +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.
>
> Very interesting patch series. Thank you for sharing this Akinobu!
>
> I only took a glance on the patches, but my understanding is that this series
> modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> utilize the sampled perf events in the perf events buffer as the source of
> DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> type perf events makes the perf events buffer filled with memory access event
> information with the access destination address. The event will be sampled
> based on time (e.g., one access event per X milliseconds). And probably that
> sample data could include more information includign the CPU and the process
> that executing the sampled access? Please correct me if I'm wrong and add more
> details I'm missing, as my understanding of perf event is very poor.
Your understanding is correct.
One thing to note is that you need to specify a PMU event that can
obtain the data address at the time of sampling.
In other words, it must be a PMU that can be used with "perf mem record".
> And one quick question. Can this work on virtual machines? I'm asking this
> question the for following reason. I'm actuaally working on a similar project
> that extends DAMON for page fault based access events sampling [1]. The
> project aims to use page fault event rather than other h/w features such as AMD
> IBS or Intel PEBS, because my understanding is that such h/w features are not
> available on virtual machines.
I haven't tried it on a virtual machine yet.
As mentioned above, if "perf mem record" works on a virtual machine,
you can specify its PMU, but it may not be supported at all.
> > Since perf event-based access checks do not require modifying the PTE
> > accessed-bit for pages representing each damon region, this patch series
> > also includes a feature that allows you to set upper and lower limits on
> > the damon region size to enable access checks with finer granularity.
>
> I was also thinking about extending DAMON with AMD IBS or Intel PEBS like h/w
> features for this kind of sub-page granularity access monitoring. So this
> makes sense to me, and sounds useful!
>
> >
> > 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.0
> >
> > Any feedback or advice on the patch set would be greatly appreciated.
> >
> > Akinobu Mita (4):
> > 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
>
> I find your patches are introducing new infra code for this extension. It
> seems bit specialized for perf event only, though. I'm concerned if future
> extension for another access check primitives cannot reuse the infra.
>
> My DAMON extension project [1] is for page fault based access monitoring, but
> it also introduces a framework for general multiple access sampling primitives.
> I'm wondering if perf event based extension can be implemented using the
> general acces ssampling primitives infra code, and if you already considered
> that but found it is not feasible.
If such infrastructure code exists, I would like to use it, so I will
consider it.
> > mm/damon: allow user to set min and max size of region
>
> The min size setup makes sense. I understand the max size is for disabling the
> regions adjustment (merge/split) mechanism. IOW, for fixed granularity
> monitoring. Users can do that by setting min_nr_regions and max_nr_regions
> same [2], though. So, max size setting seems not really needed.
Yes, if fixed granularity monitoring is possible then that is sufficient.
I have tried setting min_nr_regions and max_nr_regions to be the same.
I understand that the region split progresses over time and results
in fixed-granularity monitoring at the minimum region size, but since
my configuration has a relatively long sampling interval, it takes
time to reach that state.
So in the patch, when adding a new region with damon_set_regions(),
it will be split by the maximum region size.
> Again, great RFC patch series, thank you for sharing! I'm looking forward to
> your answers to above high level questions and comments.
Thank you so much for checking out the patch.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-27 1:29 ` Akinobu Mita
@ 2026-01-27 6:43 ` SeongJae Park
2026-01-27 12:56 ` Akinobu Mita
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-01-27 6:43 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Tue, 27 Jan 2026 10:29:53 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年1月24日(土) 11:39 SeongJae Park <sj@kernel.org>:
> >
> > On Fri, 23 Jan 2026 11:10:10 +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.
> >
> > Very interesting patch series. Thank you for sharing this Akinobu!
> >
> > I only took a glance on the patches, but my understanding is that this series
> > modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> > utilize the sampled perf events in the perf events buffer as the source of
> > DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> > type perf events makes the perf events buffer filled with memory access event
> > information with the access destination address. The event will be sampled
> > based on time (e.g., one access event per X milliseconds). And probably that
> > sample data could include more information includign the CPU and the process
> > that executing the sampled access? Please correct me if I'm wrong and add more
> > details I'm missing, as my understanding of perf event is very poor.
>
> Your understanding is correct.
>
> One thing to note is that you need to specify a PMU event that can
> obtain the data address at the time of sampling.
>
> In other words, it must be a PMU that can be used with "perf mem record".
Thank you for confirming and adding the detail.
>
> > And one quick question. Can this work on virtual machines? I'm asking this
> > question the for following reason. I'm actuaally working on a similar project
> > that extends DAMON for page fault based access events sampling [1]. The
> > project aims to use page fault event rather than other h/w features such as AMD
> > IBS or Intel PEBS, because my understanding is that such h/w features are not
> > available on virtual machines.
>
> I haven't tried it on a virtual machine yet.
> As mentioned above, if "perf mem record" works on a virtual machine,
> you can specify its PMU, but it may not be supported at all.
Seems that's the case at least for my setup. :'( On my QEMU-based machine,
'perf mem record' fails like below.
$ sudo perf mem record
failed: no PMU supports the memory events
Nevertheless, I think there should be users who want to run this on different
setups.
>
> > > Since perf event-based access checks do not require modifying the PTE
> > > accessed-bit for pages representing each damon region, this patch series
> > > also includes a feature that allows you to set upper and lower limits on
> > > the damon region size to enable access checks with finer granularity.
> >
> > I was also thinking about extending DAMON with AMD IBS or Intel PEBS like h/w
> > features for this kind of sub-page granularity access monitoring. So this
> > makes sense to me, and sounds useful!
> >
> > >
> > > 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.0
> > >
> > > Any feedback or advice on the patch set would be greatly appreciated.
> > >
> > > Akinobu Mita (4):
> > > 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
> >
> > I find your patches are introducing new infra code for this extension. It
> > seems bit specialized for perf event only, though. I'm concerned if future
> > extension for another access check primitives cannot reuse the infra.
> >
> > My DAMON extension project [1] is for page fault based access monitoring, but
> > it also introduces a framework for general multiple access sampling primitives.
> > I'm wondering if perf event based extension can be implemented using the
> > general acces ssampling primitives infra code, and if you already considered
> > that but found it is not feasible.
>
> If such infrastructure code exists, I would like to use it, so I will
> consider it.
Great. Please take a look and let me know if you have any questions about
that. The current implementation of the infra is only for proof of the
concept, so it may lack documentations and quite suboptimal.
>
> > > mm/damon: allow user to set min and max size of region
> >
> > The min size setup makes sense. I understand the max size is for disabling the
> > regions adjustment (merge/split) mechanism. IOW, for fixed granularity
> > monitoring. Users can do that by setting min_nr_regions and max_nr_regions
> > same [2], though. So, max size setting seems not really needed.
>
> Yes, if fixed granularity monitoring is possible then that is sufficient.
>
> I have tried setting min_nr_regions and max_nr_regions to be the same.
> I understand that the region split progresses over time and results
> in fixed-granularity monitoring at the minimum region size, but since
> my configuration has a relatively long sampling interval, it takes
> time to reach that state.
Ok, that makes sense. Thank you for explaining the issue.
>
> So in the patch, when adding a new region with damon_set_regions(),
> it will be split by the maximum region size.
Actually DAMON is internally setting such maximum region size based on the
min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
is applied only in regions merge time. That's why it requires the regions
split to happen sufficiently until the real fixed granularity monitoring is
started.
And I think this behavior is just a bug, or suboptimum implementation at least.
That is, users set the minimum number of regions but it may not really be kept.
That's definitely confusing behavior. Actually there was a similar case that
number of regions can be larger than the max_nr_regions. We fixed it, with
commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
max_nr_regions is unmet"). I think we discussed about similar case for
min_nr_regions, but I cannot find the discussion for now.
So, I think it is better to fix this rather than introducing a new parameter.
Maybe we can split regions based on the min_nr_regions based size limit, before
starting the main loop of kdamond_fn(). Similar to the max_nr_regions
violation, there could be yet another corner case on online parameters commit
situation, so it would better to check the case, too. You could implement such
fix on your own, or let me do that. In the latter case, if you don't mind, I
will add your Reported-by: tag to the fix. Please let me know your
preferrence.
>
> > Again, great RFC patch series, thank you for sharing! I'm looking forward to
> > your answers to above high level questions and comments.
>
> Thank you so much for checking out the patch.
The pleasure is mine, and hope this patch series to get more forward progress!
Thanks,
SJ
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-27 6:43 ` SeongJae Park
@ 2026-01-27 12:56 ` Akinobu Mita
2026-01-28 1:12 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-01-27 12:56 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon
2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> Actually DAMON is internally setting such maximum region size based on the
> min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> is applied only in regions merge time. That's why it requires the regions
> split to happen sufficiently until the real fixed granularity monitoring is
> started.
>
> And I think this behavior is just a bug, or suboptimum implementation at least.
> That is, users set the minimum number of regions but it may not really be kept.
> That's definitely confusing behavior. Actually there was a similar case that
> number of regions can be larger than the max_nr_regions. We fixed it, with
> commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> max_nr_regions is unmet"). I think we discussed about similar case for
> min_nr_regions, but I cannot find the discussion for now.
>
> So, I think it is better to fix this rather than introducing a new parameter.
I agree with that.
> Maybe we can split regions based on the min_nr_regions based size limit, before
> starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> violation, there could be yet another corner case on online parameters commit
> situation, so it would better to check the case, too. You could implement such
> fix on your own, or let me do that. In the latter case, if you don't mind, I
> will add your Reported-by: tag to the fix. Please let me know your
> preferrence.
You'll be better able to fix it, so please fix it at your convenience.
Adding Reported-by: tag is fine.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-27 12:56 ` Akinobu Mita
@ 2026-01-28 1:12 ` SeongJae Park
2026-02-17 0:13 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-01-28 1:12 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Tue, 27 Jan 2026 21:56:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> > Actually DAMON is internally setting such maximum region size based on the
> > min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> > is applied only in regions merge time. That's why it requires the regions
> > split to happen sufficiently until the real fixed granularity monitoring is
> > started.
> >
> > And I think this behavior is just a bug, or suboptimum implementation at least.
> > That is, users set the minimum number of regions but it may not really be kept.
> > That's definitely confusing behavior. Actually there was a similar case that
> > number of regions can be larger than the max_nr_regions. We fixed it, with
> > commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> > max_nr_regions is unmet"). I think we discussed about similar case for
> > min_nr_regions, but I cannot find the discussion for now.
> >
> > So, I think it is better to fix this rather than introducing a new parameter.
>
> I agree with that.
>
> > Maybe we can split regions based on the min_nr_regions based size limit, before
> > starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> > violation, there could be yet another corner case on online parameters commit
> > situation, so it would better to check the case, too. You could implement such
> > fix on your own, or let me do that. In the latter case, if you don't mind, I
> > will add your Reported-by: tag to the fix. Please let me know your
> > preferrence.
>
> You'll be better able to fix it, so please fix it at your convenience.
> Adding Reported-by: tag is fine.
Sounds good, I will do so!
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-28 1:12 ` SeongJae Park
@ 2026-02-17 0:13 ` SeongJae Park
2026-02-17 13:32 ` Akinobu Mita
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-02-17 0:13 UTC (permalink / raw)
To: SeongJae Park; +Cc: Akinobu Mita, damon
On Tue, 27 Jan 2026 17:12:23 -0800 SeongJae Park <sj@kernel.org> wrote:
> On Tue, 27 Jan 2026 21:56:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > 2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> > > Actually DAMON is internally setting such maximum region size based on the
> > > min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> > > is applied only in regions merge time. That's why it requires the regions
> > > split to happen sufficiently until the real fixed granularity monitoring is
> > > started.
> > >
> > > And I think this behavior is just a bug, or suboptimum implementation at least.
> > > That is, users set the minimum number of regions but it may not really be kept.
> > > That's definitely confusing behavior. Actually there was a similar case that
> > > number of regions can be larger than the max_nr_regions. We fixed it, with
> > > commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> > > max_nr_regions is unmet"). I think we discussed about similar case for
> > > min_nr_regions, but I cannot find the discussion for now.
> > >
> > > So, I think it is better to fix this rather than introducing a new parameter.
> >
> > I agree with that.
> >
> > > Maybe we can split regions based on the min_nr_regions based size limit, before
> > > starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> > > violation, there could be yet another corner case on online parameters commit
> > > situation, so it would better to check the case, too. You could implement such
> > > fix on your own, or let me do that. In the latter case, if you don't mind, I
> > > will add your Reported-by: tag to the fix. Please let me know your
> > > preferrence.
> >
> > You'll be better able to fix it, so please fix it at your convenience.
> > Adding Reported-by: tag is fine.
>
> Sounds good, I will do so!
I just posted an RFC patch series [1] for this. I will drop RFC tag after the
current merge window is finished. Please let me know if you find something
wrong there!
[1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-17 0:13 ` SeongJae Park
@ 2026-02-17 13:32 ` Akinobu Mita
2026-02-17 15:15 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-02-17 13:32 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon
2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
>
> On Tue, 27 Jan 2026 17:12:23 -0800 SeongJae Park <sj@kernel.org> wrote:
>
> > On Tue, 27 Jan 2026 21:56:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> >
> > > 2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> > > > Actually DAMON is internally setting such maximum region size based on the
> > > > min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> > > > is applied only in regions merge time. That's why it requires the regions
> > > > split to happen sufficiently until the real fixed granularity monitoring is
> > > > started.
> > > >
> > > > And I think this behavior is just a bug, or suboptimum implementation at least.
> > > > That is, users set the minimum number of regions but it may not really be kept.
> > > > That's definitely confusing behavior. Actually there was a similar case that
> > > > number of regions can be larger than the max_nr_regions. We fixed it, with
> > > > commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> > > > max_nr_regions is unmet"). I think we discussed about similar case for
> > > > min_nr_regions, but I cannot find the discussion for now.
> > > >
> > > > So, I think it is better to fix this rather than introducing a new parameter.
> > >
> > > I agree with that.
> > >
> > > > Maybe we can split regions based on the min_nr_regions based size limit, before
> > > > starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> > > > violation, there could be yet another corner case on online parameters commit
> > > > situation, so it would better to check the case, too. You could implement such
> > > > fix on your own, or let me do that. In the latter case, if you don't mind, I
> > > > will add your Reported-by: tag to the fix. Please let me know your
> > > > preferrence.
> > >
> > > You'll be better able to fix it, so please fix it at your convenience.
> > > Adding Reported-by: tag is fine.
> >
> > Sounds good, I will do so!
>
> I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> current merge window is finished. Please let me know if you find something
> wrong there!
>
> [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
Thank you for posting the patch series.
I tried it and found that patch 2/3 made things worse than before in terms of
monitoring at page size granularity. This is because, in my evaluation, new
target processes are added without stopping kdamond, so processes added later
are not initially monitored at page size granularity.
Would performing a split operation like damon_apply_min_nr_regions() within
damon_set_regions() solve the problem?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-17 13:32 ` Akinobu Mita
@ 2026-02-17 15:15 ` SeongJae Park
2026-02-18 8:20 ` Akinobu Mita
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-02-17 15:15 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
> >
> > On Tue, 27 Jan 2026 17:12:23 -0800 SeongJae Park <sj@kernel.org> wrote:
> >
> > > On Tue, 27 Jan 2026 21:56:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > >
> > > > 2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> > > > > Actually DAMON is internally setting such maximum region size based on the
> > > > > min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> > > > > is applied only in regions merge time. That's why it requires the regions
> > > > > split to happen sufficiently until the real fixed granularity monitoring is
> > > > > started.
> > > > >
> > > > > And I think this behavior is just a bug, or suboptimum implementation at least.
> > > > > That is, users set the minimum number of regions but it may not really be kept.
> > > > > That's definitely confusing behavior. Actually there was a similar case that
> > > > > number of regions can be larger than the max_nr_regions. We fixed it, with
> > > > > commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> > > > > max_nr_regions is unmet"). I think we discussed about similar case for
> > > > > min_nr_regions, but I cannot find the discussion for now.
> > > > >
> > > > > So, I think it is better to fix this rather than introducing a new parameter.
> > > >
> > > > I agree with that.
> > > >
> > > > > Maybe we can split regions based on the min_nr_regions based size limit, before
> > > > > starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> > > > > violation, there could be yet another corner case on online parameters commit
> > > > > situation, so it would better to check the case, too. You could implement such
> > > > > fix on your own, or let me do that. In the latter case, if you don't mind, I
> > > > > will add your Reported-by: tag to the fix. Please let me know your
> > > > > preferrence.
> > > >
> > > > You'll be better able to fix it, so please fix it at your convenience.
> > > > Adding Reported-by: tag is fine.
> > >
> > > Sounds good, I will do so!
> >
> > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > current merge window is finished. Please let me know if you find something
> > wrong there!
> >
> > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
>
> Thank you for posting the patch series.
>
> I tried it and found that patch 2/3 made things worse than before in terms of
> monitoring at page size granularity.
Thank you for sharing the test results!
> This is because, in my evaluation, new
> target processes are added without stopping kdamond, so processes added later
> are not initially monitored at page size granularity.
Nice catch. But, the min_nr_regions based region split of vaddr was triggered
by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
the regions were split before the patch? Probably I'm missing something.
Could you please clarify?
>
> Would performing a split operation like damon_apply_min_nr_regions() within
> damon_set_regions() solve the problem?
Yes, that's the plan. The split operation should be done for online updates of
total size of monitoring regions and min_nr_regions. I'm preparing a followup
patch series for that. While working on it, however, I found it requires some
refactoring and cleanup that taking time longer than I expected. Meanwhile I
was thinking your issue is only at the beginning of kdamond, and I didn't want
to make you wait too long. Hence posted the series first.
Thanks,
SJ
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-17 15:15 ` SeongJae Park
@ 2026-02-18 8:20 ` Akinobu Mita
2026-02-18 15:40 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-02-18 8:20 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon
2026年2月18日(水) 0:15 SeongJae Park <sj@kernel.org>:
>
> On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
> > >
> > > On Tue, 27 Jan 2026 17:12:23 -0800 SeongJae Park <sj@kernel.org> wrote:
> > >
> > > > On Tue, 27 Jan 2026 21:56:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > > >
> > > > > 2026年1月27日(火) 15:43 SeongJae Park <sj@kernel.org>:
> > > > > > Actually DAMON is internally setting such maximum region size based on the
> > > > > > min_nr_regions parameter, via damon_region_sz_limit(). Nonetheless, the limit
> > > > > > is applied only in regions merge time. That's why it requires the regions
> > > > > > split to happen sufficiently until the real fixed granularity monitoring is
> > > > > > started.
> > > > > >
> > > > > > And I think this behavior is just a bug, or suboptimum implementation at least.
> > > > > > That is, users set the minimum number of regions but it may not really be kept.
> > > > > > That's definitely confusing behavior. Actually there was a similar case that
> > > > > > number of regions can be larger than the max_nr_regions. We fixed it, with
> > > > > > commit 310d6c15e910 ("mm/damon/core: merge regions aggressively when
> > > > > > max_nr_regions is unmet"). I think we discussed about similar case for
> > > > > > min_nr_regions, but I cannot find the discussion for now.
> > > > > >
> > > > > > So, I think it is better to fix this rather than introducing a new parameter.
> > > > >
> > > > > I agree with that.
> > > > >
> > > > > > Maybe we can split regions based on the min_nr_regions based size limit, before
> > > > > > starting the main loop of kdamond_fn(). Similar to the max_nr_regions
> > > > > > violation, there could be yet another corner case on online parameters commit
> > > > > > situation, so it would better to check the case, too. You could implement such
> > > > > > fix on your own, or let me do that. In the latter case, if you don't mind, I
> > > > > > will add your Reported-by: tag to the fix. Please let me know your
> > > > > > preferrence.
> > > > >
> > > > > You'll be better able to fix it, so please fix it at your convenience.
> > > > > Adding Reported-by: tag is fine.
> > > >
> > > > Sounds good, I will do so!
> > >
> > > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > > current merge window is finished. Please let me know if you find something
> > > wrong there!
> > >
> > > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
> >
> > Thank you for posting the patch series.
> >
> > I tried it and found that patch 2/3 made things worse than before in terms of
> > monitoring at page size granularity.
>
> Thank you for sharing the test results!
>
> > This is because, in my evaluation, new
> > target processes are added without stopping kdamond, so processes added later
> > are not initially monitored at page size granularity.
>
> Nice catch. But, the min_nr_regions based region split of vaddr was triggered
> by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
> the regions were split before the patch? Probably I'm missing something.
> Could you please clarify?
Before applying PATCH 2/3 ("mm/damon/vaddr: do not split regions for
min_nr_regions"), the damon regions of processes newly added to the monitoring
targets after kdamond started were split by damon_va_evenly_split_region().
After applying PATCH 2/3, the call to damon_va_evenly_split_region() is
omitted, so those new processes are monitored by larger damon regions.
> > Would performing a split operation like damon_apply_min_nr_regions() within
> > damon_set_regions() solve the problem?
>
> Yes, that's the plan. The split operation should be done for online updates of
> total size of monitoring regions and min_nr_regions. I'm preparing a followup
> patch series for that. While working on it, however, I found it requires some
> refactoring and cleanup that taking time longer than I expected. Meanwhile I
> was thinking your issue is only at the beginning of kdamond, and I didn't want
> to make you wait too long. Hence posted the series first.
I see, thank you for the work.
In another evaluation using paddr, no new damon regions are added after kdamond
is started, so applying the current patch series enabled monitoring at page
size granularity.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-18 8:20 ` Akinobu Mita
@ 2026-02-18 15:40 ` SeongJae Park
2026-02-19 6:28 ` Akinobu Mita
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-02-18 15:40 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Wed, 18 Feb 2026 17:20:04 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年2月18日(水) 0:15 SeongJae Park <sj@kernel.org>:
> >
> > On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> >
> > > 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
[...]
> > > > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > > > current merge window is finished. Please let me know if you find something
> > > > wrong there!
> > > >
> > > > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
> > >
> > > Thank you for posting the patch series.
> > >
> > > I tried it and found that patch 2/3 made things worse than before in terms of
> > > monitoring at page size granularity.
> >
> > Thank you for sharing the test results!
> >
> > > This is because, in my evaluation, new
> > > target processes are added without stopping kdamond, so processes added later
> > > are not initially monitored at page size granularity.
> >
> > Nice catch. But, the min_nr_regions based region split of vaddr was triggered
> > by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
> > the regions were split before the patch? Probably I'm missing something.
> > Could you please clarify?
>
> Before applying PATCH 2/3 ("mm/damon/vaddr: do not split regions for
> min_nr_regions"), the damon regions of processes newly added to the monitoring
> targets after kdamond started were split by damon_va_evenly_split_region().
Thank you for adding this explanation. But, damon_va_evenly_split_region() is
called by only __damon_va_init_regions(), which is again called by only
damon_va_init(). And damon_va_init() is called from kdamond_fn(), only at the
beginning, before starting the main loop. If you add new monitoring targets
after kdamond started and therefore the damon_va_init() is called,
damon_va_init() cannot be called again. So damon_va_evenly_split_region() is
also not called. I confirmed this using damo like below:
$ sudo damo start --target_pid $$ --monitoring_intervals 1s 5s 10s
$ sudo damo tune --target_pid $$ --target_pid $other_process_pid \
--monitoring_intervals 1s 5s 10s \
--monitoring_nr_regions_range 20 1000
While running the command, I monitor the number of regions of targets using
damon:damon_aggregated trace event, and confirmed the initial region is evenly
split, but the new monitoring target regions that added by the second command
is not evenly split from the beginning.
Am I missing something?
> After applying PATCH 2/3, the call to damon_va_evenly_split_region() is
> omitted, so those new processes are monitored by larger damon regions.
So I'm still not understanding why you saying this.
>
> > > Would performing a split operation like damon_apply_min_nr_regions() within
> > > damon_set_regions() solve the problem?
> >
> > Yes, that's the plan. The split operation should be done for online updates of
> > total size of monitoring regions and min_nr_regions. I'm preparing a followup
> > patch series for that. While working on it, however, I found it requires some
> > refactoring and cleanup that taking time longer than I expected. Meanwhile I
> > was thinking your issue is only at the beginning of kdamond, and I didn't want
> > to make you wait too long. Hence posted the series first.
>
> I see, thank you for the work.
>
> In another evaluation using paddr, no new damon regions are added after kdamond
> is started, so applying the current patch series enabled monitoring at page
> size granularity.
Glad to hear that it helps the case!
Thanks,
SJ
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-18 15:40 ` SeongJae Park
@ 2026-02-19 6:28 ` Akinobu Mita
2026-02-19 6:49 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-02-19 6:28 UTC (permalink / raw)
To: SeongJae Park; +Cc: damon
2026年2月19日(木) 0:40 SeongJae Park <sj@kernel.org>:
>
> On Wed, 18 Feb 2026 17:20:04 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > 2026年2月18日(水) 0:15 SeongJae Park <sj@kernel.org>:
> > >
> > > On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > >
> > > > 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
> [...]
> > > > > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > > > > current merge window is finished. Please let me know if you find something
> > > > > wrong there!
> > > > >
> > > > > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
> > > >
> > > > Thank you for posting the patch series.
> > > >
> > > > I tried it and found that patch 2/3 made things worse than before in terms of
> > > > monitoring at page size granularity.
> > >
> > > Thank you for sharing the test results!
> > >
> > > > This is because, in my evaluation, new
> > > > target processes are added without stopping kdamond, so processes added later
> > > > are not initially monitored at page size granularity.
> > >
> > > Nice catch. But, the min_nr_regions based region split of vaddr was triggered
> > > by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
> > > the regions were split before the patch? Probably I'm missing something.
> > > Could you please clarify?
> >
> > Before applying PATCH 2/3 ("mm/damon/vaddr: do not split regions for
> > min_nr_regions"), the damon regions of processes newly added to the monitoring
> > targets after kdamond started were split by damon_va_evenly_split_region().
>
> Thank you for adding this explanation. But, damon_va_evenly_split_region() is
> called by only __damon_va_init_regions(), which is again called by only
> damon_va_init(). And damon_va_init() is called from kdamond_fn(), only at the
> beginning, before starting the main loop. If you add new monitoring targets
> after kdamond started and therefore the damon_va_init() is called,
> damon_va_init() cannot be called again. So damon_va_evenly_split_region() is
> also not called. I confirmed this using damo like below:
>
> $ sudo damo start --target_pid $$ --monitoring_intervals 1s 5s 10s
> $ sudo damo tune --target_pid $$ --target_pid $other_process_pid \
> --monitoring_intervals 1s 5s 10s \
> --monitoring_nr_regions_range 20 1000
>
> While running the command, I monitor the number of regions of targets using
> damon:damon_aggregated trace event, and confirmed the initial region is evenly
> split, but the new monitoring target regions that added by the second command
> is not evenly split from the beginning.
>
> Am I missing something?
Your explanation is correct.
It was only my own internal changes that were affecting it (calling ops.init()
from damon_commit_ctx()).
https://lore.kernel.org/damon/20260123021014.26915-2-akinobu.mita@gmail.com/
So the current patch series hasn't made things worse. Sorry for the mistake.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-19 6:28 ` Akinobu Mita
@ 2026-02-19 6:49 ` SeongJae Park
2026-03-03 1:05 ` SeongJae Park
0 siblings, 1 reply; 22+ messages in thread
From: SeongJae Park @ 2026-02-19 6:49 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon
On Thu, 19 Feb 2026 15:28:24 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> 2026年2月19日(木) 0:40 SeongJae Park <sj@kernel.org>:
> >
> > On Wed, 18 Feb 2026 17:20:04 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> >
> > > 2026年2月18日(水) 0:15 SeongJae Park <sj@kernel.org>:
> > > >
> > > > On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > > >
> > > > > 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
> > [...]
> > > > > > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > > > > > current merge window is finished. Please let me know if you find something
> > > > > > wrong there!
> > > > > >
> > > > > > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
> > > > >
> > > > > Thank you for posting the patch series.
> > > > >
> > > > > I tried it and found that patch 2/3 made things worse than before in terms of
> > > > > monitoring at page size granularity.
> > > >
> > > > Thank you for sharing the test results!
> > > >
> > > > > This is because, in my evaluation, new
> > > > > target processes are added without stopping kdamond, so processes added later
> > > > > are not initially monitored at page size granularity.
> > > >
> > > > Nice catch. But, the min_nr_regions based region split of vaddr was triggered
> > > > by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
> > > > the regions were split before the patch? Probably I'm missing something.
> > > > Could you please clarify?
> > >
> > > Before applying PATCH 2/3 ("mm/damon/vaddr: do not split regions for
> > > min_nr_regions"), the damon regions of processes newly added to the monitoring
> > > targets after kdamond started were split by damon_va_evenly_split_region().
> >
> > Thank you for adding this explanation. But, damon_va_evenly_split_region() is
> > called by only __damon_va_init_regions(), which is again called by only
> > damon_va_init(). And damon_va_init() is called from kdamond_fn(), only at the
> > beginning, before starting the main loop. If you add new monitoring targets
> > after kdamond started and therefore the damon_va_init() is called,
> > damon_va_init() cannot be called again. So damon_va_evenly_split_region() is
> > also not called. I confirmed this using damo like below:
> >
> > $ sudo damo start --target_pid $$ --monitoring_intervals 1s 5s 10s
> > $ sudo damo tune --target_pid $$ --target_pid $other_process_pid \
> > --monitoring_intervals 1s 5s 10s \
> > --monitoring_nr_regions_range 20 1000
> >
> > While running the command, I monitor the number of regions of targets using
> > damon:damon_aggregated trace event, and confirmed the initial region is evenly
> > split, but the new monitoring target regions that added by the second command
> > is not evenly split from the beginning.
> >
> > Am I missing something?
>
> Your explanation is correct.
> It was only my own internal changes that were affecting it (calling ops.init()
> from damon_commit_ctx()).
>
> https://lore.kernel.org/damon/20260123021014.26915-2-akinobu.mita@gmail.com/
Makes sense, thank you for clarifying this!
>
> So the current patch series hasn't made things worse. Sorry for the mistake.
Glad to hear that. I will post the series without RFC after the current merge
window. I will also try to make the planned followup for respecting the
min_nr_regions for the online-added targets and updated min_nr_regions before
the next merge window.
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-01-24 2:48 ` SeongJae Park
@ 2026-02-23 8:08 ` Namhyung Kim
2026-02-25 6:48 ` Akinobu Mita
0 siblings, 1 reply; 22+ messages in thread
From: Namhyung Kim @ 2026-02-23 8:08 UTC (permalink / raw)
To: SeongJae Park; +Cc: Akinobu Mita, damon, linux-perf-users
Hello,
On Fri, Jan 23, 2026 at 06:48:03PM -0800, SeongJae Park wrote:
> Cc-ing linux-perf-users@ for any comment from perf people, about the use of
> perf event from DAMON.
Sorry for the long delay.
>
> On Fri, 23 Jan 2026 18:39:20 SeongJae Park <sj@kernel.org> wrote:
>
> > On Fri, 23 Jan 2026 11:10:10 +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.
> >
> > Very interesting patch series. Thank you for sharing this Akinobu!
> >
> > I only took a glance on the patches, but my understanding is that this series
> > modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> > utilize the sampled perf events in the perf events buffer as the source of
> > DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> > type perf events makes the perf events buffer filled with memory access event
> > information with the access destination address. The event will be sampled
> > based on time (e.g., one access event per X milliseconds). And probably that
> > sample data could include more information includign the CPU and the process
> > that executing the sampled access? Please correct me if I'm wrong and add more
> > details I'm missing, as my understanding of perf event is very poor.
I'm afraid you need to deal with PMU hardware details. For example
PERF_SAMPLE_ADDR may not be available depends on events or sometimes it
may not have valid values in some samples like on AMD IBS. Also hybrid
CPUs may not have same events on both CPUs.
Maybe it's better to fix which event you want to use on each CPU
architecture.
> >
> > And one quick question. Can this work on virtual machines? I'm asking this
> > question the for following reason. I'm actuaally working on a similar project
> > that extends DAMON for page fault based access events sampling [1]. The
> > project aims to use page fault event rather than other h/w features such as AMD
> > IBS or Intel PEBS, because my understanding is that such h/w features are not
> > available on virtual machines.
KVM supports PMU virtualization. But the existing vPMU support has a
lot of overhead. Recently mediated vPMU patchset was added so you can
try that instead. You'll need to pass appropriate options to qemu.
https://lore.kernel.org/lkml/20251206001720.468579-1-seanjc@google.com/
Thanks,
Namhyung
> >
> > >
> > > Since perf event-based access checks do not require modifying the PTE
> > > accessed-bit for pages representing each damon region, this patch series
> > > also includes a feature that allows you to set upper and lower limits on
> > > the damon region size to enable access checks with finer granularity.
> >
> > I was also thinking about extending DAMON with AMD IBS or Intel PEBS like h/w
> > features for this kind of sub-page granularity access monitoring. So this
> > makes sense to me, and sounds useful!
> >
> > >
> > > 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.0
> > >
> > > Any feedback or advice on the patch set would be greatly appreciated.
> > >
> > > Akinobu Mita (4):
> > > 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
> >
> > I find your patches are introducing new infra code for this extension. It
> > seems bit specialized for perf event only, though. I'm concerned if future
> > extension for another access check primitives cannot reuse the infra.
> >
> > My DAMON extension project [1] is for page fault based access monitoring, but
> > it also introduces a framework for general multiple access sampling primitives.
> > I'm wondering if perf event based extension can be implemented using the
> > general acces ssampling primitives infra code, and if you already considered
> > that but found it is not feasible.
> >
> > > mm/damon: allow user to set min and max size of region
> >
> > The min size setup makes sense. I understand the max size is for disabling the
> > regions adjustment (merge/split) mechanism. IOW, for fixed granularity
> > monitoring. Users can do that by setting min_nr_regions and max_nr_regions
> > same [2], though. So, max size setting seems not really needed.
> >
> > Again, great RFC patch series, thank you for sharing! I'm looking forward to
> > your answers to above high level questions and comments.
> >
> > >
> > > .../ABI/testing/sysfs-kernel-mm-damon | 11 +
> > > include/linux/damon.h | 42 +-
> > > mm/damon/core.c | 202 ++++-
> > > mm/damon/ops-common.h | 39 +
> > > mm/damon/paddr.c | 106 ++-
> > > mm/damon/sysfs.c | 402 +++++++++-
> > > mm/damon/tests/core-kunit.h | 2 +-
> > > mm/damon/tests/sysfs-kunit.h | 2 +
> > > mm/damon/tests/vaddr-kunit.h | 7 +-
> > > mm/damon/vaddr.c | 690 ++++++++++++++++--
> > > 10 files changed, 1425 insertions(+), 78 deletions(-)
> > >
> > > --
> > > 2.43.0
> >
> > [1] https://lore.kernel.org/damon/20251208062943.68824-1-sj@kernel.org/
> > [2] https://origin.kernel.org/doc/html/latest/mm/damon/faq.html#can-i-simply-monitor-page-granularity
> >
> >
> > Thanks,
> > SJ
> >
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-23 8:08 ` Namhyung Kim
@ 2026-02-25 6:48 ` Akinobu Mita
2026-02-26 1:24 ` Namhyung Kim
0 siblings, 1 reply; 22+ messages in thread
From: Akinobu Mita @ 2026-02-25 6:48 UTC (permalink / raw)
To: Namhyung Kim; +Cc: SeongJae Park, damon, linux-perf-users
2026年2月23日(月) 17:08 Namhyung Kim <namhyung@kernel.org>:
>
> Hello,
>
> On Fri, Jan 23, 2026 at 06:48:03PM -0800, SeongJae Park wrote:
> > Cc-ing linux-perf-users@ for any comment from perf people, about the use of
> > perf event from DAMON.
>
> Sorry for the long delay.
>
> >
> > On Fri, 23 Jan 2026 18:39:20 SeongJae Park <sj@kernel.org> wrote:
> >
> > > On Fri, 23 Jan 2026 11:10:10 +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.
> > >
> > > Very interesting patch series. Thank you for sharing this Akinobu!
> > >
> > > I only took a glance on the patches, but my understanding is that this series
> > > modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> > > utilize the sampled perf events in the perf events buffer as the source of
> > > DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> > > type perf events makes the perf events buffer filled with memory access event
> > > information with the access destination address. The event will be sampled
> > > based on time (e.g., one access event per X milliseconds). And probably that
> > > sample data could include more information includign the CPU and the process
> > > that executing the sampled access? Please correct me if I'm wrong and add more
> > > details I'm missing, as my understanding of perf event is very poor.
>
> I'm afraid you need to deal with PMU hardware details. For example
> PERF_SAMPLE_ADDR may not be available depends on events or sometimes it
> may not have valid values in some samples like on AMD IBS. Also hybrid
> CPUs may not have same events on both CPUs.
>
> Maybe it's better to fix which event you want to use on each CPU
> architecture.
Thank you for your advice.
Is it possible to get a list of PMUs that have the ability to get addresses
(PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR) from the kernel at runtime?
Or is it possible to detect that a perf_event initialized with a certain
perf_event_attr does not have the ability to obtain the address?
> > > And one quick question. Can this work on virtual machines? I'm asking this
> > > question the for following reason. I'm actuaally working on a similar project
> > > that extends DAMON for page fault based access events sampling [1]. The
> > > project aims to use page fault event rather than other h/w features such as AMD
> > > IBS or Intel PEBS, because my understanding is that such h/w features are not
> > > available on virtual machines.
>
> KVM supports PMU virtualization. But the existing vPMU support has a
> lot of overhead. Recently mediated vPMU patchset was added so you can
> try that instead. You'll need to pass appropriate options to qemu.
>
> https://lore.kernel.org/lkml/20251206001720.468579-1-seanjc@google.com/
Thank you for the important information.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-25 6:48 ` Akinobu Mita
@ 2026-02-26 1:24 ` Namhyung Kim
0 siblings, 0 replies; 22+ messages in thread
From: Namhyung Kim @ 2026-02-26 1:24 UTC (permalink / raw)
To: Akinobu Mita; +Cc: SeongJae Park, damon, linux-perf-users
On Wed, Feb 25, 2026 at 03:48:29PM +0900, Akinobu Mita wrote:
> 2026年2月23日(月) 17:08 Namhyung Kim <namhyung@kernel.org>:
> >
> > Hello,
> >
> > On Fri, Jan 23, 2026 at 06:48:03PM -0800, SeongJae Park wrote:
> > > Cc-ing linux-perf-users@ for any comment from perf people, about the use of
> > > perf event from DAMON.
> >
> > Sorry for the long delay.
> >
> > >
> > > On Fri, 23 Jan 2026 18:39:20 SeongJae Park <sj@kernel.org> wrote:
> > >
> > > > On Fri, 23 Jan 2026 11:10:10 +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.
> > > >
> > > > Very interesting patch series. Thank you for sharing this Akinobu!
> > > >
> > > > I only took a glance on the patches, but my understanding is that this series
> > > > modifies DAMON to be able to enable perf events of PERF_SAMPLE_ADDR type, and
> > > > utilize the sampled perf events in the perf events buffer as the source of
> > > > DAMON's access checks. In more detail, I understand enabling PERF_SAMPLE_ADDR
> > > > type perf events makes the perf events buffer filled with memory access event
> > > > information with the access destination address. The event will be sampled
> > > > based on time (e.g., one access event per X milliseconds). And probably that
> > > > sample data could include more information includign the CPU and the process
> > > > that executing the sampled access? Please correct me if I'm wrong and add more
> > > > details I'm missing, as my understanding of perf event is very poor.
> >
> > I'm afraid you need to deal with PMU hardware details. For example
> > PERF_SAMPLE_ADDR may not be available depends on events or sometimes it
> > may not have valid values in some samples like on AMD IBS. Also hybrid
> > CPUs may not have same events on both CPUs.
> >
> > Maybe it's better to fix which event you want to use on each CPU
> > architecture.
>
> Thank you for your advice.
>
> Is it possible to get a list of PMUs that have the ability to get addresses
> (PERF_SAMPLE_ADDR or PERF_SAMPLE_PHYS_ADDR) from the kernel at runtime?
You can lookup 'mem-loads' events on Intel CPUs. It should be 'cpu',
'cpu_core' and/or 'cpu_atom'. On AMD, you can use 'ibs_op'.
$ grep -l . /sys/bus/event_source/devices/*/events/mem-loads
/sys/bus/event_source/devices/cpu/events/mem-loads
>
> Or is it possible to detect that a perf_event initialized with a certain
> perf_event_attr does not have the ability to obtain the address?
I don't think so. IIRC it will just have address of 0 if not supported.
>
> > > > And one quick question. Can this work on virtual machines? I'm asking this
> > > > question the for following reason. I'm actuaally working on a similar project
> > > > that extends DAMON for page fault based access events sampling [1]. The
> > > > project aims to use page fault event rather than other h/w features such as AMD
> > > > IBS or Intel PEBS, because my understanding is that such h/w features are not
> > > > available on virtual machines.
> >
> > KVM supports PMU virtualization. But the existing vPMU support has a
> > lot of overhead. Recently mediated vPMU patchset was added so you can
> > try that instead. You'll need to pass appropriate options to qemu.
> >
> > https://lore.kernel.org/lkml/20251206001720.468579-1-seanjc@google.com/
>
> Thank you for the important information.
No problem! You may go with the existing vPMU if you just plan to use a
couple of events only.
Thanks,
Namhyung
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 0/4] mm/damon: introduce perf event based access check
2026-02-19 6:49 ` SeongJae Park
@ 2026-03-03 1:05 ` SeongJae Park
0 siblings, 0 replies; 22+ messages in thread
From: SeongJae Park @ 2026-03-03 1:05 UTC (permalink / raw)
To: SeongJae Park; +Cc: Akinobu Mita, damon
On Wed, 18 Feb 2026 22:49:48 -0800 SeongJae Park <sj@kernel.org> wrote:
> On Thu, 19 Feb 2026 15:28:24 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
>
> > 2026年2月19日(木) 0:40 SeongJae Park <sj@kernel.org>:
> > >
> > > On Wed, 18 Feb 2026 17:20:04 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > >
> > > > 2026年2月18日(水) 0:15 SeongJae Park <sj@kernel.org>:
> > > > >
> > > > > On Tue, 17 Feb 2026 22:32:43 +0900 Akinobu Mita <akinobu.mita@gmail.com> wrote:
> > > > >
> > > > > > 2026年2月17日(火) 9:13 SeongJae Park <sj@kernel.org>:
> > > [...]
> > > > > > > I just posted an RFC patch series [1] for this. I will drop RFC tag after the
> > > > > > > current merge window is finished. Please let me know if you find something
> > > > > > > wrong there!
> > > > > > >
> > > > > > > [1] https://lore.kernel.org/20260217000400.69056-1-sj@kernel.org
> > > > > >
> > > > > > Thank you for posting the patch series.
> > > > > >
> > > > > > I tried it and found that patch 2/3 made things worse than before in terms of
> > > > > > monitoring at page size granularity.
> > > > >
> > > > > Thank you for sharing the test results!
> > > > >
> > > > > > This is because, in my evaluation, new
> > > > > > target processes are added without stopping kdamond, so processes added later
> > > > > > are not initially monitored at page size granularity.
> > > > >
> > > > > Nice catch. But, the min_nr_regions based region split of vaddr was triggered
> > > > > by damon_va_init(), which is called before the kdamond_fn()'s main loop. How
> > > > > the regions were split before the patch? Probably I'm missing something.
> > > > > Could you please clarify?
> > > >
> > > > Before applying PATCH 2/3 ("mm/damon/vaddr: do not split regions for
> > > > min_nr_regions"), the damon regions of processes newly added to the monitoring
> > > > targets after kdamond started were split by damon_va_evenly_split_region().
> > >
> > > Thank you for adding this explanation. But, damon_va_evenly_split_region() is
> > > called by only __damon_va_init_regions(), which is again called by only
> > > damon_va_init(). And damon_va_init() is called from kdamond_fn(), only at the
> > > beginning, before starting the main loop. If you add new monitoring targets
> > > after kdamond started and therefore the damon_va_init() is called,
> > > damon_va_init() cannot be called again. So damon_va_evenly_split_region() is
> > > also not called. I confirmed this using damo like below:
> > >
> > > $ sudo damo start --target_pid $$ --monitoring_intervals 1s 5s 10s
> > > $ sudo damo tune --target_pid $$ --target_pid $other_process_pid \
> > > --monitoring_intervals 1s 5s 10s \
> > > --monitoring_nr_regions_range 20 1000
> > >
> > > While running the command, I monitor the number of regions of targets using
> > > damon:damon_aggregated trace event, and confirmed the initial region is evenly
> > > split, but the new monitoring target regions that added by the second command
> > > is not evenly split from the beginning.
> > >
> > > Am I missing something?
> >
> > Your explanation is correct.
> > It was only my own internal changes that were affecting it (calling ops.init()
> > from damon_commit_ctx()).
> >
> > https://lore.kernel.org/damon/20260123021014.26915-2-akinobu.mita@gmail.com/
>
> Makes sense, thank you for clarifying this!
>
> >
> > So the current patch series hasn't made things worse. Sorry for the mistake.
>
> Glad to hear that. I will post the series without RFC after the current merge
> window. I will also try to make the planned followup for respecting the
> min_nr_regions for the online-added targets and updated min_nr_regions before
> the next merge window.
The non-RFC series is posted [1]. The version also do the min_nr_regions
respect for online updates together.
[1] https://lore.kernel.org/20260228222831.7232-1-sj@kernel.org
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-03-03 1:05 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-23 2:10 [RFC PATCH 0/4] mm/damon: introduce perf event based access check Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 1/4] mm/damon/core: add common code for " Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 2/4] mm/damon/vaddr: support " Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 3/4] mm/damon/paddr: " Akinobu Mita
2026-01-23 2:10 ` [RFC PATCH 4/4] mm/damon: allow user to set min and max size of region Akinobu Mita
2026-01-24 2:39 ` [RFC PATCH 0/4] mm/damon: introduce perf event based access check SeongJae Park
2026-01-24 2:48 ` SeongJae Park
2026-02-23 8:08 ` Namhyung Kim
2026-02-25 6:48 ` Akinobu Mita
2026-02-26 1:24 ` Namhyung Kim
2026-01-27 1:29 ` Akinobu Mita
2026-01-27 6:43 ` SeongJae Park
2026-01-27 12:56 ` Akinobu Mita
2026-01-28 1:12 ` SeongJae Park
2026-02-17 0:13 ` SeongJae Park
2026-02-17 13:32 ` Akinobu Mita
2026-02-17 15:15 ` SeongJae Park
2026-02-18 8:20 ` Akinobu Mita
2026-02-18 15:40 ` SeongJae Park
2026-02-19 6:28 ` Akinobu Mita
2026-02-19 6:49 ` SeongJae Park
2026-03-03 1:05 ` SeongJae Park
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox