[RFC v2 4/9] mm/damon/debugfs: Allow users to set initial monitoring target regions

SeongJae Park
2020-06-03T14:13:49Z

Some users would want to monitor only a part of the entire virtual
memory address space. The '->init_target_regions' callback is therefore
provided, but only programming interface can use it.

For the reason, this commit introduces a new debugfs file,
'init_region'. Users can specify which initial monitoring target
address regions they want by writing special input to the file. The
input should describe each region in each line in below form:

<pid> <start address> <end address>

This commit also makes the default '->init_target_regions' callback,
'kdamon_init_vm_regions()' to do nothing if the user has set the initial
target regions already.

Note that the regions will be updated to cover entire memory mapped
regions after 'regions update interval'. If you want the regions to not
be updated after the initial setting, you could set the interval as a
very long time, say, a few decades.

Signed-off-by: SeongJae Park <sjpark@amazon.de>
---
mm/damon.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 163 insertions(+), 6 deletions(-)

diff --git a/mm/damon.c b/mm/damon.c
index 2a3c1abb9b47..c7806e81248a 100644
--- a/mm/damon.c
+++ b/mm/damon.c
@@ -472,8 +472,10 @@ void kdamond_init_vm_regions(struct damon_ctx *ctx)
{
struct damon_task *t;

- damon_for_each_task(t, ctx)
- damon_init_vm_regions_of(ctx, t);
+ damon_for_each_task(t, ctx) {
+ if (!nr_damon_regions(t))
+ damon_init_vm_regions_of(ctx, t);
+ }
}

static void damon_mkold(struct mm_struct *mm, unsigned long addr)
@@ -1685,6 +1687,154 @@ static ssize_t debugfs_record_write(struct file *file,
return ret;
}

+/* This function should not be called while the monitoring is ongoing */
+static ssize_t sprint_init_regions(struct damon_ctx *c, char *buf, ssize_t len)
+{
+ struct damon_task *t;
+ struct damon_region *r;
+ int written = 0;
+ int rc;
+
+ damon_for_each_task(t, c) {
+ damon_for_each_region(r, t) {
+ rc = snprintf(&buf[written], len - written,
+ "%d %lu %lu\n",
+ t->pid, r->ar.start, r->ar.end);
+ if (!rc)
+ return -ENOMEM;
+ written += rc;
+ }
+ }
+ return written;
+}
+
+static ssize_t debugfs_init_regions_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct damon_ctx *ctx = &damon_user_ctx;
+ char *kbuf;
+ ssize_t len;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ mutex_lock(&ctx->kdamond_lock);
+ if (ctx->kdamond) {
+ mutex_unlock(&ctx->kdamond_lock);
+ return -EBUSY;
+ }
+
+ len = sprint_init_regions(ctx, kbuf, count);
+ mutex_unlock(&ctx->kdamond_lock);
+ if (len < 0)
+ goto out;
+ len = simple_read_from_buffer(buf, count, ppos, kbuf, len);
+
+out:
+ kfree(kbuf);
+ return len;
+}
+
+static int add_init_region(struct damon_ctx *c,
+ int pid, struct damon_addr_range *ar)
+{
+ struct damon_task *t;
+ struct damon_region *r, *prev;
+ int rc = -EINVAL;
+
+ if (ar->start >= ar->end)
+ return -EINVAL;
+
+ damon_for_each_task(t, c) {
+ if (t->pid == pid) {
+ r = damon_new_region(c, ar->start, ar->end);
+ if (!r)
+ return -ENOMEM;
+ damon_add_region(r, t);
+ if (nr_damon_regions(t) > 1) {
+ prev = damon_prev_region(r);
+ if (prev->ar.end > r->ar.start) {
+ damon_destroy_region(r);
+ return -EINVAL;
+ }
+ }
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static int set_init_regions(struct damon_ctx *c, const char *str, ssize_t len)
+{
+ struct damon_task *t;
+ struct damon_region *r, *next;
+ int pos = 0, parsed, ret;
+ int pid;
+ struct damon_addr_range ar;
+ int err;
+
+ damon_for_each_task(t, c) {
+ damon_for_each_region_safe(r, next, t)
+ damon_destroy_region(r);
+ }
+
+ while (pos < len) {
+ ret = sscanf(&str[pos], "%d %lu %lu%n",
+ &pid, &ar.start, &ar.end, &parsed);
+ if (ret != 3)
+ break;
+ err = add_init_region(c, pid, &ar);
+ if (err)
+ goto fail;
+ pos += parsed;
+ }
+
+ return 0;
+
+fail:
+ damon_for_each_task(t, c) {
+ damon_for_each_region_safe(r, next, t)
+ damon_destroy_region(r);
+ }
+ return err;
+}
+
+static ssize_t debugfs_init_regions_write(struct file *file, const char __user
+ *buf, size_t count, loff_t *ppos)
+{
+ struct damon_ctx *ctx = &damon_user_ctx;
+ char *kbuf;
+ ssize_t ret;
+ int err;
+
+ if (*ppos)
+ return -EINVAL;
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = simple_write_to_buffer(kbuf, count, ppos, buf, count);
+ if (ret < 0)
+ goto out;
+
+ mutex_lock(&ctx->kdamond_lock);
+ if (ctx->kdamond) {
+ ret = -EBUSY;
+ goto unlock_out;
+ }
+
+ err = set_init_regions(ctx, kbuf, ret);
+ if (err)
+ ret = err;
+
+unlock_out:
+ mutex_unlock(&ctx->kdamond_lock);
+out:
+ kfree(kbuf);
+ return ret;
+}

static ssize_t debugfs_attrs_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
@@ -1766,6 +1916,12 @@ static const struct file_operations record_fops = {
.write = debugfs_record_write,
};

+static const struct file_operations init_regions_fops = {
+ .owner = THIS_MODULE,
+ .read = debugfs_init_regions_read,
+ .write = debugfs_init_regions_write,
+};
+
static const struct file_operations attrs_fops = {
.owner = THIS_MODULE,
.read = debugfs_attrs_read,
@@ -1776,10 +1932,11 @@ static struct dentry *debugfs_root;

static int __init damon_debugfs_init(void)
{
- const char * const file_names[] = {"attrs", "record", "schemes",
- "pids", "monitor_on"};
- const struct file_operations *fops[] = {&attrs_fops, &record_fops,
- &schemes_fops, &pids_fops, &monitor_on_fops};
+ const char * const file_names[] = {"attrs", "init_regions", "record",
+ "schemes", "pids", "monitor_on"};
+ const struct file_operations *fops[] = {&attrs_fops,
+ &init_regions_fops, &record_fops, &schemes_fops, &pids_fops,
+ &monitor_on_fops};
int i;

debugfs_root = debugfs_create_dir("damon", NULL);
--
2.17.1