[RFC PATCH 3/5] mm/damon: Move recording feature from core to dbgfs

From: SeongJae Park
Date: Mon Sep 28 2020 - 06:37:29 EST


From: SeongJae Park <sjpark@xxxxxxxxx>

DAMON passes the monitoring results to user space via two ways: 1) a
tracepoint and 2) it's recording feature. The recording feature is for
the users who want simplest use.

However, as the feature is for the user space only while the core is
fundamentally a framework for the kernel space, keeping the feature in
the core would make no sense. Therefore, this commit moves the feature
to the debugfs interface of DAMON.

Signed-off-by: SeongJae Park <sjpark@xxxxxxxxx>
---
include/linux/damon.h | 23 +---
mm/damon/core-test.h | 57 ++-------
mm/damon/core.c | 150 +-----------------------
mm/damon/dbgfs-test.h | 87 +++++++++++++-
mm/damon/dbgfs.c | 264 ++++++++++++++++++++++++++++++++++++++++--
5 files changed, 359 insertions(+), 222 deletions(-)

diff --git a/include/linux/damon.h b/include/linux/damon.h
index 505e6261cefa..606e59f785a2 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -134,14 +134,6 @@ struct damos {
* in case of virtual memory monitoring) and applies the changes for each
* @regions_update_interval. All time intervals are in micro-seconds.
*
- * @rbuf: In-memory buffer for monitoring result recording.
- * @rbuf_len: The length of @rbuf.
- * @rbuf_offset: The offset for next write to @rbuf.
- * @rfile_path: Record file path.
- *
- * If @rbuf, @rbuf_len, and @rfile_path are set, the monitored results are
- * automatically stored in @rfile_path file.
- *
* @kdamond: Kernel thread who does the monitoring.
* @kdamond_stop: Notifies whether kdamond should stop.
* @kdamond_lock: Mutex for the synchronizations with @kdamond.
@@ -164,6 +156,8 @@ struct damos {
* @targets_list: Head of monitoring targets (&damon_target) list.
* @schemes_list: Head of schemes (&damos) list.
*
+ * @private Private user data.
+ *
* @init_target_regions: Constructs initial monitoring target regions.
* @update_target_regions: Updates monitoring target regions.
* @prepare_access_checks: Prepares next access check of target regions.
@@ -214,11 +208,6 @@ struct damon_ctx {
struct timespec64 last_aggregation;
struct timespec64 last_regions_update;

- unsigned char *rbuf;
- unsigned int rbuf_len;
- unsigned int rbuf_offset;
- char *rfile_path;
-
struct task_struct *kdamond;
bool kdamond_stop;
struct mutex kdamond_lock;
@@ -226,6 +215,8 @@ struct damon_ctx {
struct list_head targets_list; /* 'damon_target' objects */
struct list_head schemes_list; /* 'damos' objects */

+ void *private;
+
/* callbacks */
void (*init_target_regions)(struct damon_ctx *context);
void (*update_target_regions)(struct damon_ctx *context);
@@ -241,10 +232,6 @@ struct damon_ctx {

#ifdef CONFIG_DAMON

-#define MIN_RECORD_BUFFER_LEN 1024
-#define MAX_RECORD_BUFFER_LEN (4 * 1024 * 1024)
-#define MAX_RFILE_PATH_LEN 256
-
#define damon_next_region(r) \
(container_of(r->list.next, struct damon_region, list))

@@ -298,8 +285,6 @@ int damon_set_attrs(struct damon_ctx *ctx, unsigned long sample_int,
unsigned long min_nr_reg, unsigned long max_nr_reg);
int damon_set_schemes(struct damon_ctx *ctx,
struct damos **schemes, ssize_t nr_schemes);
-int damon_set_recording(struct damon_ctx *ctx,
- unsigned int rbuf_len, char *rfile_path);
int damon_nr_running_ctxs(void);

int damon_start(struct damon_ctx *ctxs, int nr_ctxs);
diff --git a/mm/damon/core-test.h b/mm/damon/core-test.h
index c916d773397a..b815dfbfb5fd 100644
--- a/mm/damon/core-test.h
+++ b/mm/damon/core-test.h
@@ -36,6 +36,17 @@ static void damon_test_regions(struct kunit *test)
damon_free_target(t);
}

+static unsigned int nr_damon_targets(struct damon_ctx *ctx)
+{
+ struct damon_target *t;
+ unsigned int nr_targets = 0;
+
+ damon_for_each_target(t, ctx)
+ nr_targets++;
+
+ return nr_targets;
+}
+
static void damon_test_target(struct kunit *test)
{
struct damon_ctx *c = damon_new_ctx();
@@ -54,23 +65,6 @@ static void damon_test_target(struct kunit *test)
damon_destroy_ctx(c);
}

-static void damon_test_set_recording(struct kunit *test)
-{
- struct damon_ctx *ctx = damon_new_ctx();
- int err;
-
- err = damon_set_recording(ctx, 42, "foo");
- KUNIT_EXPECT_EQ(test, err, -EINVAL);
- damon_set_recording(ctx, 4242, "foo.bar");
- KUNIT_EXPECT_EQ(test, ctx->rbuf_len, 4242u);
- KUNIT_EXPECT_STREQ(test, ctx->rfile_path, "foo.bar");
- damon_set_recording(ctx, 424242, "foo");
- KUNIT_EXPECT_EQ(test, ctx->rbuf_len, 424242u);
- KUNIT_EXPECT_STREQ(test, ctx->rfile_path, "foo");
-
- damon_destroy_ctx(ctx);
-}
-
/*
* Test kdamond_reset_aggregated()
*
@@ -91,9 +85,7 @@ static void damon_test_aggregate(struct kunit *test)
struct damon_target *t;
struct damon_region *r;
int it, ir;
- ssize_t sz, sr, sp;

- damon_set_recording(ctx, 4242, "damon.data");
damon_set_targets(ctx, target_ids, 3);

it = 0;
@@ -121,31 +113,6 @@ static void damon_test_aggregate(struct kunit *test)
/* targets also should be preserved */
KUNIT_EXPECT_EQ(test, 3, it);

- /* The aggregated information should be written in the buffer */
- sr = sizeof(r->ar.start) + sizeof(r->ar.end) + sizeof(r->nr_accesses);
- sp = sizeof(t->id) + sizeof(unsigned int) + 3 * sr;
- sz = sizeof(struct timespec64) + sizeof(unsigned int) + 3 * sp;
- KUNIT_EXPECT_EQ(test, (unsigned int)sz, ctx->rbuf_offset);
-
- damon_destroy_ctx(ctx);
-}
-
-static void damon_test_write_rbuf(struct kunit *test)
-{
- struct damon_ctx *ctx = damon_new_ctx();
- char *data;
-
- damon_set_recording(ctx, 4242, "damon.data");
-
- data = "hello";
- damon_write_rbuf(ctx, data, strnlen(data, 256));
- KUNIT_EXPECT_EQ(test, ctx->rbuf_offset, 5u);
-
- damon_write_rbuf(ctx, data, 0);
- KUNIT_EXPECT_EQ(test, ctx->rbuf_offset, 5u);
-
- KUNIT_EXPECT_STREQ(test, (char *)ctx->rbuf, data);
-
damon_destroy_ctx(ctx);
}

@@ -267,9 +234,7 @@ static void damon_test_split_regions_of(struct kunit *test)
static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_test_target),
KUNIT_CASE(damon_test_regions),
- KUNIT_CASE(damon_test_set_recording),
KUNIT_CASE(damon_test_aggregate),
- KUNIT_CASE(damon_test_write_rbuf),
KUNIT_CASE(damon_test_split_at),
KUNIT_CASE(damon_test_merge_two),
KUNIT_CASE(damon_test_merge_regions_of),
diff --git a/mm/damon/core.c b/mm/damon/core.c
index ba52421a2673..ba0035d7a27a 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -208,11 +208,6 @@ struct damon_ctx *damon_new_ctx(void)
ktime_get_coarse_ts64(&ctx->last_aggregation);
ctx->last_regions_update = ctx->last_aggregation;

- if (damon_set_recording(ctx, 0, "none")) {
- kfree(ctx);
- return NULL;
- }
-
mutex_init(&ctx->kdamond_lock);

INIT_LIST_HEAD(&ctx->targets_list);
@@ -328,54 +323,6 @@ int damon_set_schemes(struct damon_ctx *ctx, struct damos **schemes,
return 0;
}

-/**
- * damon_set_recording() - Set attributes for the recording.
- * @ctx: target kdamond context
- * @rbuf_len: length of the result buffer
- * @rfile_path: path to the monitor result files
- *
- * Setting 'rbuf_len' 0 disables recording.
- *
- * This function should not be called while the kdamond is running.
- *
- * Return: 0 on success, negative error code otherwise.
- */
-int damon_set_recording(struct damon_ctx *ctx,
- unsigned int rbuf_len, char *rfile_path)
-{
- size_t rfile_path_len;
-
- if (rbuf_len && (rbuf_len > MAX_RECORD_BUFFER_LEN ||
- rbuf_len < MIN_RECORD_BUFFER_LEN)) {
- pr_err("result buffer size (%u) is out of [%d,%d]\n",
- rbuf_len, MIN_RECORD_BUFFER_LEN,
- MAX_RECORD_BUFFER_LEN);
- return -EINVAL;
- }
- rfile_path_len = strnlen(rfile_path, MAX_RFILE_PATH_LEN);
- if (rfile_path_len >= MAX_RFILE_PATH_LEN) {
- pr_err("too long (>%d) result file path %s\n",
- MAX_RFILE_PATH_LEN, rfile_path);
- return -EINVAL;
- }
- ctx->rbuf_len = rbuf_len;
- kfree(ctx->rbuf);
- ctx->rbuf = NULL;
- kfree(ctx->rfile_path);
- ctx->rfile_path = NULL;
-
- if (rbuf_len) {
- ctx->rbuf = kvmalloc(rbuf_len, GFP_KERNEL);
- if (!ctx->rbuf)
- return -ENOMEM;
- }
- ctx->rfile_path = kmalloc(rfile_path_len + 1, GFP_KERNEL);
- if (!ctx->rfile_path)
- return -ENOMEM;
- strncpy(ctx->rfile_path, rfile_path, rfile_path_len + 1);
- return 0;
-}
-
/**
* damon_nr_running_ctxs() - Return number of currently running contexts.
*/
@@ -390,17 +337,6 @@ int damon_nr_running_ctxs(void)
return nr_ctxs;
}

-static unsigned int nr_damon_targets(struct damon_ctx *ctx)
-{
- struct damon_target *t;
- unsigned int nr_targets = 0;
-
- damon_for_each_target(t, ctx)
- nr_targets++;
-
- return nr_targets;
-}
-
/* Returns the size upper limit for each monitoring region */
static unsigned long damon_region_sz_limit(struct damon_ctx *ctx)
{
@@ -613,87 +549,18 @@ static bool kdamond_aggregate_interval_passed(struct damon_ctx *ctx)
}

/*
- * Flush the content in the result buffer to the result file
- */
-static void damon_flush_rbuffer(struct damon_ctx *ctx)
-{
- ssize_t sz;
- loff_t pos = 0;
- struct file *rfile;
-
- if (!ctx->rbuf_offset)
- return;
-
- rfile = filp_open(ctx->rfile_path,
- O_CREAT | O_RDWR | O_APPEND | O_LARGEFILE, 0644);
- if (IS_ERR(rfile)) {
- pr_err("Cannot open the result file %s\n",
- ctx->rfile_path);
- return;
- }
-
- while (ctx->rbuf_offset) {
- sz = kernel_write(rfile, ctx->rbuf, ctx->rbuf_offset, &pos);
- if (sz < 0)
- break;
- ctx->rbuf_offset -= sz;
- }
- filp_close(rfile, NULL);
-}
-
-/*
- * Write a data into the result buffer
- */
-static void damon_write_rbuf(struct damon_ctx *ctx, void *data, ssize_t size)
-{
- if (!ctx->rbuf_len || !ctx->rbuf || !ctx->rfile_path)
- return;
- if (ctx->rbuf_offset + size > ctx->rbuf_len)
- damon_flush_rbuffer(ctx);
- if (ctx->rbuf_offset + size > ctx->rbuf_len) {
- pr_warn("%s: flush failed, or wrong size given(%u, %zu)\n",
- __func__, ctx->rbuf_offset, size);
- return;
- }
-
- memcpy(&ctx->rbuf[ctx->rbuf_offset], data, size);
- ctx->rbuf_offset += size;
-}
-
-/*
- * Flush the aggregated monitoring results to the result buffer
- *
- * Stores current tracking results to the result buffer and reset 'nr_accesses'
- * of each region. The format for the result buffer is as below:
- *
- * <time> <number of targets> <array of target infos>
- *
- * target info: <id> <number of regions> <array of region infos>
- * region info: <start address> <end address> <nr_accesses>
+ * Reset the aggregated monitoring results ('nr_accesses' of each region).
*/
static void kdamond_reset_aggregated(struct damon_ctx *c)
{
struct damon_target *t;
- struct timespec64 now;
unsigned int nr;

- ktime_get_coarse_ts64(&now);
-
- damon_write_rbuf(c, &now, sizeof(now));
- nr = nr_damon_targets(c);
- damon_write_rbuf(c, &nr, sizeof(nr));
-
damon_for_each_target(t, c) {
struct damon_region *r;

- damon_write_rbuf(c, &t->id, sizeof(t->id));
nr = damon_nr_regions(t);
- damon_write_rbuf(c, &nr, sizeof(nr));
damon_for_each_region(r, t) {
- damon_write_rbuf(c, &r->ar.start, sizeof(r->ar.start));
- damon_write_rbuf(c, &r->ar.end, sizeof(r->ar.end));
- damon_write_rbuf(c, &r->nr_accesses,
- sizeof(r->nr_accesses));
trace_damon_aggregated(t, r, nr);
r->last_nr_accesses = r->nr_accesses;
r->nr_accesses = 0;
@@ -927,14 +794,6 @@ static bool kdamond_need_stop(struct damon_ctx *ctx)
return true;
}

-static void kdamond_write_record_header(struct damon_ctx *ctx)
-{
- int recfmt_ver = 2;
-
- damon_write_rbuf(ctx, "damon_recfmt_ver", 16);
- damon_write_rbuf(ctx, &recfmt_ver, sizeof(recfmt_ver));
-}
-
/*
* The monitoring daemon that runs as a kernel thread
*/
@@ -951,8 +810,6 @@ static int kdamond_fn(void *data)
ctx->init_target_regions(ctx);
sz_limit = damon_region_sz_limit(ctx);

- kdamond_write_record_header(ctx);
-
while (!kdamond_need_stop(ctx)) {
if (ctx->prepare_access_checks)
ctx->prepare_access_checks(ctx);
@@ -965,10 +822,10 @@ static int kdamond_fn(void *data)
max_nr_accesses = ctx->check_accesses(ctx);

if (kdamond_aggregate_interval_passed(ctx)) {
- if (ctx->aggregate_cb)
- ctx->aggregate_cb(ctx);
kdamond_merge_regions(ctx, max_nr_accesses / 10,
sz_limit);
+ if (ctx->aggregate_cb)
+ ctx->aggregate_cb(ctx);
kdamond_apply_schemes(ctx);
kdamond_reset_aggregated(ctx);
kdamond_split_regions(ctx);
@@ -980,7 +837,6 @@ static int kdamond_fn(void *data)
sz_limit = damon_region_sz_limit(ctx);
}
}
- damon_flush_rbuffer(ctx);
damon_for_each_target(t, ctx) {
damon_for_each_region_safe(r, next, t)
damon_destroy_region(r);
diff --git a/mm/damon/dbgfs-test.h b/mm/damon/dbgfs-test.h
index dffb9f70e399..426adf5dadc2 100644
--- a/mm/damon/dbgfs-test.h
+++ b/mm/damon/dbgfs-test.h
@@ -78,7 +78,7 @@ static void damon_dbgfs_test_str_to_target_ids(struct kunit *test)

static void damon_dbgfs_test_set_targets(struct kunit *test)
{
- struct damon_ctx *ctx = damon_new_ctx();
+ struct damon_ctx *ctx = debugfs_new_ctx();
unsigned long ids[] = {1, 2, 3};
char buf[64];

@@ -105,9 +105,91 @@ static void damon_dbgfs_test_set_targets(struct kunit *test)
sprint_target_ids(ctx, buf, 64);
KUNIT_EXPECT_STREQ(test, (char *)buf, "\n");

+ debugfs_destroy_ctx(ctx);
+}
+
+static void damon_dbgfs_test_set_recording(struct kunit *test)
+{
+ struct damon_ctx *ctx = debugfs_new_ctx();
+ struct debugfs_recorder *rec = ctx->private;
+ int err;
+
+ err = debugfs_set_recording(ctx, 42, "foo");
+ KUNIT_EXPECT_EQ(test, err, -EINVAL);
+ debugfs_set_recording(ctx, 4242, "foo.bar");
+ KUNIT_EXPECT_EQ(test, rec->rbuf_len, 4242u);
+ KUNIT_EXPECT_STREQ(test, rec->rfile_path, "foo.bar");
+ debugfs_set_recording(ctx, 424242, "foo");
+ KUNIT_EXPECT_EQ(test, rec->rbuf_len, 424242u);
+ KUNIT_EXPECT_STREQ(test, rec->rfile_path, "foo");
+
+ debugfs_destroy_ctx(ctx);
+}
+
+static void damon_dbgfs_test_write_rbuf(struct kunit *test)
+{
+ struct damon_ctx *ctx = debugfs_new_ctx();
+ struct debugfs_recorder *rec = ctx->private;
+ char *data;
+
+ debugfs_set_recording(ctx, 4242, "damon.data");
+
+ data = "hello";
+ debugfs_write_rbuf(ctx, data, strnlen(data, 256));
+ KUNIT_EXPECT_EQ(test, rec->rbuf_offset, 5u);
+
+ debugfs_write_rbuf(ctx, data, 0);
+ KUNIT_EXPECT_EQ(test, rec->rbuf_offset, 5u);
+
+ KUNIT_EXPECT_STREQ(test, (char *)rec->rbuf, data);
+
+ debugfs_destroy_ctx(ctx);
+}
+
+/*
+ * Test debugfs_aggregate_cb()
+ *
+ * dbgfs sets debugfs_aggregate_cb() as aggregate callback. It stores the
+ * aggregated monitoring information ('->nr_accesses' of each regions) to the
+ * result buffer.
+ */
+static void damon_dbgfs_test_aggregate(struct kunit *test)
+{
+ struct damon_ctx *ctx = debugfs_new_ctx();
+ struct debugfs_recorder *rec = ctx->private;
+ unsigned long target_ids[] = {1, 2, 3};
+ unsigned long saddr[][3] = {{10, 20, 30}, {5, 42, 49}, {13, 33, 55} };
+ unsigned long eaddr[][3] = {{15, 27, 40}, {31, 45, 55}, {23, 44, 66} };
+ unsigned long accesses[][3] = {{42, 95, 84}, {10, 20, 30}, {0, 1, 2} };
+ struct damon_target *t;
+ struct damon_region *r;
+ int it, ir;
+ ssize_t sz, sr, sp;
+
+ debugfs_set_recording(ctx, 4242, "damon.data");
+ damon_set_targets(ctx, target_ids, 3);
+
+ it = 0;
+ damon_for_each_target(t, ctx) {
+ for (ir = 0; ir < 3; ir++) {
+ r = damon_new_region(saddr[it][ir], eaddr[it][ir]);
+ r->nr_accesses = accesses[it][ir];
+ damon_add_region(r, t);
+ }
+ it++;
+ }
+ debugfs_aggregate_cb(ctx);
+
+ /* The aggregated information should be written in the buffer */
+ sr = sizeof(r->ar.start) + sizeof(r->ar.end) + sizeof(r->nr_accesses);
+ sp = sizeof(t->id) + sizeof(unsigned int) + 3 * sr;
+ sz = sizeof(struct timespec64) + sizeof(unsigned int) + 3 * sp;
+ KUNIT_EXPECT_EQ(test, (unsigned int)sz, rec->rbuf_offset);
+
damon_destroy_ctx(ctx);
}

+
static void damon_dbgfs_test_set_init_regions(struct kunit *test)
{
struct damon_ctx *ctx = damon_new_ctx();
@@ -164,6 +246,9 @@ static void damon_dbgfs_test_set_init_regions(struct kunit *test)
static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_dbgfs_test_str_to_target_ids),
KUNIT_CASE(damon_dbgfs_test_set_targets),
+ KUNIT_CASE(damon_dbgfs_test_set_recording),
+ KUNIT_CASE(damon_dbgfs_test_write_rbuf),
+ KUNIT_CASE(damon_dbgfs_test_aggregate),
KUNIT_CASE(damon_dbgfs_test_set_init_regions),
{},
};
diff --git a/mm/damon/dbgfs.c b/mm/damon/dbgfs.c
index 646a492100ff..7a6c279690f8 100644
--- a/mm/damon/dbgfs.c
+++ b/mm/damon/dbgfs.c
@@ -10,15 +10,155 @@
#include <linux/damon.h>
#include <linux/debugfs.h>
#include <linux/file.h>
+#include <linux/mm.h>
#include <linux/module.h>
#include <linux/slab.h>

+#define MIN_RECORD_BUFFER_LEN 1024
+#define MAX_RECORD_BUFFER_LEN (4 * 1024 * 1024)
+#define MAX_RFILE_PATH_LEN 256
+
+struct debugfs_recorder {
+ unsigned char *rbuf;
+ unsigned int rbuf_len;
+ unsigned int rbuf_offset;
+ char *rfile_path;
+};
+
/* Monitoring contexts for debugfs interface users. */
static struct damon_ctx **debugfs_ctxs;
static int debugfs_nr_ctxs = 1;

static DEFINE_MUTEX(damon_dbgfs_lock);

+static unsigned int nr_damon_targets(struct damon_ctx *ctx)
+{
+ struct damon_target *t;
+ unsigned int nr_targets = 0;
+
+ damon_for_each_target(t, ctx)
+ nr_targets++;
+
+ return nr_targets;
+}
+
+/*
+ * Flush the content in the result buffer to the result file
+ */
+static void debugfs_flush_rbuffer(struct debugfs_recorder *rec)
+{
+ ssize_t sz;
+ loff_t pos = 0;
+ struct file *rfile;
+
+ if (!rec->rbuf_offset)
+ return;
+
+ rfile = filp_open(rec->rfile_path,
+ O_CREAT | O_RDWR | O_APPEND | O_LARGEFILE, 0644);
+ if (IS_ERR(rfile)) {
+ pr_err("Cannot open the result file %s\n",
+ rec->rfile_path);
+ return;
+ }
+
+ while (rec->rbuf_offset) {
+ sz = kernel_write(rfile, rec->rbuf, rec->rbuf_offset, &pos);
+ if (sz < 0)
+ break;
+ rec->rbuf_offset -= sz;
+ }
+ filp_close(rfile, NULL);
+}
+
+/*
+ * Write a data into the result buffer
+ */
+static void debugfs_write_rbuf(struct damon_ctx *ctx, void *data, ssize_t size)
+{
+ struct debugfs_recorder *rec = (struct debugfs_recorder *)ctx->private;
+
+ if (!rec->rbuf_len || !rec->rbuf || !rec->rfile_path)
+ return;
+ if (rec->rbuf_offset + size > rec->rbuf_len)
+ debugfs_flush_rbuffer(ctx->private);
+ if (rec->rbuf_offset + size > rec->rbuf_len) {
+ pr_warn("%s: flush failed, or wrong size given(%u, %zu)\n",
+ __func__, rec->rbuf_offset, size);
+ return;
+ }
+
+ memcpy(&rec->rbuf[rec->rbuf_offset], data, size);
+ rec->rbuf_offset += size;
+}
+
+static void debugfs_write_record_header(struct damon_ctx *ctx)
+{
+ int recfmt_ver = 2;
+
+ debugfs_write_rbuf(ctx, "damon_recfmt_ver", 16);
+ debugfs_write_rbuf(ctx, &recfmt_ver, sizeof(recfmt_ver));
+}
+
+static void debugfs_init_vm_regions(struct damon_ctx *ctx)
+{
+ debugfs_write_record_header(ctx);
+ kdamond_init_vm_regions(ctx);
+}
+
+static void debugfs_vm_cleanup(struct damon_ctx *ctx)
+{
+ debugfs_flush_rbuffer(ctx->private);
+ kdamond_vm_cleanup(ctx);
+}
+
+static void debugfs_init_phys_regions(struct damon_ctx *ctx)
+{
+ debugfs_write_record_header(ctx);
+}
+
+static void debugfs_phys_cleanup(struct damon_ctx *ctx)
+{
+ debugfs_flush_rbuffer(ctx->private);
+}
+
+/*
+ * Store the aggregated monitoring results to the result buffer
+ *
+ * The format for the result buffer is as below:
+ *
+ * <time> <number of targets> <array of target infos>
+ *
+ * target info: <id> <number of regions> <array of region infos>
+ * region info: <start address> <end address> <nr_accesses>
+ */
+static void debugfs_aggregate_cb(struct damon_ctx *c)
+{
+ struct damon_target *t;
+ struct timespec64 now;
+ unsigned int nr;
+
+ ktime_get_coarse_ts64(&now);
+
+ debugfs_write_rbuf(c, &now, sizeof(now));
+ nr = nr_damon_targets(c);
+ debugfs_write_rbuf(c, &nr, sizeof(nr));
+
+ damon_for_each_target(t, c) {
+ struct damon_region *r;
+
+ debugfs_write_rbuf(c, &t->id, sizeof(t->id));
+ nr = damon_nr_regions(t);
+ debugfs_write_rbuf(c, &nr, sizeof(nr));
+ damon_for_each_region(r, t) {
+ debugfs_write_rbuf(c, &r->ar.start, sizeof(r->ar.start));
+ debugfs_write_rbuf(c, &r->ar.end, sizeof(r->ar.end));
+ debugfs_write_rbuf(c, &r->nr_accesses,
+ sizeof(r->nr_accesses));
+ }
+ }
+}
+
static ssize_t debugfs_monitor_on_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
@@ -330,6 +470,20 @@ static struct pid *damon_get_pidfd_pid(unsigned int pidfd)
return pid;
}

+static void debugfs_set_vaddr_primitives(struct damon_ctx *ctx)
+{
+ damon_set_vaddr_primitives(ctx);
+ ctx->init_target_regions = debugfs_init_vm_regions;
+ ctx->cleanup = debugfs_vm_cleanup;
+}
+
+static void debugfs_set_paddr_primitives(struct damon_ctx *ctx)
+{
+ damon_set_paddr_primitives(ctx);
+ ctx->init_target_regions = debugfs_init_phys_regions;
+ ctx->cleanup = debugfs_phys_cleanup;
+}
+
static ssize_t debugfs_target_ids_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
@@ -349,12 +503,13 @@ static ssize_t debugfs_target_ids_write(struct file *file,
nrs = kbuf;
if (!strncmp(kbuf, "paddr\n", count)) {
/* Configure the context for physical memory monitoring */
- damon_set_paddr_primitives(ctx);
+ debugfs_set_paddr_primitives(ctx);
/* target id is meaningless here, but we set it just for fun */
scnprintf(kbuf, count, "42 ");
} else {
/* Configure the context for virtual memory monitoring */
- damon_set_vaddr_primitives(ctx);
+ debugfs_set_vaddr_primitives(ctx);
+
if (!strncmp(kbuf, "pidfd ", 6)) {
received_pidfds = true;
nrs = &kbuf[6];
@@ -398,16 +553,76 @@ static ssize_t debugfs_record_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
struct damon_ctx *ctx = file->private_data;
+ struct debugfs_recorder *rec = ctx->private;
char record_buf[20 + MAX_RFILE_PATH_LEN];
int ret;

mutex_lock(&ctx->kdamond_lock);
ret = scnprintf(record_buf, ARRAY_SIZE(record_buf), "%u %s\n",
- ctx->rbuf_len, ctx->rfile_path);
+ rec->rbuf_len, rec->rfile_path);
mutex_unlock(&ctx->kdamond_lock);
return simple_read_from_buffer(buf, count, ppos, record_buf, ret);
}

+/*
+ * debugfs_set_recording() - Set attributes for the recording.
+ * @ctx: target kdamond context
+ * @rbuf_len: length of the result buffer
+ * @rfile_path: path to the monitor result files
+ *
+ * Setting 'rbuf_len' 0 disables recording.
+ *
+ * This function should not be called while the kdamond is running.
+ *
+ * Return: 0 on success, negative error code otherwise.
+ */
+static int debugfs_set_recording(struct damon_ctx *ctx,
+ unsigned int rbuf_len, char *rfile_path)
+{
+ struct debugfs_recorder *recorder;
+ size_t rfile_path_len;
+
+ if (rbuf_len && (rbuf_len > MAX_RECORD_BUFFER_LEN ||
+ rbuf_len < MIN_RECORD_BUFFER_LEN)) {
+ pr_err("result buffer size (%u) is out of [%d,%d]\n",
+ rbuf_len, MIN_RECORD_BUFFER_LEN,
+ MAX_RECORD_BUFFER_LEN);
+ return -EINVAL;
+ }
+ rfile_path_len = strnlen(rfile_path, MAX_RFILE_PATH_LEN);
+ if (rfile_path_len >= MAX_RFILE_PATH_LEN) {
+ pr_err("too long (>%d) result file path %s\n",
+ MAX_RFILE_PATH_LEN, rfile_path);
+ return -EINVAL;
+ }
+
+ recorder = ctx->private;
+ if (!recorder) {
+ recorder = kzalloc(sizeof(*recorder), GFP_KERNEL);
+ if (!recorder)
+ return -ENOMEM;
+ ctx->private = recorder;
+ }
+
+ recorder->rbuf_len = rbuf_len;
+ kfree(recorder->rbuf);
+ recorder->rbuf = NULL;
+ kfree(recorder->rfile_path);
+ recorder->rfile_path = NULL;
+
+ if (rbuf_len) {
+ recorder->rbuf = kvmalloc(rbuf_len, GFP_KERNEL);
+ if (!recorder->rbuf)
+ return -ENOMEM;
+ }
+ recorder->rfile_path = kmalloc(rfile_path_len + 1, GFP_KERNEL);
+ if (!recorder->rfile_path)
+ return -ENOMEM;
+ strncpy(recorder->rfile_path, rfile_path, rfile_path_len + 1);
+
+ return 0;
+}
+
static ssize_t debugfs_record_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
@@ -434,7 +649,7 @@ static ssize_t debugfs_record_write(struct file *file,
goto unlock_out;
}

- err = damon_set_recording(ctx, rbuf_len, rfile_path);
+ err = debugfs_set_recording(ctx, rbuf_len, rfile_path);
if (err)
ret = err;
unlock_out:
@@ -654,6 +869,38 @@ static struct dentry **debugfs_dirs;

static int debugfs_fill_ctx_dir(struct dentry *dir, struct damon_ctx *ctx);

+static void debugfs_free_recorder(struct debugfs_recorder *recorder)
+{
+ kfree(recorder->rbuf);
+ kfree(recorder->rfile_path);
+ kfree(recorder);
+}
+
+static struct damon_ctx *debugfs_new_ctx(void)
+{
+ struct damon_ctx *ctx;
+
+ ctx = damon_new_ctx();
+ if (!ctx)
+ return NULL;
+
+ if (debugfs_set_recording(ctx, 0, "none")) {
+ damon_destroy_ctx(ctx);
+ return NULL;
+ }
+
+ debugfs_set_vaddr_primitives(ctx);
+ ctx->aggregate_cb = debugfs_aggregate_cb;
+ return ctx;
+}
+
+static void debugfs_destroy_ctx(struct damon_ctx *ctx)
+{
+ debugfs_free_recorder(ctx->private);
+ damon_destroy_ctx(ctx);
+}
+
+
static ssize_t debugfs_nr_contexts_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
@@ -689,7 +936,7 @@ static ssize_t debugfs_nr_contexts_write(struct file *file,

for (i = nr_contexts; i < debugfs_nr_ctxs; i++) {
debugfs_remove(debugfs_dirs[i]);
- damon_destroy_ctx(debugfs_ctxs[i]);
+ debugfs_destroy_ctx(debugfs_ctxs[i]);
}

new_dirs = kmalloc_array(nr_contexts, sizeof(*new_dirs), GFP_KERNEL);
@@ -729,13 +976,13 @@ static ssize_t debugfs_nr_contexts_write(struct file *file,
break;
}

- debugfs_ctxs[i] = damon_new_ctx();
+ debugfs_ctxs[i] = debugfs_new_ctx();
if (!debugfs_ctxs[i]) {
pr_err("ctx for %s creation failed\n", dirname);
+ debugfs_remove(debugfs_dirs[i]);
ret = -ENOMEM;
break;
}
- damon_set_vaddr_primitives(debugfs_ctxs[i]);

if (debugfs_fill_ctx_dir(debugfs_dirs[i], debugfs_ctxs[i])) {
ret = -ENOMEM;
@@ -865,10 +1112,9 @@ static int __init damon_dbgfs_init(void)
int rc;

debugfs_ctxs = kmalloc(sizeof(*debugfs_ctxs), GFP_KERNEL);
- debugfs_ctxs[0] = damon_new_ctx();
+ debugfs_ctxs[0] = debugfs_new_ctx();
if (!debugfs_ctxs[0])
return -ENOMEM;
- damon_set_vaddr_primitives(debugfs_ctxs[0]);

rc = damon_debugfs_init();
if (rc)
--
2.17.1