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)
