The user space users can control DAMON and get the monitoring results via implements 'recording' feature in 'damon-sysfs'. The feature can be used via 'record' and 'state' file in the '<sysfs>/kernel/mm/ damon/admin/kdamonds/N/' directory. The file allows users to record monitored access patterns in a text file. Firstly, users set the size of the buffer and the path of the result file by writing to the ``record`` file. Then the recorded results are first written in an in-memory buffer and flushed the recorded results to a file in batch by writing 'record' to the ``state`` file. For example, below commands set the buffer to be 4 KiB and the result to be saved in ``/damon.txt``. :: # cd <sysfs>/kernel/mm/damon/admin/kdamonds/N # echo "4096 /damon.txt" > record # echo "record" > state Signed-off-by: cuiyangpei <cuiyangpei@xxxxxxxxxx> --- .../ABI/testing/sysfs-kernel-mm-damon | 20 +- include/linux/damon.h | 11 + mm/damon/sysfs.c | 282 ++++++++++++++++++ 3 files changed, 307 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-kernel-mm-damon b/Documentation/ABI/testing/sysfs-kernel-mm-damon index b35649a46a2f..819534dcfb6c 100644 --- a/Documentation/ABI/testing/sysfs-kernel-mm-damon +++ b/Documentation/ABI/testing/sysfs-kernel-mm-damon @@ -25,15 +25,23 @@ Description: Writing 'on' or 'off' to this file makes the kdamond starts or stops, respectively. Reading the file returns the keywords based on the current status. Writing 'commit' to this file makes the kdamond reads the user inputs in the sysfs files - except 'state' again. Writing 'update_schemes_stats' to the - file updates contents of schemes stats files of the kdamond. - Writing 'update_schemes_tried_regions' to the file updates - contents of 'tried_regions' directory of every scheme directory - of this kdamond. Writing 'update_schemes_tried_bytes' to the - file updates only '.../tried_regions/total_bytes' files of this + except 'state' again. Writing 'record' to this file makes the + kdamond saves the monitoring results to file specified by the + /record file. Writing 'update_schemes_stats'to the file updates + contents of schemes stats files of the kdamond. Writing + 'update_schemes_tried_regions' to the file updates contents of + 'tried_regions' directory of every scheme directory of this + kdamond. Writing 'update_schemes_tried_bytes' to the file + updates only '.../tried_regions/total_bytes' files of this kdamond. Writing 'clear_schemes_tried_regions' to the file removes contents of the 'tried_regions' directory. +What: /sys/kernel/mm/damon/admin/kdamonds/<K>/record +Date: Nov 2023 +Contact: Ping Xiong <xiongping1@xxxxxxxxxx> +Description: Writing a string '4096 /damon.txt' to this file makes the + user to record monitored access patterns in a text file. + What: /sys/kernel/mm/damon/admin/kdamonds/<K>/pid Date: Mar 2022 Contact: SeongJae Park <sj@xxxxxxxxxx> diff --git a/include/linux/damon.h b/include/linux/damon.h index ab2f17d9926b..6495513cc6de 100644 --- a/include/linux/damon.h +++ b/include/linux/damon.h @@ -19,6 +19,17 @@ /* Max priority score for DAMON-based operation schemes */ #define DAMOS_MAX_SCORE (99) +#define MIN_RECORD_BUFFER_LEN 1024 +#define MAX_RECORD_BUFFER_LEN (4 * 1024 * 1024) +#define MAX_RFILE_PATH_LEN 256 + +struct sysfs_recorder { + unsigned char *rbuf; + unsigned int rbuf_len; + unsigned int rbuf_offset; + char *rfile_path; +}; + /* Get a random number in [l, r) */ static inline unsigned long damon_rand(unsigned long l, unsigned long r) { diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c index e27846708b5a..7a7d41e609e3 100644 --- a/mm/damon/sysfs.c +++ b/mm/damon/sysfs.c @@ -994,6 +994,8 @@ enum damon_sysfs_cmd { DAMON_SYSFS_CMD_OFF, /* @DAMON_SYSFS_CMD_COMMIT: Update kdamond inputs. */ DAMON_SYSFS_CMD_COMMIT, + /* @DAMON_SYSFS_CMD_RECORD: Save the monitoring results to file. */ + DAMON_SYSFS_CMD_RECORD, /* * @DAMON_SYSFS_CMD_UPDATE_SCHEMES_STATS: Update scheme stats sysfs * files. @@ -1025,6 +1027,7 @@ static const char * const damon_sysfs_cmd_strs[] = { "on", "off", "commit", + "record", "update_schemes_stats", "update_schemes_tried_bytes", "update_schemes_tried_regions", @@ -1349,6 +1352,160 @@ static int damon_sysfs_commit_input(struct damon_sysfs_kdamond *kdamond) kdamond->contexts->contexts_arr[0]); } +/* + * Flush the content in the result buffer to the result file + */ +static int sysfs_flush_rbuffer(struct sysfs_recorder *rec) +{ + ssize_t sz; + loff_t pos = 0; + struct file *rfile; + + if (!rec->rbuf_offset) + return 0; + + 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 PTR_ERR(rfile); + } + + while (rec->rbuf_offset) { + sz = kernel_write(rfile, rec->rbuf, rec->rbuf_offset, &pos); + if (sz < 0) { + filp_close(rfile, NULL); + return sz; + } + rec->rbuf_offset -= sz; + } + filp_close(rfile, NULL); + + return 0; +} + +/* + * Write a data into the result buffer + */ +static void sysfs_write_rbuf(struct damon_ctx *ctx, char *data, int size) +{ + struct sysfs_recorder *rec = ctx->callback.private; + + if (!rec->rbuf_len || !rec->rbuf || !rec->rfile_path) + return; + if (rec->rbuf_offset + size > rec->rbuf_len) + sysfs_flush_rbuffer(ctx->callback.private); + if (rec->rbuf_offset + size > rec->rbuf_len) { + pr_warn("%s: flush failed, or wrong size given(%u, %d)\n", + __func__, rec->rbuf_offset, size); + return; + } + + memcpy(&rec->rbuf[rec->rbuf_offset], data, size); + rec->rbuf_offset += size; +} + +static unsigned int nr_damon_targets(struct damon_ctx *ctx) +{ + struct damon_target *t; + unsigned int nr_targets = 0; + int count = 0; + + damon_for_each_target(t, ctx) { + count++; + nr_targets++; + } + + return nr_targets; +} + +/* + * Store the aggregated monitoring results to the result buffer + * + * The format for the result buffer is as below: + * + * <time> <number of targets> + * + * target info: <pid> <number of regions> + * region info: <start address> <region size> <nr_accesses> + */ +static int damon_flush_aggregation(struct damon_ctx *c) +{ + struct damon_target *t; + struct timespec64 now; + struct task_struct *tsk; + int tsk_pid = -1; + unsigned int nr = 0; + char buf[128]; + int rc = 0; + + memset(buf, 0, sizeof(buf)); + ktime_get_coarse_ts64(&now); + nr = nr_damon_targets(c); + rc = scnprintf(buf, sizeof(buf), "time: %lld.%09ld, nr: %u\n", + (long long)now.tv_sec, now.tv_nsec, nr); + if (!rc) + return -ENOMEM; + + sysfs_write_rbuf(c, buf, rc); + memset(buf, 0, sizeof(buf)); + + damon_for_each_target(t, c) { + struct damon_region *r; + + tsk = get_pid_task(t->pid, PIDTYPE_PID); + tsk_pid = tsk->pid; + nr = damon_nr_regions(t); + rc = scnprintf(buf, sizeof(buf), "pid: %d, nr: %u\n", tsk_pid, nr); + if (!rc) + return -ENOMEM; + + sysfs_write_rbuf(c, buf, rc); + memset(buf, 0, sizeof(buf)); + + damon_for_each_region(r, t) { + + rc = scnprintf(buf, sizeof(buf), "%lu, %lu, %d\n", + r->ar.start, r->ar.end - r->ar.start, r->nr_accesses); + if (!rc) + return -ENOMEM; + + sysfs_write_rbuf(c, buf, rc); + memset(buf, 0, sizeof(buf)); + } + } + + return 0; +} + +/* + * damon_sysfs_record() - Save the monitoring results to file. + * @kdamond: The kobject wrapper for the associated kdamond. + * + * If the sysfs input is wrong, the kdamond will be terminated. + */ +static int damon_sysfs_record(struct damon_sysfs_kdamond *kdamond) +{ + struct damon_ctx *ctx = kdamond->damon_ctx; + struct sysfs_recorder *rec = ctx->callback.private; + int err = 0; + + if (!damon_sysfs_kdamond_running(kdamond)) + return -EINVAL; + /* TODO: Support multiple contexts per kdamond */ + if (kdamond->contexts->nr != 1) + return -EINVAL; + + err = damon_flush_aggregation(ctx); + if (!err) { + if (rec->rbuf_offset) + err = sysfs_flush_rbuffer(rec); + } + + return err; +} + /* * damon_sysfs_cmd_request_callback() - DAMON callback for handling requests. * @c: The DAMON context of the callback. @@ -1371,6 +1528,9 @@ static int damon_sysfs_cmd_request_callback(struct damon_ctx *c, bool active) if (!kdamond || kdamond->damon_ctx != c) goto out; switch (damon_sysfs_cmd_request.cmd) { + case DAMON_SYSFS_CMD_RECORD: + err = damon_sysfs_record(kdamond); + break; case DAMON_SYSFS_CMD_UPDATE_SCHEMES_STATS: err = damon_sysfs_upd_schemes_stats(kdamond); break; @@ -1433,6 +1593,65 @@ static int damon_sysfs_after_aggregation(struct damon_ctx *c) return damon_sysfs_cmd_request_callback(c, true); } +/* + * sysfs_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 sysfs_set_recording(struct damon_ctx *ctx, + unsigned int rbuf_len, char *rfile_path) +{ + struct sysfs_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; + strscpy(recorder->rfile_path, rfile_path, rfile_path_len + 1); + + return 0; +} + static struct damon_ctx *damon_sysfs_build_ctx( struct damon_sysfs_context *sys_ctx) { @@ -1442,6 +1661,12 @@ static struct damon_ctx *damon_sysfs_build_ctx( if (!ctx) return ERR_PTR(-ENOMEM); + err = sysfs_set_recording(ctx, 0, "none"); + if (err) { + damon_destroy_ctx(ctx); + return ERR_PTR(err); + } + err = damon_sysfs_apply_inputs(ctx, sys_ctx); if (err) { damon_destroy_ctx(ctx); @@ -1599,6 +1824,59 @@ static ssize_t pid_show(struct kobject *kobj, return sysfs_emit(buf, "%d\n", pid); } +static ssize_t record_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct damon_sysfs_kdamond *kdamond = container_of(kobj, + struct damon_sysfs_kdamond, kobj); + struct damon_ctx *ctx; + struct sysfs_recorder *rec; + int len = 0; + + if (!mutex_trylock(&damon_sysfs_lock)) + return -EBUSY; + ctx = kdamond->damon_ctx; + if (!ctx) + goto out; + rec = ctx->callback.private; + len = sysfs_emit(buf, "%u %s\n", rec->rbuf_len, rec->rfile_path); + +out: + mutex_unlock(&damon_sysfs_lock); + return len; +} + +static ssize_t record_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct damon_sysfs_kdamond *kdamond = container_of(kobj, + struct damon_sysfs_kdamond, kobj); + struct damon_ctx *ctx; + unsigned int rbuf_len; + char rfile_path[MAX_RFILE_PATH_LEN]; + ssize_t ret = count; + int err; + + if (sscanf(buf, "%10u %128s", &rbuf_len, rfile_path) != 2) { + ret = -EINVAL; + goto out; + } + + if (!mutex_trylock(&damon_sysfs_lock)) + return -EBUSY; + ctx = kdamond->damon_ctx; + if (!ctx) + goto unlock_out; + + err = sysfs_set_recording(ctx, rbuf_len, rfile_path); + if (err) + ret = err; +unlock_out: + mutex_unlock(&damon_sysfs_lock); +out: + return ret; +} + static void damon_sysfs_kdamond_release(struct kobject *kobj) { struct damon_sysfs_kdamond *kdamond = container_of(kobj, @@ -1615,9 +1893,13 @@ static struct kobj_attribute damon_sysfs_kdamond_state_attr = static struct kobj_attribute damon_sysfs_kdamond_pid_attr = __ATTR_RO_MODE(pid, 0400); +static struct kobj_attribute damon_sysfs_kdamond_record_attr = + __ATTR_RW_MODE(record, 0600); + static struct attribute *damon_sysfs_kdamond_attrs[] = { &damon_sysfs_kdamond_state_attr.attr, &damon_sysfs_kdamond_pid_attr.attr, + &damon_sysfs_kdamond_record_attr.attr, NULL, }; ATTRIBUTE_GROUPS(damon_sysfs_kdamond); -- 2.43.0