[PATCH v21 11/18] mm/damon/dbgfs: Implement recording feature

From: SeongJae Park
Date: Mon Oct 05 2020 - 06:59:38 EST


From: SeongJae Park <sjpark@xxxxxxxxx>

The user space users can control DAMON via and get the monitoring
results using the 'damon_aggregated' tracepoint event. However, dealing
with the tracepoint might be complex for some simple use cases. This
commit therefore implements 'recording' feature in 'damon-dbgfs'. The
feature can be used via 'record' file in the '<debugfs>/damon/'
directory.

The file allows users to record monitored access patterns in a regular
binary file. The recorded results are first written in an in-memory
buffer and flushed to a file in batch. Users can get and set the size
of the buffer and the path to the result file by reading from and
writing to the ``record`` file. For example, below commands set the
buffer to be 4 KiB and the result to be saved in ``/damon.data``. ::

# cd <debugfs>/damon
# echo "4096 /damon.data" > record
# cat record
4096 /damon.data

The recording can be disabled by setting the buffer size zero.

Signed-off-by: SeongJae Park <sjpark@xxxxxxxxx>
---
mm/damon/dbgfs.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 253 insertions(+), 2 deletions(-)

diff --git a/mm/damon/dbgfs.c b/mm/damon/dbgfs.c
index 27cf8b96ab56..c4401762fc16 100644
--- a/mm/damon/dbgfs.c
+++ b/mm/damon/dbgfs.c
@@ -15,6 +15,17 @@
#include <linux/page_idle.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 dbgfs_recorder {
+ unsigned char *rbuf;
+ unsigned int rbuf_len;
+ unsigned int rbuf_offset;
+ char *rfile_path;
+};
+
static struct damon_ctx **dbgfs_ctxs;
static int dbgfs_nr_ctxs = 1;
static int dbgfs_nr_terminated_ctxs;
@@ -99,6 +110,116 @@ static ssize_t dbgfs_attrs_write(struct file *file,
return ret;
}

+static ssize_t dbgfs_record_read(struct file *file,
+ char __user *buf, size_t count, loff_t *ppos)
+{
+ struct damon_ctx *ctx = file->private_data;
+ struct dbgfs_recorder *rec = ctx->callback.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",
+ rec->rbuf_len, rec->rfile_path);
+ mutex_unlock(&ctx->kdamond_lock);
+ return simple_read_from_buffer(buf, count, ppos, record_buf, ret);
+}
+
+/*
+ * dbgfs_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 dbgfs_set_recording(struct damon_ctx *ctx,
+ unsigned int rbuf_len, char *rfile_path)
+{
+ struct dbgfs_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->callback.private;
+ if (!recorder) {
+ recorder = kzalloc(sizeof(*recorder), GFP_KERNEL);
+ if (!recorder)
+ return -ENOMEM;
+ ctx->callback.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 dbgfs_record_write(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct damon_ctx *ctx = file->private_data;
+ char *kbuf;
+ unsigned int rbuf_len;
+ char rfile_path[MAX_RFILE_PATH_LEN];
+ ssize_t ret = count;
+ int err;
+
+ kbuf = user_input_str(buf, count, ppos);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ if (sscanf(kbuf, "%u %s",
+ &rbuf_len, rfile_path) != 2) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&ctx->kdamond_lock);
+ if (ctx->kdamond) {
+ ret = -EBUSY;
+ goto unlock_out;
+ }
+
+ err = dbgfs_set_recording(ctx, rbuf_len, rfile_path);
+ if (err)
+ ret = err;
+unlock_out:
+ mutex_unlock(&ctx->kdamond_lock);
+out:
+ kfree(kbuf);
+ return ret;
+}
+
#define targetid_is_pid(ctx) \
(ctx->primitive.target_valid == damon_va_target_valid)

@@ -262,6 +383,13 @@ static const struct file_operations attrs_fops = {
.write = dbgfs_attrs_write,
};

+static const struct file_operations record_fops = {
+ .owner = THIS_MODULE,
+ .open = damon_dbgfs_open,
+ .read = dbgfs_record_read,
+ .write = dbgfs_record_write,
+};
+
static const struct file_operations target_ids_fops = {
.owner = THIS_MODULE,
.open = damon_dbgfs_open,
@@ -271,8 +399,9 @@ static const struct file_operations target_ids_fops = {

static int dbgfs_fill_ctx_dir(struct dentry *dir, struct damon_ctx *ctx)
{
- const char * const file_names[] = {"attrs", "target_ids"};
- const struct file_operations *fops[] = {&attrs_fops, &target_ids_fops};
+ const char * const file_names[] = {"attrs", "record", "target_ids"};
+ const struct file_operations *fops[] = {&attrs_fops, &record_fops,
+ &target_ids_fops};
int i;

for (i = 0; i < ARRAY_SIZE(file_names); i++) {
@@ -286,6 +415,120 @@ static int dbgfs_fill_ctx_dir(struct dentry *dir, struct damon_ctx *ctx)
return 0;
}

+/*
+ * Flush the content in the result buffer to the result file
+ */
+static void dbgfs_flush_rbuffer(struct dbgfs_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 dbgfs_write_rbuf(struct damon_ctx *ctx, void *data, ssize_t size)
+{
+ struct dbgfs_recorder *rec = ctx->callback.private;
+
+ if (!rec->rbuf_len || !rec->rbuf || !rec->rfile_path)
+ return;
+ if (rec->rbuf_offset + size > rec->rbuf_len)
+ dbgfs_flush_rbuffer(ctx->callback.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 dbgfs_write_record_header(struct damon_ctx *ctx)
+{
+ int recfmt_ver = 2;
+
+ dbgfs_write_rbuf(ctx, "damon_recfmt_ver", 16);
+ dbgfs_write_rbuf(ctx, &recfmt_ver, sizeof(recfmt_ver));
+}
+
+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 int dbgfs_before_start(struct damon_ctx *ctx)
+{
+ dbgfs_write_record_header(ctx);
+ return 0;
+}
+
+/*
+ * 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 int dbgfs_after_aggregation(struct damon_ctx *c)
+{
+ struct damon_target *t;
+ struct timespec64 now;
+ unsigned int nr;
+
+ ktime_get_coarse_ts64(&now);
+
+ dbgfs_write_rbuf(c, &now, sizeof(now));
+ nr = nr_damon_targets(c);
+ dbgfs_write_rbuf(c, &nr, sizeof(nr));
+
+ damon_for_each_target(t, c) {
+ struct damon_region *r;
+
+ dbgfs_write_rbuf(c, &t->id, sizeof(t->id));
+ nr = damon_nr_regions(t);
+ dbgfs_write_rbuf(c, &nr, sizeof(nr));
+ damon_for_each_region(r, t) {
+ dbgfs_write_rbuf(c, &r->ar.start, sizeof(r->ar.start));
+ dbgfs_write_rbuf(c, &r->ar.end, sizeof(r->ar.end));
+ dbgfs_write_rbuf(c, &r->nr_accesses,
+ sizeof(r->nr_accesses));
+ }
+ }
+
+ return 0;
+}
+
static void dbgfs_unlock_page_idle_lock(void)
{
mutex_lock(&damon_dbgfs_lock);
@@ -298,6 +541,7 @@ static void dbgfs_unlock_page_idle_lock(void)

static int dbgfs_before_terminate(struct damon_ctx *ctx)
{
+ dbgfs_flush_rbuffer(ctx->callback.private);
dbgfs_unlock_page_idle_lock();
return 0;
}
@@ -310,7 +554,14 @@ static struct damon_ctx *dbgfs_new_ctx(void)
if (!ctx)
return NULL;

+ if (dbgfs_set_recording(ctx, 0, "none")) {
+ damon_destroy_ctx(ctx);
+ return NULL;
+ }
+
damon_va_set_primitives(ctx);
+ ctx->callback.before_start = dbgfs_before_start;
+ ctx->callback.after_aggregation = dbgfs_after_aggregation;
ctx->callback.before_terminate = dbgfs_before_terminate;
return ctx;
}
--
2.17.1