| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Data Access Monitor |
| */ |
| |
| #define pr_fmt(fmt) "damon: " fmt |
| |
| #include <linux/damon.h> |
| #include <linux/delay.h> |
| #include <linux/kthread.h> |
| #include <linux/memcontrol.h> |
| #include <linux/mm.h> |
| #include <linux/psi.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/string_choices.h> |
| |
| /* for damon_get_folio() used by node eligible memory metrics */ |
| #include "ops-common.h" |
| |
| #define CREATE_TRACE_POINTS |
| #include <trace/events/damon.h> |
| |
| static DEFINE_MUTEX(damon_lock); |
| static int nr_running_ctxs; |
| static bool running_exclusive_ctxs; |
| |
| static DEFINE_MUTEX(damon_ops_lock); |
| static struct damon_operations damon_registered_ops[NR_DAMON_OPS]; |
| |
| static struct kmem_cache *damon_region_cache __ro_after_init; |
| |
| /* Should be called under damon_ops_lock with id smaller than NR_DAMON_OPS */ |
| static bool __damon_is_registered_ops(enum damon_ops_id id) |
| { |
| struct damon_operations empty_ops = {}; |
| |
| if (!memcmp(&empty_ops, &damon_registered_ops[id], sizeof(empty_ops))) |
| return false; |
| return true; |
| } |
| |
| /** |
| * damon_is_registered_ops() - Check if a given damon_operations is registered. |
| * @id: Id of the damon_operations to check if registered. |
| * |
| * Return: true if the ops is set, false otherwise. |
| */ |
| bool damon_is_registered_ops(enum damon_ops_id id) |
| { |
| bool registered; |
| |
| if (id >= NR_DAMON_OPS) |
| return false; |
| mutex_lock(&damon_ops_lock); |
| registered = __damon_is_registered_ops(id); |
| mutex_unlock(&damon_ops_lock); |
| return registered; |
| } |
| |
| /** |
| * damon_register_ops() - Register a monitoring operations set to DAMON. |
| * @ops: monitoring operations set to register. |
| * |
| * This function registers a monitoring operations set of valid &struct |
| * damon_operations->id so that others can find and use them later. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_register_ops(struct damon_operations *ops) |
| { |
| int err = 0; |
| |
| if (ops->id >= NR_DAMON_OPS) |
| return -EINVAL; |
| |
| mutex_lock(&damon_ops_lock); |
| /* Fail for already registered ops */ |
| if (__damon_is_registered_ops(ops->id)) |
| err = -EINVAL; |
| else |
| damon_registered_ops[ops->id] = *ops; |
| mutex_unlock(&damon_ops_lock); |
| return err; |
| } |
| |
| /** |
| * damon_select_ops() - Select a monitoring operations to use with the context. |
| * @ctx: monitoring context to use the operations. |
| * @id: id of the registered monitoring operations to select. |
| * |
| * This function finds registered monitoring operations set of @id and make |
| * @ctx to use it. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_select_ops(struct damon_ctx *ctx, enum damon_ops_id id) |
| { |
| int err = 0; |
| |
| if (id >= NR_DAMON_OPS) |
| return -EINVAL; |
| |
| mutex_lock(&damon_ops_lock); |
| if (!__damon_is_registered_ops(id)) |
| err = -EINVAL; |
| else |
| ctx->ops = damon_registered_ops[id]; |
| mutex_unlock(&damon_ops_lock); |
| return err; |
| } |
| |
| struct damon_filter *damon_new_filter(enum damon_filter_type type, |
| bool matching, bool allow) |
| { |
| struct damon_filter *filter; |
| |
| filter = kmalloc_obj(*filter); |
| if (!filter) |
| return NULL; |
| filter->type = type; |
| filter->matching = matching; |
| filter->allow = allow; |
| INIT_LIST_HEAD(&filter->list); |
| return filter; |
| } |
| |
| void damon_add_filter(struct damon_probe *p, struct damon_filter *f) |
| { |
| list_add_tail(&f->list, &p->filters); |
| } |
| |
| static void damon_del_filter(struct damon_filter *f) |
| { |
| list_del(&f->list); |
| } |
| |
| static void damon_free_filter(struct damon_filter *f) |
| { |
| kfree(f); |
| } |
| |
| void damon_destroy_filter(struct damon_filter *f) |
| { |
| damon_del_filter(f); |
| damon_free_filter(f); |
| } |
| |
| static struct damon_filter *damon_nth_filter(int n, struct damon_probe *p) |
| { |
| struct damon_filter *f; |
| int i = 0; |
| |
| damon_for_each_filter(f, p) { |
| if (i++ == n) |
| return f; |
| } |
| return NULL; |
| } |
| |
| struct damon_probe *damon_new_probe(void) |
| { |
| struct damon_probe *p; |
| |
| p = kmalloc_obj(*p); |
| if (!p) |
| return NULL; |
| p->weight = 0; |
| INIT_LIST_HEAD(&p->filters); |
| INIT_LIST_HEAD(&p->list); |
| return p; |
| } |
| |
| void damon_add_probe(struct damon_ctx *ctx, struct damon_probe *probe) |
| { |
| list_add_tail(&probe->list, &ctx->probes); |
| } |
| |
| static void damon_del_probe(struct damon_probe *p) |
| { |
| list_del(&p->list); |
| } |
| |
| static void damon_free_probe(struct damon_probe *p) |
| { |
| struct damon_filter *f, *next; |
| |
| damon_for_each_filter_safe(f, next, p) |
| damon_free_filter(f); |
| kfree(p); |
| } |
| |
| static void damon_destroy_probe(struct damon_probe *p) |
| { |
| damon_del_probe(p); |
| damon_free_probe(p); |
| } |
| |
| static struct damon_probe *damon_nth_probe(int n, struct damon_ctx *ctx) |
| { |
| struct damon_probe *p; |
| int i = 0; |
| |
| damon_for_each_probe(p, ctx) { |
| if (i++ == n) |
| return p; |
| } |
| return NULL; |
| } |
| |
| static bool damon_has_probe_weights(struct damon_ctx *c) |
| { |
| struct damon_probe *p; |
| |
| damon_for_each_probe(p, c) { |
| if (p->weight) |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| * damon_mvsum() - Returns pseudo moving sum value for a time window. |
| * @current_nr: The value of the current aggregation window. |
| * @last_nr: The value of the last aggregation window. |
| * @left_window_bp: Left time of the current aggregation window. |
| * |
| * This function calculates a pseudo moving sum value of a counter that is |
| * aggregated for each time window. @current_nr is the value of the counter |
| * that aggregated so far (maybe not yet complete), from the beginning of the |
| * current aggregation time window. @last_nr is the value of the counter that |
| * has completely aggregated in the last aggregation time window. |
| * @left_window_bp represents how much time is left for the current aggregation |
| * time window in bp (1/10,000). For example, the aggregation time window is |
| * for every 10 seconds and 7 seconds has passed since the beginning of the |
| * current window, this parameter will be 3000 ((10 - 7) / 10 * 10000). |
| * |
| * The logic assumes the aggregation in the last phase was made in a single |
| * speed. Based on the assumption, the value from the last window that needs |
| * to be added to the current value is calculated as a portion of the last |
| * value based on the remaining time window. |
| */ |
| static unsigned long damon_mvsum(unsigned long current_nr, |
| unsigned long last_nr, unsigned long left_window_bp) |
| { |
| return current_nr + mult_frac(last_nr, left_window_bp, 10000); |
| } |
| |
| /** |
| * damon_nr_accesses_mvsum() - Returns moving sum access frequency score. |
| * @r: Region to get the access frequency of. |
| * @ctx: DAMON context of @r. |
| * |
| * This function returns for how many sampling iterations in the last |
| * aggregation interval (&damon_attrs->aggr_interval) the region was found to |
| * be accessed. Hence the value can be interpreted as the relative access |
| * frequency score of the region (@r). The value is calculated as a pseudo |
| * moving sum, and hence it is not an exact value but just a best-effort |
| * reasonable estimation. |
| * |
| * Return: the pseudo moving sum access frequency score. |
| */ |
| unsigned int damon_nr_accesses_mvsum(struct damon_region *r, |
| struct damon_ctx *ctx) |
| { |
| unsigned long sample_interval, aggr_interval; |
| unsigned long window_len, left_window, left_window_bp; |
| |
| sample_interval = ctx->attrs.sample_interval ? : 1; |
| aggr_interval = ctx->attrs.aggr_interval ? : 1; |
| window_len = aggr_interval / sample_interval; |
| if (time_after_eq(ctx->passed_sample_intervals, |
| ctx->next_aggregation_sis)) |
| left_window = 0; |
| else |
| left_window = ctx->next_aggregation_sis - |
| ctx->passed_sample_intervals; |
| left_window_bp = mult_frac(left_window, 10000, window_len); |
| |
| if (left_window_bp == 10000) |
| return r->last_nr_accesses; |
| |
| return damon_mvsum(r->nr_accesses, r->last_nr_accesses, |
| left_window_bp); |
| } |
| |
| unsigned char damon_probe_hits_mvsum(int probe_idx, struct damon_region *r, |
| struct damon_ctx *ctx) |
| { |
| unsigned long sample_interval, aggr_interval; |
| unsigned long window_len, left_window, left_window_bp; |
| |
| sample_interval = ctx->attrs.sample_interval ? : 1; |
| aggr_interval = ctx->attrs.aggr_interval ? : 1; |
| window_len = aggr_interval / sample_interval; |
| if (time_after_eq(ctx->passed_sample_intervals, |
| ctx->next_aggregation_sis)) |
| left_window = 0; |
| else |
| left_window = ctx->next_aggregation_sis - |
| ctx->passed_sample_intervals; |
| left_window_bp = mult_frac(left_window, 10000, window_len); |
| |
| if (left_window_bp == 10000) |
| return r->last_probe_hits[probe_idx]; |
| |
| return damon_mvsum(r->probe_hits[probe_idx], |
| r->last_probe_hits[probe_idx], left_window_bp); |
| } |
| |
| #ifdef CONFIG_DAMON_DEBUG_SANITY |
| static void damon_verify_new_region(unsigned long start, unsigned long end) |
| { |
| WARN_ONCE(start >= end, "start %lu >= end %lu\n", start, end); |
| } |
| #else |
| static void damon_verify_new_region(unsigned long start, unsigned long end) |
| { |
| } |
| #endif |
| |
| /* |
| * Construct a damon_region struct |
| * |
| * Returns the pointer to the new struct if success, or NULL otherwise |
| */ |
| struct damon_region *damon_new_region(unsigned long start, unsigned long end) |
| { |
| struct damon_region *region; |
| int i; |
| |
| damon_verify_new_region(start, end); |
| region = kmem_cache_alloc(damon_region_cache, GFP_KERNEL); |
| if (!region) |
| return NULL; |
| |
| region->ar.start = start; |
| region->ar.end = end; |
| region->nr_accesses = 0; |
| for (i = 0; i < DAMON_MAX_PROBES; i++) { |
| region->probe_hits[i] = 0; |
| region->last_probe_hits[i] = 0; |
| } |
| INIT_LIST_HEAD(®ion->list); |
| |
| region->age = 0; |
| region->last_nr_accesses = 0; |
| |
| return region; |
| } |
| |
| static void damon_add_region(struct damon_region *r, struct damon_target *t) |
| { |
| list_add_tail(&r->list, &t->regions_list); |
| t->nr_regions++; |
| } |
| |
| /* |
| * Add a region between two other regions |
| */ |
| static inline void damon_insert_region(struct damon_region *r, |
| struct damon_region *prev, struct damon_region *next, |
| struct damon_target *t) |
| { |
| __list_add(&r->list, &prev->list, &next->list); |
| t->nr_regions++; |
| } |
| |
| #ifdef CONFIG_DAMON_DEBUG_SANITY |
| static void damon_verify_del_region(struct damon_target *t) |
| { |
| WARN_ONCE(t->nr_regions == 0, "t->nr_regions == 0\n"); |
| } |
| #else |
| static void damon_verify_del_region(struct damon_target *t) |
| { |
| } |
| #endif |
| |
| static void damon_del_region(struct damon_region *r, struct damon_target *t) |
| { |
| damon_verify_del_region(t); |
| |
| list_del(&r->list); |
| t->nr_regions--; |
| } |
| |
| static void damon_free_region(struct damon_region *r) |
| { |
| kmem_cache_free(damon_region_cache, r); |
| } |
| |
| static void damon_destroy_region(struct damon_region *r, |
| struct damon_target *t) |
| { |
| damon_del_region(r, t); |
| damon_free_region(r); |
| } |
| |
| static bool damon_is_last_region(struct damon_region *r, |
| struct damon_target *t) |
| { |
| return list_is_last(&r->list, &t->regions_list); |
| } |
| |
| /** |
| * damon_probe_hits_wsum() - Returns probe hits weighted sum of a region. |
| * @r: region to get the weighted sum of. |
| * @last: if the request is for last-window aggregated probe hits. |
| * @ctx: context of &r. |
| * |
| * Return: the weighted sum of probe hits of the region. |
| */ |
| unsigned int damon_probe_hits_wsum(struct damon_region *r, bool last, |
| struct damon_ctx *ctx) |
| { |
| struct damon_probe *probe; |
| unsigned int sum = 0; |
| int i = 0; |
| |
| damon_for_each_probe(probe, ctx) { |
| if (last) |
| sum += r->last_probe_hits[i++] * probe->weight; |
| else |
| sum += r->probe_hits[i++] * probe->weight; |
| } |
| return sum; |
| } |
| |
| /* |
| * Check whether a region is intersecting an address range |
| * |
| * Returns true if it is. |
| */ |
| static bool damon_intersect(struct damon_region *r, |
| struct damon_addr_range *re) |
| { |
| return !(r->ar.end <= re->start || re->end <= r->ar.start); |
| } |
| |
| /* |
| * Fill holes in regions with new regions. |
| */ |
| static int damon_fill_regions_holes(struct damon_region *first, |
| struct damon_region *last, struct damon_target *t) |
| { |
| struct damon_region *r = first; |
| |
| damon_for_each_region_from(r, t) { |
| struct damon_region *next, *newr; |
| |
| if (r == last) |
| break; |
| next = damon_next_region(r); |
| if (r->ar.end != next->ar.start) { |
| newr = damon_new_region(r->ar.end, next->ar.start); |
| if (!newr) |
| return -ENOMEM; |
| damon_insert_region(newr, r, next, t); |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * damon_set_regions() - Set regions of a target for given address ranges. |
| * @t: the given target. |
| * @ranges: array of new monitoring target ranges. |
| * @nr_ranges: length of @ranges. |
| * @min_region_sz: minimum region size. |
| * |
| * This function adds new regions to, or modify existing regions of a |
| * monitoring target to fit in specific ranges. |
| * |
| * Return: 0 if success, or negative error code otherwise. |
| */ |
| int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges, |
| unsigned int nr_ranges, unsigned long min_region_sz) |
| { |
| struct damon_region *r, *next; |
| unsigned int i; |
| unsigned long last_end; |
| int err; |
| |
| for (i = 0; i < nr_ranges; i++) { |
| unsigned long start, end; |
| |
| start = ALIGN_DOWN(ranges[i].start, min_region_sz); |
| end = ALIGN(ranges[i].end, min_region_sz); |
| if (start >= end) |
| return -EINVAL; |
| if (i > 0 && last_end > start) |
| return -EINVAL; |
| last_end = end; |
| } |
| |
| /* Remove regions which are not in the new ranges */ |
| damon_for_each_region_safe(r, next, t) { |
| for (i = 0; i < nr_ranges; i++) { |
| if (damon_intersect(r, &ranges[i])) |
| break; |
| } |
| if (i == nr_ranges) |
| damon_destroy_region(r, t); |
| } |
| |
| if (!damon_nr_regions(t)) { |
| for (i = 0; i < nr_ranges; i++) { |
| r = damon_new_region( |
| ALIGN_DOWN(ranges[i].start, |
| min_region_sz), |
| ALIGN(ranges[i].end, min_region_sz)); |
| if (!r) |
| return -ENOMEM; |
| damon_add_region(r, t); |
| } |
| return 0; |
| } |
| |
| r = damon_first_region(t); |
| /* Add new regions or resize existing regions to fit in the ranges */ |
| for (i = 0; i < nr_ranges; i++) { |
| struct damon_region *first = NULL, *last, *newr; |
| struct damon_addr_range *range; |
| bool insert_before_r = false; |
| |
| range = &ranges[i]; |
| /* Get the first/last regions intersecting with the range */ |
| damon_for_each_region_from(r, t) { |
| if (damon_intersect(r, range)) { |
| if (!first) |
| first = r; |
| last = r; |
| } |
| if (r->ar.start >= range->end) { |
| insert_before_r = true; |
| break; |
| } |
| } |
| if (!first) { |
| /* no region intersects with this range */ |
| newr = damon_new_region( |
| ALIGN_DOWN(range->start, |
| min_region_sz), |
| ALIGN(range->end, min_region_sz)); |
| if (!newr) |
| return -ENOMEM; |
| if (insert_before_r) |
| damon_insert_region(newr, damon_prev_region(r), |
| r, t); |
| else |
| damon_add_region(newr, t); |
| } else { |
| /* resize intersecting regions to fit in this range */ |
| first->ar.start = ALIGN_DOWN(range->start, |
| min_region_sz); |
| last->ar.end = ALIGN(range->end, min_region_sz); |
| |
| /* fill possible holes in the range */ |
| err = damon_fill_regions_holes(first, last, t); |
| if (err) |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| struct damos_filter *damos_new_filter(enum damos_filter_type type, |
| bool matching, bool allow) |
| { |
| struct damos_filter *filter; |
| |
| filter = kmalloc_obj(*filter); |
| if (!filter) |
| return NULL; |
| filter->type = type; |
| filter->matching = matching; |
| filter->allow = allow; |
| INIT_LIST_HEAD(&filter->list); |
| return filter; |
| } |
| |
| /** |
| * damos_filter_for_ops() - Return if the filter is ops-handled one. |
| * @type: type of the filter. |
| * |
| * Return: true if the filter of @type needs to be handled by ops layer, false |
| * otherwise. |
| */ |
| bool damos_filter_for_ops(enum damos_filter_type type) |
| { |
| switch (type) { |
| case DAMOS_FILTER_TYPE_ADDR: |
| case DAMOS_FILTER_TYPE_TARGET: |
| return false; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| void damos_add_filter(struct damos *s, struct damos_filter *f) |
| { |
| if (damos_filter_for_ops(f->type)) |
| list_add_tail(&f->list, &s->ops_filters); |
| else |
| list_add_tail(&f->list, &s->core_filters); |
| } |
| |
| static void damos_del_filter(struct damos_filter *f) |
| { |
| list_del(&f->list); |
| } |
| |
| static void damos_free_filter(struct damos_filter *f) |
| { |
| kfree(f); |
| } |
| |
| void damos_destroy_filter(struct damos_filter *f) |
| { |
| damos_del_filter(f); |
| damos_free_filter(f); |
| } |
| |
| struct damos_quota_goal *damos_new_quota_goal( |
| enum damos_quota_goal_metric metric, |
| unsigned long target_value) |
| { |
| struct damos_quota_goal *goal; |
| |
| goal = kmalloc_obj(*goal); |
| if (!goal) |
| return NULL; |
| goal->metric = metric; |
| goal->target_value = target_value; |
| INIT_LIST_HEAD(&goal->list); |
| return goal; |
| } |
| |
| void damos_add_quota_goal(struct damos_quota *q, struct damos_quota_goal *g) |
| { |
| list_add_tail(&g->list, &q->goals); |
| } |
| |
| static void damos_del_quota_goal(struct damos_quota_goal *g) |
| { |
| list_del(&g->list); |
| } |
| |
| static void damos_free_quota_goal(struct damos_quota_goal *g) |
| { |
| kfree(g); |
| } |
| |
| void damos_destroy_quota_goal(struct damos_quota_goal *g) |
| { |
| damos_del_quota_goal(g); |
| damos_free_quota_goal(g); |
| } |
| |
| static bool damos_quota_goals_empty(struct damos_quota *q) |
| { |
| return list_empty(&q->goals); |
| } |
| |
| /* initialize fields of @quota that normally API users wouldn't set */ |
| static struct damos_quota *damos_quota_init(struct damos_quota *quota) |
| { |
| quota->esz = 0; |
| quota->total_charged_sz = 0; |
| quota->total_charged_ns = 0; |
| quota->charged_sz = 0; |
| quota->charged_from = 0; |
| quota->charge_target_from = NULL; |
| quota->charge_addr_from = 0; |
| quota->esz_bp = 0; |
| return quota; |
| } |
| |
| struct damos *damon_new_scheme(struct damos_access_pattern *pattern, |
| enum damos_action action, |
| unsigned long apply_interval_us, |
| struct damos_quota *quota, |
| struct damos_watermarks *wmarks, |
| int target_nid) |
| { |
| struct damos *scheme; |
| |
| scheme = kmalloc_obj(*scheme); |
| if (!scheme) |
| return NULL; |
| scheme->pattern = *pattern; |
| scheme->action = action; |
| scheme->apply_interval_us = apply_interval_us; |
| /* |
| * next_apply_sis will be set when kdamond starts. While kdamond is |
| * running, it will also updated when it is added to the DAMON context, |
| * or damon_attrs are updated. |
| */ |
| scheme->next_apply_sis = 0; |
| scheme->walk_completed = false; |
| INIT_LIST_HEAD(&scheme->core_filters); |
| INIT_LIST_HEAD(&scheme->ops_filters); |
| scheme->stat = (struct damos_stat){}; |
| scheme->max_nr_snapshots = 0; |
| scheme->last_applied = NULL; |
| INIT_LIST_HEAD(&scheme->list); |
| |
| scheme->quota = *(damos_quota_init(quota)); |
| /* quota.goals should be separately set by caller */ |
| INIT_LIST_HEAD(&scheme->quota.goals); |
| |
| scheme->wmarks = *wmarks; |
| scheme->wmarks.activated = true; |
| |
| scheme->migrate_dests = (struct damos_migrate_dests){}; |
| scheme->target_nid = target_nid; |
| |
| return scheme; |
| } |
| |
| static void damos_set_next_apply_sis(struct damos *s, struct damon_ctx *ctx) |
| { |
| unsigned long sample_interval = ctx->attrs.sample_interval ? |
| ctx->attrs.sample_interval : 1; |
| unsigned long apply_interval = s->apply_interval_us ? |
| s->apply_interval_us : ctx->attrs.aggr_interval; |
| |
| s->next_apply_sis = ctx->passed_sample_intervals + |
| apply_interval / sample_interval; |
| } |
| |
| void damon_add_scheme(struct damon_ctx *ctx, struct damos *s) |
| { |
| list_add_tail(&s->list, &ctx->schemes); |
| damos_set_next_apply_sis(s, ctx); |
| } |
| |
| static void damon_del_scheme(struct damos *s) |
| { |
| list_del(&s->list); |
| } |
| |
| static void damon_free_scheme(struct damos *s) |
| { |
| kfree(s); |
| } |
| |
| void damon_destroy_scheme(struct damos *s) |
| { |
| struct damos_quota_goal *g, *g_next; |
| struct damos_filter *f, *next; |
| |
| damos_for_each_quota_goal_safe(g, g_next, &s->quota) |
| damos_destroy_quota_goal(g); |
| |
| damos_for_each_core_filter_safe(f, next, s) |
| damos_destroy_filter(f); |
| |
| damos_for_each_ops_filter_safe(f, next, s) |
| damos_destroy_filter(f); |
| |
| kfree(s->migrate_dests.node_id_arr); |
| kfree(s->migrate_dests.weight_arr); |
| damon_del_scheme(s); |
| damon_free_scheme(s); |
| } |
| |
| /* |
| * Construct a damon_target struct |
| * |
| * Returns the pointer to the new struct if success, or NULL otherwise |
| */ |
| struct damon_target *damon_new_target(void) |
| { |
| struct damon_target *t; |
| |
| t = kmalloc_obj(*t); |
| if (!t) |
| return NULL; |
| |
| t->pid = NULL; |
| t->nr_regions = 0; |
| INIT_LIST_HEAD(&t->regions_list); |
| INIT_LIST_HEAD(&t->list); |
| t->obsolete = false; |
| |
| return t; |
| } |
| |
| void damon_add_target(struct damon_ctx *ctx, struct damon_target *t) |
| { |
| list_add_tail(&t->list, &ctx->adaptive_targets); |
| } |
| |
| bool damon_targets_empty(struct damon_ctx *ctx) |
| { |
| return list_empty(&ctx->adaptive_targets); |
| } |
| |
| static void damon_del_target(struct damon_target *t) |
| { |
| list_del(&t->list); |
| } |
| |
| void damon_free_target(struct damon_target *t) |
| { |
| struct damon_region *r, *next; |
| |
| damon_for_each_region_safe(r, next, t) |
| damon_free_region(r); |
| kfree(t); |
| } |
| |
| void damon_destroy_target(struct damon_target *t, struct damon_ctx *ctx) |
| { |
| |
| if (ctx && ctx->ops.cleanup_target) |
| ctx->ops.cleanup_target(t); |
| |
| damon_del_target(t); |
| damon_free_target(t); |
| } |
| |
| unsigned int damon_nr_regions(struct damon_target *t) |
| { |
| return t->nr_regions; |
| } |
| |
| struct damon_ctx *damon_new_ctx(void) |
| { |
| struct damon_ctx *ctx; |
| |
| ctx = kzalloc_obj(*ctx); |
| if (!ctx) |
| return NULL; |
| |
| init_completion(&ctx->kdamond_started); |
| |
| ctx->attrs.sample_interval = 5 * 1000; |
| ctx->attrs.aggr_interval = 100 * 1000; |
| ctx->attrs.ops_update_interval = 60 * 1000 * 1000; |
| |
| ctx->passed_sample_intervals = 0; |
| /* These will be set from kdamond_init_ctx() */ |
| ctx->next_aggregation_sis = 0; |
| ctx->next_ops_update_sis = 0; |
| |
| mutex_init(&ctx->kdamond_lock); |
| INIT_LIST_HEAD(&ctx->call_controls); |
| mutex_init(&ctx->call_controls_lock); |
| mutex_init(&ctx->walk_control_lock); |
| |
| ctx->attrs.min_nr_regions = 10; |
| ctx->attrs.max_nr_regions = 1000; |
| |
| INIT_LIST_HEAD(&ctx->probes); |
| |
| ctx->addr_unit = 1; |
| ctx->min_region_sz = DAMON_MIN_REGION_SZ; |
| |
| INIT_LIST_HEAD(&ctx->adaptive_targets); |
| INIT_LIST_HEAD(&ctx->schemes); |
| |
| prandom_seed_state(&ctx->rnd_state, get_random_u64()); |
| |
| return ctx; |
| } |
| |
| static void damon_destroy_targets(struct damon_ctx *ctx) |
| { |
| struct damon_target *t, *next_t; |
| |
| damon_for_each_target_safe(t, next_t, ctx) |
| damon_destroy_target(t, ctx); |
| } |
| |
| void damon_destroy_ctx(struct damon_ctx *ctx) |
| { |
| struct damos *s, *next_s; |
| struct damon_probe *p, *next_p; |
| |
| damon_destroy_targets(ctx); |
| |
| damon_for_each_scheme_safe(s, next_s, ctx) |
| damon_destroy_scheme(s); |
| |
| damon_for_each_probe_safe(p, next_p, ctx) |
| damon_destroy_probe(p); |
| |
| kfree(ctx); |
| } |
| |
| static bool damon_attrs_equals(const struct damon_attrs *attrs1, |
| const struct damon_attrs *attrs2) |
| { |
| const struct damon_intervals_goal *ig1 = &attrs1->intervals_goal; |
| const struct damon_intervals_goal *ig2 = &attrs2->intervals_goal; |
| |
| return attrs1->sample_interval == attrs2->sample_interval && |
| attrs1->aggr_interval == attrs2->aggr_interval && |
| attrs1->ops_update_interval == attrs2->ops_update_interval && |
| attrs1->min_nr_regions == attrs2->min_nr_regions && |
| attrs1->max_nr_regions == attrs2->max_nr_regions && |
| ig1->access_bp == ig2->access_bp && |
| ig1->aggrs == ig2->aggrs && |
| ig1->min_sample_us == ig2->min_sample_us && |
| ig1->max_sample_us == ig2->max_sample_us; |
| } |
| |
| static unsigned int damon_age_for_new_attrs(unsigned int age, |
| struct damon_attrs *old_attrs, struct damon_attrs *new_attrs) |
| { |
| return age * old_attrs->aggr_interval / new_attrs->aggr_interval; |
| } |
| |
| /* convert sample ratio in bp (per 10,000) to count */ |
| static unsigned int damon_sample_bp_to_count( |
| unsigned int bp, struct damon_attrs *attrs) |
| { |
| return bp * damon_nr_samples_per_aggr(attrs) / 10000; |
| } |
| |
| /* convert sample count to ratio in bp (per 10,000) */ |
| static unsigned int damon_sample_count_to_bp( |
| unsigned int count, struct damon_attrs *attrs) |
| { |
| return mult_frac(count, 10000, damon_nr_samples_per_aggr(attrs)); |
| } |
| |
| static unsigned int damon_nr_samples_for_new_attrs(unsigned int nr, |
| struct damon_attrs *old_attrs, struct damon_attrs *new_attrs) |
| { |
| return damon_sample_bp_to_count( |
| damon_sample_count_to_bp(nr, old_attrs), new_attrs); |
| } |
| |
| static void damon_update_probe_hits(struct damon_region *r, |
| struct damon_attrs *old_attrs, struct damon_attrs *new_attrs, |
| bool aggregating, struct damon_ctx *ctx) |
| { |
| struct damon_probe *p; |
| int i = 0; |
| |
| damon_for_each_probe(p, ctx) { |
| r->last_probe_hits[i] = damon_nr_samples_for_new_attrs( |
| r->last_probe_hits[i], old_attrs, new_attrs); |
| if (!aggregating) |
| r->probe_hits[i] = damon_nr_samples_for_new_attrs( |
| r->probe_hits[i], old_attrs, |
| new_attrs); |
| else |
| r->probe_hits[i] = 0; |
| i++; |
| } |
| } |
| |
| static void damon_update_monitoring_result(struct damon_region *r, |
| struct damon_attrs *old_attrs, struct damon_attrs *new_attrs, |
| bool aggregating, struct damon_ctx *ctx) |
| { |
| damon_update_probe_hits(r, old_attrs, new_attrs, aggregating, ctx); |
| |
| r->last_nr_accesses = damon_nr_samples_for_new_attrs( |
| r->last_nr_accesses, old_attrs, new_attrs); |
| if (!aggregating) |
| r->nr_accesses = damon_nr_samples_for_new_attrs( |
| r->nr_accesses, old_attrs, new_attrs); |
| else |
| /* |
| * if this is called in the middle of the aggregation, reset |
| * the aggregations we made so far for this aggregation |
| * interval. In other words, make the status like |
| * kdamond_reset_aggregated() is called. |
| */ |
| r->nr_accesses = 0; |
| r->age = damon_age_for_new_attrs(r->age, old_attrs, new_attrs); |
| } |
| |
| /* |
| * region->nr_accesses is the number of sampling intervals in the last |
| * aggregation interval that access to the region has found, and region->age is |
| * the number of aggregation intervals that its access pattern has maintained. |
| * For the reason, the real meaning of the two fields depend on current |
| * sampling interval and aggregation interval. This function updates |
| * ->nr_accesses and ->age of given damon_ctx's regions for new damon_attrs. |
| */ |
| static void damon_update_monitoring_results(struct damon_ctx *ctx, |
| struct damon_attrs *new_attrs, bool aggregating) |
| { |
| struct damon_attrs *old_attrs = &ctx->attrs; |
| struct damon_target *t; |
| struct damon_region *r; |
| |
| /* if any interval is zero, simply forgive conversion */ |
| if (!old_attrs->sample_interval || !old_attrs->aggr_interval || |
| !new_attrs->sample_interval || |
| !new_attrs->aggr_interval) |
| return; |
| |
| damon_for_each_target(t, ctx) |
| damon_for_each_region(r, t) |
| damon_update_monitoring_result(r, old_attrs, new_attrs, |
| aggregating, ctx); |
| } |
| |
| /* |
| * damon_valid_intervals_goal() - return if the intervals goal of @attrs is |
| * valid. |
| */ |
| static bool damon_valid_intervals_goal(struct damon_attrs *attrs) |
| { |
| struct damon_intervals_goal *goal = &attrs->intervals_goal; |
| |
| /* tuning is disabled */ |
| if (!goal->aggrs) |
| return true; |
| if (goal->min_sample_us > goal->max_sample_us) |
| return false; |
| if (attrs->sample_interval < goal->min_sample_us || |
| goal->max_sample_us < attrs->sample_interval) |
| return false; |
| return true; |
| } |
| |
| /** |
| * damon_set_attrs() - Set attributes for the monitoring. |
| * @ctx: monitoring context |
| * @attrs: monitoring attributes |
| * |
| * This function updates monitoring results and next monitoring/damos operation |
| * schedules. Because those are periodically updated by kdamond, this should |
| * be called from a safe contexts. Such contexts include damon_ctx setup time |
| * while the kdamond is not yet started, and inside of kdamond_fn(). |
| * |
| * In detail, all DAMON API callers directly call this function for initial |
| * setup of damon_ctx before calling damon_start(). Some of the API callers |
| * also indirectly call this function via damon_call() -> damon_commit() for |
| * online parameters updates. Finally, kdamond_fn() itself use this for |
| * applying auto-tuned monitoring intervals. |
| * |
| * Every time interval is in micro-seconds. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_set_attrs(struct damon_ctx *ctx, struct damon_attrs *attrs) |
| { |
| unsigned long sample_interval = attrs->sample_interval ? |
| attrs->sample_interval : 1; |
| struct damos *s; |
| bool aggregating = ctx->passed_sample_intervals < |
| ctx->next_aggregation_sis; |
| |
| if (!damon_valid_intervals_goal(attrs)) |
| return -EINVAL; |
| |
| if (attrs->min_nr_regions < 3) |
| return -EINVAL; |
| if (attrs->min_nr_regions > attrs->max_nr_regions) |
| return -EINVAL; |
| if (attrs->sample_interval > attrs->aggr_interval) |
| return -EINVAL; |
| |
| /* calls from core-external doesn't set this. */ |
| if (!attrs->aggr_samples) |
| attrs->aggr_samples = attrs->aggr_interval / sample_interval; |
| |
| ctx->next_aggregation_sis = ctx->passed_sample_intervals + |
| attrs->aggr_interval / sample_interval; |
| ctx->next_ops_update_sis = ctx->passed_sample_intervals + |
| attrs->ops_update_interval / sample_interval; |
| /* |
| * next_intervals_tune_sis will be updated inside kdamond_fn(). |
| */ |
| |
| damon_update_monitoring_results(ctx, attrs, aggregating); |
| ctx->attrs = *attrs; |
| |
| damon_for_each_scheme(s, ctx) |
| damos_set_next_apply_sis(s, ctx); |
| |
| return 0; |
| } |
| |
| /** |
| * damon_set_schemes() - Set data access monitoring based operation schemes. |
| * @ctx: monitoring context |
| * @schemes: array of the schemes |
| * @nr_schemes: number of entries in @schemes |
| * |
| * This function should not be called while the kdamond of the context is |
| * running. |
| */ |
| void damon_set_schemes(struct damon_ctx *ctx, struct damos **schemes, |
| ssize_t nr_schemes) |
| { |
| struct damos *s, *next; |
| ssize_t i; |
| |
| damon_for_each_scheme_safe(s, next, ctx) |
| damon_destroy_scheme(s); |
| for (i = 0; i < nr_schemes; i++) |
| damon_add_scheme(ctx, schemes[i]); |
| } |
| |
| static struct damos_quota_goal *damos_nth_quota_goal( |
| int n, struct damos_quota *q) |
| { |
| struct damos_quota_goal *goal; |
| int i = 0; |
| |
| damos_for_each_quota_goal(goal, q) { |
| if (i++ == n) |
| return goal; |
| } |
| return NULL; |
| } |
| |
| static void damos_commit_quota_goal_union( |
| struct damos_quota_goal *dst, struct damos_quota_goal *src) |
| { |
| switch (dst->metric) { |
| case DAMOS_QUOTA_NODE_MEM_USED_BP: |
| case DAMOS_QUOTA_NODE_MEM_FREE_BP: |
| dst->nid = src->nid; |
| break; |
| case DAMOS_QUOTA_NODE_MEMCG_USED_BP: |
| case DAMOS_QUOTA_NODE_MEMCG_FREE_BP: |
| dst->nid = src->nid; |
| dst->memcg_id = src->memcg_id; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void damos_commit_quota_goal( |
| struct damos_quota_goal *dst, struct damos_quota_goal *src) |
| { |
| dst->metric = src->metric; |
| dst->target_value = src->target_value; |
| if (dst->metric == DAMOS_QUOTA_USER_INPUT) |
| dst->current_value = src->current_value; |
| /* keep last_psi_total as is, since it will be updated in next cycle */ |
| damos_commit_quota_goal_union(dst, src); |
| } |
| |
| /** |
| * damos_commit_quota_goals() - Commit DAMOS quota goals to another quota. |
| * @dst: The commit destination DAMOS quota. |
| * @src: The commit source DAMOS quota. |
| * |
| * Copies user-specified parameters for quota goals from @src to @dst. Users |
| * should use this function for quota goals-level parameters update of running |
| * DAMON contexts, instead of manual in-place updates. |
| * |
| * This function should be called from parameters-update safe context, like |
| * damon_call(). |
| */ |
| int damos_commit_quota_goals(struct damos_quota *dst, struct damos_quota *src) |
| { |
| struct damos_quota_goal *dst_goal, *next, *src_goal, *new_goal; |
| int i = 0, j = 0; |
| |
| damos_for_each_quota_goal_safe(dst_goal, next, dst) { |
| src_goal = damos_nth_quota_goal(i++, src); |
| if (src_goal) |
| damos_commit_quota_goal(dst_goal, src_goal); |
| else |
| damos_destroy_quota_goal(dst_goal); |
| } |
| damos_for_each_quota_goal_safe(src_goal, next, src) { |
| if (j++ < i) |
| continue; |
| new_goal = damos_new_quota_goal( |
| src_goal->metric, src_goal->target_value); |
| if (!new_goal) |
| return -ENOMEM; |
| damos_commit_quota_goal(new_goal, src_goal); |
| damos_add_quota_goal(dst, new_goal); |
| } |
| return 0; |
| } |
| |
| static int damos_commit_quota(struct damos_quota *dst, struct damos_quota *src) |
| { |
| int err; |
| |
| dst->reset_interval = src->reset_interval; |
| dst->ms = src->ms; |
| dst->sz = src->sz; |
| err = damos_commit_quota_goals(dst, src); |
| if (err) |
| return err; |
| dst->goal_tuner = src->goal_tuner; |
| dst->fail_charge_num = src->fail_charge_num; |
| dst->fail_charge_denom = src->fail_charge_denom; |
| dst->weight_sz = src->weight_sz; |
| dst->weight_nr_accesses = src->weight_nr_accesses; |
| dst->weight_age = src->weight_age; |
| return 0; |
| } |
| |
| static struct damos_filter *damos_nth_core_filter(int n, struct damos *s) |
| { |
| struct damos_filter *filter; |
| int i = 0; |
| |
| damos_for_each_core_filter(filter, s) { |
| if (i++ == n) |
| return filter; |
| } |
| return NULL; |
| } |
| |
| static struct damos_filter *damos_nth_ops_filter(int n, struct damos *s) |
| { |
| struct damos_filter *filter; |
| int i = 0; |
| |
| damos_for_each_ops_filter(filter, s) { |
| if (i++ == n) |
| return filter; |
| } |
| return NULL; |
| } |
| |
| static void damos_commit_filter_arg( |
| struct damos_filter *dst, struct damos_filter *src) |
| { |
| switch (dst->type) { |
| case DAMOS_FILTER_TYPE_MEMCG: |
| dst->memcg_id = src->memcg_id; |
| break; |
| case DAMOS_FILTER_TYPE_ADDR: |
| dst->addr_range = src->addr_range; |
| break; |
| case DAMOS_FILTER_TYPE_TARGET: |
| dst->target_idx = src->target_idx; |
| break; |
| case DAMOS_FILTER_TYPE_HUGEPAGE_SIZE: |
| dst->sz_range = src->sz_range; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void damos_commit_filter( |
| struct damos_filter *dst, struct damos_filter *src) |
| { |
| dst->type = src->type; |
| dst->matching = src->matching; |
| dst->allow = src->allow; |
| damos_commit_filter_arg(dst, src); |
| } |
| |
| static int damos_commit_core_filters(struct damos *dst, struct damos *src) |
| { |
| struct damos_filter *dst_filter, *next, *src_filter, *new_filter; |
| int i = 0, j = 0; |
| |
| damos_for_each_core_filter_safe(dst_filter, next, dst) { |
| src_filter = damos_nth_core_filter(i++, src); |
| if (src_filter) |
| damos_commit_filter(dst_filter, src_filter); |
| else |
| damos_destroy_filter(dst_filter); |
| } |
| |
| damos_for_each_core_filter_safe(src_filter, next, src) { |
| if (j++ < i) |
| continue; |
| |
| new_filter = damos_new_filter( |
| src_filter->type, src_filter->matching, |
| src_filter->allow); |
| if (!new_filter) |
| return -ENOMEM; |
| damos_commit_filter_arg(new_filter, src_filter); |
| damos_add_filter(dst, new_filter); |
| } |
| return 0; |
| } |
| |
| static int damos_commit_ops_filters(struct damos *dst, struct damos *src) |
| { |
| struct damos_filter *dst_filter, *next, *src_filter, *new_filter; |
| int i = 0, j = 0; |
| |
| damos_for_each_ops_filter_safe(dst_filter, next, dst) { |
| src_filter = damos_nth_ops_filter(i++, src); |
| if (src_filter) |
| damos_commit_filter(dst_filter, src_filter); |
| else |
| damos_destroy_filter(dst_filter); |
| } |
| |
| damos_for_each_ops_filter_safe(src_filter, next, src) { |
| if (j++ < i) |
| continue; |
| |
| new_filter = damos_new_filter( |
| src_filter->type, src_filter->matching, |
| src_filter->allow); |
| if (!new_filter) |
| return -ENOMEM; |
| damos_commit_filter_arg(new_filter, src_filter); |
| damos_add_filter(dst, new_filter); |
| } |
| return 0; |
| } |
| |
| /** |
| * damos_filters_default_reject() - decide whether to reject memory that didn't |
| * match with any given filter. |
| * @filters: Given DAMOS filters of a group. |
| */ |
| static bool damos_filters_default_reject(struct list_head *filters) |
| { |
| struct damos_filter *last_filter; |
| |
| if (list_empty(filters)) |
| return false; |
| last_filter = list_last_entry(filters, struct damos_filter, list); |
| return last_filter->allow; |
| } |
| |
| static void damos_set_filters_default_reject(struct damos *s) |
| { |
| if (!list_empty(&s->ops_filters)) |
| s->core_filters_default_reject = false; |
| else |
| s->core_filters_default_reject = |
| damos_filters_default_reject(&s->core_filters); |
| s->ops_filters_default_reject = |
| damos_filters_default_reject(&s->ops_filters); |
| } |
| |
| static bool damon_valid_probe_params(struct damon_ctx *ctx) |
| { |
| unsigned long sample_interval; |
| unsigned char max_probe_hits; |
| struct damon_probe *probe; |
| unsigned int wsum, wsum_to_add; |
| |
| if (!damon_has_probe_weights(ctx)) |
| return true; |
| |
| sample_interval = ctx->attrs.sample_interval ? : 1; |
| if (ctx->attrs.aggr_interval / sample_interval > U8_MAX) |
| return false; |
| |
| /* invalid if probe hits weighted sum can overflow */ |
| max_probe_hits = damon_nr_samples_per_aggr(&ctx->attrs); |
| wsum = 0; |
| damon_for_each_probe(probe, ctx) { |
| if (probe->weight > UINT_MAX / max_probe_hits) |
| return false; |
| wsum_to_add = probe->weight * max_probe_hits; |
| if (UINT_MAX - wsum < wsum_to_add) |
| return false; |
| wsum += wsum_to_add; |
| } |
| return true; |
| } |
| |
| /* |
| * damos_commit_dests() - Copy migration destinations from @src to @dst. |
| * @dst: Destination structure to update. |
| * @src: Source structure to copy from. |
| * |
| * If the number of destinations has changed, the old arrays in @dst are freed |
| * and new ones are allocated. On success, @dst contains a full copy of |
| * @src's arrays and count. |
| * |
| * On allocation failure, @dst is left in a partially torn-down state: its |
| * arrays may be NULL and @nr_dests may not reflect the actual allocation |
| * sizes. The structure remains safe to deallocate via damon_destroy_scheme(), |
| * but callers must not reuse @dst for further commits — it should be |
| * discarded. |
| * |
| * Return: 0 on success, -ENOMEM on allocation failure. |
| */ |
| static int damos_commit_dests(struct damos_migrate_dests *dst, |
| struct damos_migrate_dests *src) |
| { |
| if (dst->nr_dests != src->nr_dests) { |
| kfree(dst->node_id_arr); |
| kfree(dst->weight_arr); |
| |
| dst->node_id_arr = kmalloc_array(src->nr_dests, |
| sizeof(*dst->node_id_arr), GFP_KERNEL); |
| if (!dst->node_id_arr) { |
| dst->weight_arr = NULL; |
| return -ENOMEM; |
| } |
| |
| dst->weight_arr = kmalloc_array(src->nr_dests, |
| sizeof(*dst->weight_arr), GFP_KERNEL); |
| if (!dst->weight_arr) { |
| /* ->node_id_arr will be freed by scheme destruction */ |
| return -ENOMEM; |
| } |
| } |
| |
| dst->nr_dests = src->nr_dests; |
| for (int i = 0; i < src->nr_dests; i++) { |
| dst->node_id_arr[i] = src->node_id_arr[i]; |
| dst->weight_arr[i] = src->weight_arr[i]; |
| } |
| |
| return 0; |
| } |
| |
| static int damos_commit_filters(struct damos *dst, struct damos *src) |
| { |
| int err; |
| |
| err = damos_commit_core_filters(dst, src); |
| if (err) |
| return err; |
| err = damos_commit_ops_filters(dst, src); |
| if (err) |
| return err; |
| damos_set_filters_default_reject(dst); |
| return 0; |
| } |
| |
| static struct damos *damon_nth_scheme(int n, struct damon_ctx *ctx) |
| { |
| struct damos *s; |
| int i = 0; |
| |
| damon_for_each_scheme(s, ctx) { |
| if (i++ == n) |
| return s; |
| } |
| return NULL; |
| } |
| |
| static int damos_commit(struct damos *dst, struct damos *src) |
| { |
| int err; |
| |
| dst->pattern = src->pattern; |
| dst->action = src->action; |
| dst->apply_interval_us = src->apply_interval_us; |
| |
| err = damos_commit_quota(&dst->quota, &src->quota); |
| if (err) |
| return err; |
| |
| dst->wmarks = src->wmarks; |
| dst->target_nid = src->target_nid; |
| |
| err = damos_commit_dests(&dst->migrate_dests, &src->migrate_dests); |
| if (err) |
| return err; |
| |
| err = damos_commit_filters(dst, src); |
| if (err) |
| return err; |
| |
| dst->max_nr_snapshots = src->max_nr_snapshots; |
| return 0; |
| } |
| |
| static int damon_commit_schemes(struct damon_ctx *dst, struct damon_ctx *src) |
| { |
| struct damos *dst_scheme, *next, *src_scheme, *new_scheme; |
| int i = 0, j = 0, err; |
| |
| damon_for_each_scheme_safe(dst_scheme, next, dst) { |
| src_scheme = damon_nth_scheme(i++, src); |
| if (src_scheme) { |
| err = damos_commit(dst_scheme, src_scheme); |
| if (err) |
| return err; |
| } else { |
| damon_destroy_scheme(dst_scheme); |
| } |
| } |
| |
| damon_for_each_scheme_safe(src_scheme, next, src) { |
| if (j++ < i) |
| continue; |
| new_scheme = damon_new_scheme(&src_scheme->pattern, |
| src_scheme->action, |
| src_scheme->apply_interval_us, |
| &src_scheme->quota, &src_scheme->wmarks, |
| NUMA_NO_NODE); |
| if (!new_scheme) |
| return -ENOMEM; |
| err = damos_commit(new_scheme, src_scheme); |
| if (err) { |
| damon_destroy_scheme(new_scheme); |
| return err; |
| } |
| damon_add_scheme(dst, new_scheme); |
| } |
| return 0; |
| } |
| |
| static struct damon_target *damon_nth_target(int n, struct damon_ctx *ctx) |
| { |
| struct damon_target *t; |
| int i = 0; |
| |
| damon_for_each_target(t, ctx) { |
| if (i++ == n) |
| return t; |
| } |
| return NULL; |
| } |
| |
| /* |
| * The caller should ensure the regions of @src are |
| * 1. valid (end >= src) and |
| * 2. sorted by starting address. |
| * |
| * If @src has no region, @dst keeps current regions. |
| */ |
| static int damon_commit_target_regions(struct damon_target *dst, |
| struct damon_target *src, unsigned long src_min_region_sz) |
| { |
| struct damon_region *src_region, *prev = NULL; |
| struct damon_addr_range *ranges; |
| int i = 0, err; |
| |
| damon_for_each_region(src_region, src) { |
| if (!prev || prev->ar.end != src_region->ar.start) |
| i++; |
| prev = src_region; |
| } |
| if (!i) |
| return 0; |
| |
| ranges = kvmalloc_objs(*ranges, i, GFP_KERNEL | __GFP_NOWARN); |
| if (!ranges) |
| return -ENOMEM; |
| prev = NULL; |
| i = 0; |
| damon_for_each_region(src_region, src) { |
| if (!prev) { |
| ranges[i].start = src_region->ar.start; |
| } else if (prev->ar.end != src_region->ar.start) { |
| ranges[i++].end = prev->ar.end; |
| ranges[i].start = src_region->ar.start; |
| } |
| prev = src_region; |
| } |
| ranges[i++].end = damon_last_region(src)->ar.end; |
| err = damon_set_regions(dst, ranges, i, src_min_region_sz); |
| kvfree(ranges); |
| return err; |
| } |
| |
| static int damon_commit_target( |
| struct damon_target *dst, bool dst_has_pid, |
| struct damon_target *src, bool src_has_pid, |
| unsigned long src_min_region_sz) |
| { |
| int err; |
| |
| err = damon_commit_target_regions(dst, src, src_min_region_sz); |
| if (err) |
| return err; |
| if (dst_has_pid) |
| put_pid(dst->pid); |
| if (src_has_pid) |
| get_pid(src->pid); |
| dst->pid = src->pid; |
| return 0; |
| } |
| |
| /* |
| * damon_revert_target_commits() - revert unsuccessful target commits. |
| * @dst: Commit destination context |
| * @failed: Commit failed destination target |
| * @src: Commit source context |
| * |
| * Revert target states that changed by damon_commit_target(), and cannot be |
| * cleaned up by the destination context's ops.cleanup_target(). |
| */ |
| static void damon_revert_target_commits(struct damon_ctx *dst, |
| struct damon_target *failed, struct damon_ctx *src) |
| { |
| struct damon_target *target; |
| |
| if (!damon_target_has_pid(src)) |
| return; |
| if (dst->ops.cleanup_target) |
| return; |
| damon_for_each_target(target, dst) { |
| if (target == failed) |
| return; |
| put_pid(target->pid); |
| } |
| } |
| |
| static int damon_commit_targets( |
| struct damon_ctx *dst, struct damon_ctx *src) |
| { |
| struct damon_target *dst_target, *next, *src_target, *new_target; |
| struct damon_target *failed; |
| int i = 0, j = 0, err; |
| |
| damon_for_each_target_safe(dst_target, next, dst) { |
| src_target = damon_nth_target(i++, src); |
| /* |
| * If src target is obsolete, do not commit the parameters to |
| * the dst target, and further remove the dst target. |
| */ |
| if (src_target && !src_target->obsolete) { |
| err = damon_commit_target( |
| dst_target, damon_target_has_pid(dst), |
| src_target, damon_target_has_pid(src), |
| src->min_region_sz); |
| if (err) { |
| failed = dst_target; |
| goto out; |
| } |
| } else { |
| struct damos *s; |
| |
| damon_destroy_target(dst_target, dst); |
| damon_for_each_scheme(s, dst) { |
| if (s->quota.charge_target_from == dst_target) { |
| s->quota.charge_target_from = NULL; |
| s->quota.charge_addr_from = 0; |
| } |
| } |
| } |
| } |
| |
| failed = NULL; |
| damon_for_each_target_safe(src_target, next, src) { |
| if (j++ < i) |
| continue; |
| /* target to remove has no matching dst */ |
| if (src_target->obsolete) { |
| err = -EINVAL; |
| goto out; |
| } |
| new_target = damon_new_target(); |
| if (!new_target) { |
| err = -ENOMEM; |
| goto out; |
| } |
| err = damon_commit_target(new_target, false, |
| src_target, damon_target_has_pid(src), |
| src->min_region_sz); |
| if (err) { |
| damon_destroy_target(new_target, NULL); |
| goto out; |
| } |
| damon_add_target(dst, new_target); |
| } |
| return 0; |
| |
| out: |
| damon_revert_target_commits(dst, failed, src); |
| return err; |
| } |
| |
| static void damon_commit_filter(struct damon_filter *dst, |
| struct damon_filter *src) |
| { |
| dst->type = src->type; |
| dst->matching = src->matching; |
| dst->allow = src->allow; |
| switch (dst->type) { |
| case DAMON_FILTER_TYPE_MEMCG: |
| dst->memcg_id = src->memcg_id; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static int damon_commit_filters(struct damon_probe *dst, |
| struct damon_probe *src) |
| { |
| struct damon_filter *dst_filter, *next, *src_filter, *new_filter; |
| int i = 0, j = 0; |
| |
| damon_for_each_filter_safe(dst_filter, next, dst) { |
| src_filter = damon_nth_filter(i++, src); |
| if (src_filter) |
| damon_commit_filter(dst_filter, src_filter); |
| else |
| damon_destroy_filter(dst_filter); |
| } |
| |
| damon_for_each_filter_safe(src_filter, next, src) { |
| if (j++ < i) |
| continue; |
| |
| new_filter = damon_new_filter(src_filter->type, |
| src_filter->matching, src_filter->allow); |
| if (!new_filter) |
| return -ENOMEM; |
| switch (src_filter->type) { |
| case DAMON_FILTER_TYPE_MEMCG: |
| new_filter->memcg_id = src_filter->memcg_id; |
| break; |
| default: |
| break; |
| } |
| damon_add_filter(dst, new_filter); |
| } |
| return 0; |
| } |
| |
| static int damon_commit_probes(struct damon_ctx *dst, struct damon_ctx *src) |
| { |
| struct damon_probe *dst_probe, *next, *src_probe, *new_probe; |
| int i = 0, j = 0, err; |
| |
| damon_for_each_probe_safe(dst_probe, next, dst) { |
| src_probe = damon_nth_probe(i++, src); |
| if (src_probe) { |
| dst_probe->weight = src_probe->weight; |
| err = damon_commit_filters(dst_probe, src_probe); |
| if (err) |
| return err; |
| } else { |
| damon_destroy_probe(dst_probe); |
| } |
| } |
| |
| damon_for_each_probe_safe(src_probe, next, src) { |
| if (j++ < i) |
| continue; |
| |
| new_probe = damon_new_probe(); |
| if (!new_probe) |
| return -ENOMEM; |
| damon_add_probe(dst, new_probe); |
| new_probe->weight = src_probe->weight; |
| err = damon_commit_filters(new_probe, src_probe); |
| if (err) |
| return err; |
| } |
| return 0; |
| } |
| |
| static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) |
| { |
| int err; |
| struct damos *scheme; |
| struct damos_quota_goal *goal; |
| |
| dst->maybe_corrupted = true; |
| if (!is_power_of_2(src->min_region_sz)) |
| return -EINVAL; |
| |
| /* node_eligible_mem_bp metric requires PADDR ops */ |
| if (src->ops.id != DAMON_OPS_PADDR) { |
| damon_for_each_scheme(scheme, src) { |
| struct damos_quota *quota = &scheme->quota; |
| |
| damos_for_each_quota_goal(goal, quota) { |
| if (goal->metric == |
| DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP) |
| return -EINVAL; |
| } |
| } |
| } |
| |
| if (!damon_valid_probe_params(src)) |
| return -EINVAL; |
| |
| err = damon_commit_schemes(dst, src); |
| if (err) |
| return err; |
| err = damon_commit_targets(dst, src); |
| if (err) |
| return err; |
| /* |
| * schemes and targets should be updated first, since |
| * 1. damon_set_attrs() updates monitoring results of targets and |
| * next_apply_sis of schemes, and |
| * 2. ops update should be done after pid handling is done (target |
| * committing require putting pids). |
| */ |
| if (!damon_attrs_equals(&dst->attrs, &src->attrs)) { |
| err = damon_set_attrs(dst, &src->attrs); |
| if (err) { |
| damon_revert_target_commits(dst, NULL, src); |
| return err; |
| } |
| } |
| dst->pause = src->pause; |
| dst->ops = src->ops; |
| err = damon_commit_probes(dst, src); |
| if (err) |
| return err; |
| dst->addr_unit = src->addr_unit; |
| dst->min_region_sz = src->min_region_sz; |
| |
| dst->maybe_corrupted = false; |
| return 0; |
| } |
| |
| static struct damon_ctx *damon_new_test_ctx(struct damon_ctx *dst) |
| { |
| struct damon_ctx *test_ctx; |
| int err; |
| |
| test_ctx = damon_new_ctx(); |
| if (!test_ctx) |
| return NULL; |
| err = __damon_commit_ctx(test_ctx, dst); |
| if (err) { |
| damon_destroy_ctx(test_ctx); |
| return NULL; |
| } |
| return test_ctx; |
| } |
| |
| /** |
| * damon_commit_ctx() - Commit parameters of a DAMON context to another. |
| * @dst: The commit destination DAMON context. |
| * @src: The commit source DAMON context. |
| * |
| * This function copies user-specified parameters from @src to @dst and update |
| * the internal status and results accordingly. Users should use this function |
| * for context-level parameters update of running context, instead of manual |
| * in-place updates. |
| * |
| * This function should be called from parameters-update safe context, like |
| * damon_call(). |
| */ |
| int damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src) |
| { |
| struct damon_ctx *test_ctx; |
| int err; |
| |
| test_ctx = damon_new_test_ctx(dst); |
| if (!test_ctx) |
| return -ENOMEM; |
| err = __damon_commit_ctx(test_ctx, src); |
| if (err) |
| goto out; |
| err = __damon_commit_ctx(dst, src); |
| out: |
| damon_destroy_ctx(test_ctx); |
| return err; |
| } |
| |
| /** |
| * damon_nr_running_ctxs() - Return number of currently running contexts. |
| */ |
| int damon_nr_running_ctxs(void) |
| { |
| int nr_ctxs; |
| |
| mutex_lock(&damon_lock); |
| nr_ctxs = nr_running_ctxs; |
| mutex_unlock(&damon_lock); |
| |
| return nr_ctxs; |
| } |
| |
| /* Returns the size upper limit for each monitoring region */ |
| static unsigned long damon_region_sz_limit(struct damon_ctx *ctx) |
| { |
| struct damon_target *t; |
| struct damon_region *r; |
| unsigned long sz = 0; |
| |
| damon_for_each_target(t, ctx) { |
| damon_for_each_region(r, t) |
| sz += damon_sz_region(r); |
| } |
| |
| if (ctx->attrs.min_nr_regions) |
| sz /= ctx->attrs.min_nr_regions; |
| if (sz < ctx->min_region_sz) |
| sz = ctx->min_region_sz; |
| |
| return sz; |
| } |
| |
| static int damon_split_region_at(struct damon_target *t, |
| struct damon_region *r, unsigned long sz_r); |
| |
| /* |
| * damon_apply_min_nr_regions() - Make effect of min_nr_regions parameter. |
| * @ctx: monitoring context. |
| * |
| * This function implement min_nr_regions (minimum number of damon_region |
| * objects in the given monitoring context) behavior. It first calculates |
| * maximum size of each region for enforcing the min_nr_regions as total size |
| * of the regions divided by the min_nr_regions. After that, this function |
| * splits regions to ensure all regions are equal to or smaller than the size |
| * limit. Finally, this function returns the maximum size limit. |
| * |
| * Returns: maximum size of each region for convincing min_nr_regions. |
| */ |
| static unsigned long damon_apply_min_nr_regions(struct damon_ctx *ctx) |
| { |
| unsigned long max_region_sz = damon_region_sz_limit(ctx); |
| struct damon_target *t; |
| struct damon_region *r, *next; |
| |
| max_region_sz = ALIGN(max_region_sz, ctx->min_region_sz); |
| damon_for_each_target(t, ctx) { |
| damon_for_each_region_safe(r, next, t) { |
| while (damon_sz_region(r) > max_region_sz) { |
| if (damon_split_region_at(t, r, max_region_sz)) |
| goto out; |
| r = damon_next_region(r); |
| } |
| } |
| } |
| out: |
| return max_region_sz; |
| } |
| |
| static int kdamond_fn(void *data); |
| |
| /* |
| * __damon_start() - Starts monitoring with given context. |
| * @ctx: monitoring context |
| * |
| * This function should be called while damon_lock is hold. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| static int __damon_start(struct damon_ctx *ctx) |
| { |
| int err = -EBUSY; |
| |
| mutex_lock(&ctx->kdamond_lock); |
| if (!ctx->kdamond) { |
| err = 0; |
| reinit_completion(&ctx->kdamond_started); |
| ctx->kdamond = kthread_run(kdamond_fn, ctx, "kdamond.%d", |
| nr_running_ctxs); |
| if (IS_ERR(ctx->kdamond)) { |
| err = PTR_ERR(ctx->kdamond); |
| ctx->kdamond = NULL; |
| } else { |
| wait_for_completion(&ctx->kdamond_started); |
| } |
| } |
| mutex_unlock(&ctx->kdamond_lock); |
| |
| return err; |
| } |
| |
| static int __damon_commit_ctx(struct damon_ctx *dst, struct damon_ctx *src); |
| |
| /** |
| * damon_start() - Starts the monitorings for a given group of contexts. |
| * @ctxs: an array of the pointers for contexts to start monitoring |
| * @nr_ctxs: size of @ctxs |
| * @exclusive: exclusiveness of this contexts group |
| * |
| * This function starts a group of monitoring threads for a group of monitoring |
| * contexts. One thread per each context is created and run in parallel. The |
| * caller should handle synchronization between the threads by itself. If |
| * @exclusive is true and a group of threads that created by other |
| * 'damon_start()' call is currently running, this function does nothing but |
| * returns -EBUSY. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_start(struct damon_ctx **ctxs, int nr_ctxs, bool exclusive) |
| { |
| int i; |
| int err = 0; |
| |
| for (i = 0; i < nr_ctxs; i++) { |
| struct damon_ctx *test_ctx; |
| |
| test_ctx = damon_new_ctx(); |
| if (!test_ctx) |
| return -ENOMEM; |
| |
| err = __damon_commit_ctx(test_ctx, ctxs[i]); |
| damon_destroy_ctx(test_ctx); |
| if (err) |
| return err; |
| } |
| |
| mutex_lock(&damon_lock); |
| if ((exclusive && nr_running_ctxs) || |
| (!exclusive && running_exclusive_ctxs)) { |
| mutex_unlock(&damon_lock); |
| return -EBUSY; |
| } |
| |
| for (i = 0; i < nr_ctxs; i++) { |
| err = __damon_start(ctxs[i]); |
| if (err) |
| break; |
| nr_running_ctxs++; |
| } |
| if (exclusive && nr_running_ctxs) |
| running_exclusive_ctxs = true; |
| mutex_unlock(&damon_lock); |
| |
| if (i != nr_ctxs) |
| damon_stop(ctxs, i); |
| |
| return err; |
| } |
| |
| /* |
| * __damon_stop() - Stops monitoring of a given context. |
| * @ctx: monitoring context |
| */ |
| static void __damon_stop(struct damon_ctx *ctx) |
| { |
| struct task_struct *tsk; |
| |
| mutex_lock(&ctx->kdamond_lock); |
| tsk = ctx->kdamond; |
| if (tsk) { |
| get_task_struct(tsk); |
| mutex_unlock(&ctx->kdamond_lock); |
| kthread_stop_put(tsk); |
| return; |
| } |
| mutex_unlock(&ctx->kdamond_lock); |
| } |
| |
| /** |
| * damon_stop() - Stops the monitorings for a given group of contexts. |
| * @ctxs: an array of the pointers for contexts to stop monitoring |
| * @nr_ctxs: size of @ctxs |
| */ |
| void damon_stop(struct damon_ctx **ctxs, int nr_ctxs) |
| { |
| int i; |
| |
| for (i = 0; i < nr_ctxs; i++) |
| /* nr_running_ctxs is decremented in kdamond_fn */ |
| __damon_stop(ctxs[i]); |
| } |
| |
| /** |
| * damon_is_running() - Returns if a given DAMON context is running. |
| * @ctx: The DAMON context to see if running. |
| * |
| * Return: true if @ctx is running, false otherwise. |
| */ |
| bool damon_is_running(struct damon_ctx *ctx) |
| { |
| bool running; |
| |
| mutex_lock(&ctx->kdamond_lock); |
| running = ctx->kdamond != NULL; |
| mutex_unlock(&ctx->kdamond_lock); |
| return running; |
| } |
| |
| /** |
| * damon_kdamond_pid() - Return pid of a given DAMON context's worker thread. |
| * @ctx: The DAMON context of the question. |
| * |
| * Return: pid if @ctx is running, negative error code otherwise. |
| */ |
| int damon_kdamond_pid(struct damon_ctx *ctx) |
| { |
| int pid = -EINVAL; |
| |
| mutex_lock(&ctx->kdamond_lock); |
| if (ctx->kdamond) |
| pid = ctx->kdamond->pid; |
| mutex_unlock(&ctx->kdamond_lock); |
| return pid; |
| } |
| |
| /** |
| * damon_call() - Invoke a given function on DAMON worker thread (kdamond). |
| * @ctx: DAMON context to call the function for. |
| * @control: Control variable of the call request. |
| * |
| * Ask DAMON worker thread (kdamond) of @ctx to call a function with an |
| * argument data that respectively passed via &damon_call_control->fn and |
| * &damon_call_control->data of @control. If &damon_call_control->repeat of |
| * @control is unset, further wait until the kdamond finishes handling of the |
| * request. Otherwise, return as soon as the request is made. |
| * |
| * The kdamond executes the function with the argument in the main loop, just |
| * after a sampling of the iteration is finished. The function can hence |
| * safely access the internal data of the &struct damon_ctx without additional |
| * synchronization. The return value of the function will be saved in |
| * &damon_call_control->return_code. |
| * |
| * Note that this function should be called only after damon_start() with the |
| * @ctx has succeeded. Otherwise, this function could fall into an indefinite |
| * wait. |
| * |
| * When this function is failed, the @ctx is guaranteed to be stopped. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_call(struct damon_ctx *ctx, struct damon_call_control *control) |
| { |
| if (!control->repeat) |
| init_completion(&control->completion); |
| control->canceled = false; |
| INIT_LIST_HEAD(&control->list); |
| |
| mutex_lock(&ctx->call_controls_lock); |
| if (ctx->call_controls_obsolete) { |
| mutex_unlock(&ctx->call_controls_lock); |
| goto canceled; |
| } |
| list_add_tail(&control->list, &ctx->call_controls); |
| mutex_unlock(&ctx->call_controls_lock); |
| if (control->repeat) |
| return 0; |
| wait_for_completion(&control->completion); |
| if (control->canceled) |
| goto canceled; |
| return 0; |
| |
| canceled: |
| while (damon_is_running(ctx)) |
| schedule_timeout_idle(msecs_to_jiffies(100)); |
| return -ECANCELED; |
| |
| } |
| |
| /** |
| * damos_walk() - Invoke a given functions while DAMOS walk regions. |
| * @ctx: DAMON context to call the functions for. |
| * @control: Control variable of the walk request. |
| * |
| * Ask DAMON worker thread (kdamond) of @ctx to call a function for each region |
| * that the kdamond will apply DAMOS action to, and wait until the kdamond |
| * finishes handling of the request. |
| * |
| * The kdamond executes the given function in the main loop, for each region |
| * just after it applied any DAMOS actions of @ctx to it. The invocation is |
| * made only within one &damos->apply_interval_us since damos_walk() |
| * invocation, for each scheme. The given callback function can hence safely |
| * access the internal data of &struct damon_ctx and &struct damon_region that |
| * each of the scheme will apply the action for next interval, without |
| * additional synchronizations against the kdamond. If every scheme of @ctx |
| * passed at least one &damos->apply_interval_us, kdamond marks the request as |
| * completed so that damos_walk() can wakeup and return. |
| * |
| * Note that this function should be called only after damon_start() with the |
| * @ctx has succeeded. Otherwise, this function could fall into an indefinite |
| * wait. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damos_walk(struct damon_ctx *ctx, struct damos_walk_control *control) |
| { |
| init_completion(&control->completion); |
| control->canceled = false; |
| mutex_lock(&ctx->walk_control_lock); |
| if (ctx->walk_control_obsolete) { |
| mutex_unlock(&ctx->walk_control_lock); |
| return -ECANCELED; |
| } |
| if (ctx->walk_control) { |
| mutex_unlock(&ctx->walk_control_lock); |
| return -EBUSY; |
| } |
| ctx->walk_control = control; |
| mutex_unlock(&ctx->walk_control_lock); |
| wait_for_completion(&control->completion); |
| if (control->canceled) |
| return -ECANCELED; |
| return 0; |
| } |
| |
| /* |
| * Reset the aggregated monitoring results ('nr_accesses' of each region). |
| */ |
| static void kdamond_reset_aggregated(struct damon_ctx *c) |
| { |
| struct damon_target *t; |
| unsigned int ti = 0; /* target's index */ |
| unsigned int nr_probes = 0; |
| struct damon_probe *probe; |
| |
| if (trace_damon_region_aggregated_enabled()) { |
| damon_for_each_probe(probe, c) |
| nr_probes++; |
| } |
| |
| damon_for_each_target(t, c) { |
| struct damon_region *r; |
| |
| damon_for_each_region(r, t) { |
| int i; |
| |
| trace_damon_aggregated(ti, r, damon_nr_regions(t)); |
| trace_damon_region_aggregated(ti, r, |
| damon_nr_regions(t), nr_probes); |
| r->last_nr_accesses = r->nr_accesses; |
| r->nr_accesses = 0; |
| for (i = 0; i < DAMON_MAX_PROBES; i++) { |
| r->last_probe_hits[i] = r->probe_hits[i]; |
| r->probe_hits[i] = 0; |
| } |
| } |
| ti++; |
| } |
| } |
| |
| static unsigned long damon_get_intervals_score(struct damon_ctx *c) |
| { |
| struct damon_target *t; |
| struct damon_region *r; |
| unsigned long sz_region, max_access_events = 0, access_events = 0; |
| unsigned long target_access_events; |
| unsigned long goal_bp = c->attrs.intervals_goal.access_bp; |
| |
| damon_for_each_target(t, c) { |
| damon_for_each_region(r, t) { |
| sz_region = damon_sz_region(r); |
| max_access_events += sz_region * c->attrs.aggr_samples; |
| access_events += sz_region * r->nr_accesses; |
| } |
| } |
| target_access_events = max_access_events * goal_bp / 10000; |
| target_access_events = target_access_events ? : 1; |
| return mult_frac(access_events, 10000, target_access_events); |
| } |
| |
| static unsigned long damon_feed_loop_next_input(unsigned long last_input, |
| unsigned long score); |
| |
| static unsigned long damon_get_intervals_adaptation_bp(struct damon_ctx *c) |
| { |
| unsigned long score_bp, adaptation_bp; |
| |
| score_bp = damon_get_intervals_score(c); |
| adaptation_bp = damon_feed_loop_next_input(100000000, score_bp) / |
| 10000; |
| /* |
| * adaptation_bp ranges from 1 to 20,000. Avoid too rapid reduction of |
| * the intervals by rescaling [1,10,000] to [5000, 10,000]. |
| */ |
| if (adaptation_bp <= 10000) |
| adaptation_bp = 5000 + adaptation_bp / 2; |
| return adaptation_bp; |
| } |
| |
| static noinline_for_stack void kdamond_tune_intervals(struct damon_ctx *c) |
| { |
| unsigned long adaptation_bp; |
| struct damon_attrs new_attrs; |
| struct damon_intervals_goal *goal; |
| |
| adaptation_bp = damon_get_intervals_adaptation_bp(c); |
| if (adaptation_bp == 10000) |
| return; |
| |
| new_attrs = c->attrs; |
| goal = &c->attrs.intervals_goal; |
| new_attrs.sample_interval = min(goal->max_sample_us, |
| c->attrs.sample_interval * adaptation_bp / 10000); |
| new_attrs.sample_interval = max(goal->min_sample_us, |
| new_attrs.sample_interval); |
| new_attrs.aggr_interval = new_attrs.sample_interval * |
| c->attrs.aggr_samples; |
| trace_damon_monitor_intervals_tune(new_attrs.sample_interval); |
| damon_set_attrs(c, &new_attrs); |
| } |
| |
| static bool __damos_valid_target(struct damon_region *r, struct damos *s, |
| struct damon_ctx *c) |
| { |
| unsigned long sz; |
| unsigned int nr_accesses = damon_nr_accesses_mvsum(r, c); |
| |
| sz = damon_sz_region(r); |
| return s->pattern.min_sz_region <= sz && |
| sz <= s->pattern.max_sz_region && |
| s->pattern.min_nr_accesses <= nr_accesses && |
| nr_accesses <= s->pattern.max_nr_accesses && |
| s->pattern.min_age_region <= r->age && |
| r->age <= s->pattern.max_age_region; |
| } |
| |
| /* |
| * damos_quota_is_set() - Return if the given quota is actually set. |
| * @quota: The quota to check. |
| * |
| * Returns true if the quota is set, false otherwise. |
| */ |
| static bool damos_quota_is_set(struct damos_quota *quota) |
| { |
| return quota->esz || quota->sz || quota->ms || |
| !damos_quota_goals_empty(quota); |
| } |
| |
| static bool damos_valid_target(struct damon_ctx *c, struct damon_region *r, |
| struct damos *s) |
| { |
| bool ret = __damos_valid_target(r, s, c); |
| |
| if (!ret || !damos_quota_is_set(&s->quota) || !c->ops.get_scheme_score) |
| return ret; |
| |
| return c->ops.get_scheme_score(c, r, s) >= s->quota.min_score; |
| } |
| |
| /* |
| * damos_skip_charged_region() - Check if the given region or starting part of |
| * it is already charged for the DAMOS quota. |
| * @t: The target of the region. |
| * @rp: The pointer to the region. |
| * @s: The scheme to be applied. |
| * @min_region_sz: minimum region size. |
| * |
| * If a quota of a scheme has exceeded in a quota charge window, the scheme's |
| * action would applied to only a part of the target access pattern fulfilling |
| * regions. To avoid applying the scheme action to only already applied |
| * regions, DAMON skips applying the scheme action to the regions that charged |
| * in the previous charge window. |
| * |
| * This function checks if a given region should be skipped or not for the |
| * reason. If only the starting part of the region has previously charged, |
| * this function splits the region into two so that the second one covers the |
| * area that not charged in the previous charge widnow, and return true. The |
| * caller can see the second one on the next iteration of the region walk. |
| * Note that this means the caller should use damon_for_each_region() instead |
| * of damon_for_each_region_safe(). If damon_for_each_region_safe() is used, |
| * the second region will just be ignored. |
| * |
| * Return: true if the region should be skipped, false otherwise. |
| */ |
| static bool damos_skip_charged_region(struct damon_target *t, |
| struct damon_region *r, struct damos *s, |
| unsigned long min_region_sz) |
| { |
| struct damos_quota *quota = &s->quota; |
| unsigned long sz_to_skip; |
| |
| /* Skip previously charged regions */ |
| if (quota->charge_target_from) { |
| if (t != quota->charge_target_from) |
| return true; |
| if (r == damon_last_region(t)) { |
| quota->charge_target_from = NULL; |
| quota->charge_addr_from = 0; |
| return true; |
| } |
| if (quota->charge_addr_from && |
| r->ar.end <= quota->charge_addr_from) |
| return true; |
| |
| if (quota->charge_addr_from && r->ar.start < |
| quota->charge_addr_from) { |
| sz_to_skip = ALIGN_DOWN(quota->charge_addr_from - |
| r->ar.start, min_region_sz); |
| if (!sz_to_skip) { |
| if (damon_sz_region(r) <= min_region_sz) |
| return true; |
| sz_to_skip = min_region_sz; |
| } |
| damon_split_region_at(t, r, sz_to_skip); |
| return true; |
| } |
| quota->charge_target_from = NULL; |
| quota->charge_addr_from = 0; |
| } |
| return false; |
| } |
| |
| static void damos_update_stat(struct damos *s, |
| unsigned long sz_tried, unsigned long sz_applied, |
| unsigned long sz_ops_filter_passed) |
| { |
| s->stat.nr_tried++; |
| s->stat.sz_tried += sz_tried; |
| if (sz_applied) |
| s->stat.nr_applied++; |
| s->stat.sz_applied += sz_applied; |
| s->stat.sz_ops_filter_passed += sz_ops_filter_passed; |
| } |
| |
| static bool damos_filter_match(struct damon_ctx *ctx, struct damon_target *t, |
| struct damon_region *r, struct damos_filter *filter, |
| unsigned long min_region_sz) |
| { |
| bool matched = false; |
| struct damon_target *ti; |
| int target_idx = 0; |
| unsigned long start, end; |
| |
| switch (filter->type) { |
| case DAMOS_FILTER_TYPE_TARGET: |
| damon_for_each_target(ti, ctx) { |
| if (ti == t) |
| break; |
| target_idx++; |
| } |
| matched = target_idx == filter->target_idx; |
| break; |
| case DAMOS_FILTER_TYPE_ADDR: |
| start = ALIGN_DOWN(filter->addr_range.start, min_region_sz); |
| end = ALIGN_DOWN(filter->addr_range.end, min_region_sz); |
| |
| /* inside the range */ |
| if (start <= r->ar.start && r->ar.end <= end) { |
| matched = true; |
| break; |
| } |
| /* outside of the range */ |
| if (r->ar.end <= start || end <= r->ar.start) { |
| matched = false; |
| break; |
| } |
| /* start before the range and overlap */ |
| if (r->ar.start < start) { |
| damon_split_region_at(t, r, start - r->ar.start); |
| matched = false; |
| break; |
| } |
| /* start inside the range */ |
| damon_split_region_at(t, r, end - r->ar.start); |
| matched = true; |
| break; |
| default: |
| return false; |
| } |
| |
| return matched == filter->matching; |
| } |
| |
| static bool damos_core_filter_out(struct damon_ctx *ctx, struct damon_target *t, |
| struct damon_region *r, struct damos *s) |
| { |
| struct damos_filter *filter; |
| |
| s->core_filters_allowed = false; |
| damos_for_each_core_filter(filter, s) { |
| if (damos_filter_match(ctx, t, r, filter, ctx->min_region_sz)) { |
| if (filter->allow) |
| s->core_filters_allowed = true; |
| return !filter->allow; |
| } |
| } |
| return s->core_filters_default_reject; |
| } |
| |
| /* |
| * damos_walk_call_walk() - Call &damos_walk_control->walk_fn. |
| * @ctx: The context of &damon_ctx->walk_control. |
| * @t: The monitoring target of @r that @s will be applied. |
| * @r: The region of @t that @s will be applied. |
| * @s: The scheme of @ctx that will be applied to @r. |
| * |
| * This function is called from kdamond whenever it asked the operation set to |
| * apply a DAMOS scheme action to a region. If a DAMOS walk request is |
| * installed by damos_walk() and not yet uninstalled, invoke it. |
| */ |
| static void damos_walk_call_walk(struct damon_ctx *ctx, struct damon_target *t, |
| struct damon_region *r, struct damos *s, |
| unsigned long sz_filter_passed) |
| { |
| struct damos_walk_control *control; |
| |
| if (s->walk_completed) |
| return; |
| |
| control = ctx->walk_control; |
| if (!control) |
| return; |
| |
| control->walk_fn(control->data, ctx, t, r, s, sz_filter_passed); |
| } |
| |
| /* |
| * damos_walk_complete() - Complete DAMOS walk request if all walks are done. |
| * @ctx: The context of &damon_ctx->walk_control. |
| * @s: A scheme of @ctx that all walks are now done. |
| * |
| * This function is called when kdamond finished applying the action of a DAMOS |
| * scheme to all regions that eligible for the given &damos->apply_interval_us. |
| * If every scheme of @ctx including @s now finished walking for at least one |
| * &damos->apply_interval_us, this function makrs the handling of the given |
| * DAMOS walk request is done, so that damos_walk() can wake up and return. |
| */ |
| static void damos_walk_complete(struct damon_ctx *ctx, struct damos *s) |
| { |
| struct damos *siter; |
| struct damos_walk_control *control; |
| |
| control = ctx->walk_control; |
| if (!control) |
| return; |
| |
| s->walk_completed = true; |
| /* if all schemes completed, signal completion to walker */ |
| damon_for_each_scheme(siter, ctx) { |
| if (!siter->walk_completed) |
| return; |
| } |
| damon_for_each_scheme(siter, ctx) |
| siter->walk_completed = false; |
| |
| complete(&control->completion); |
| ctx->walk_control = NULL; |
| } |
| |
| /* |
| * damos_walk_cancel() - Cancel the current DAMOS walk request. |
| * @ctx: The context of &damon_ctx->walk_control. |
| * |
| * This function is called when @ctx is deactivated by DAMOS watermarks, DAMOS |
| * walk is requested but there is no DAMOS scheme to walk for, or the kdamond |
| * is already out of the main loop and therefore gonna be terminated, and hence |
| * cannot continue the walks. This function therefore marks the walk request |
| * as canceled, so that damos_walk() can wake up and return. |
| */ |
| static void damos_walk_cancel(struct damon_ctx *ctx) |
| { |
| struct damos_walk_control *control; |
| |
| mutex_lock(&ctx->walk_control_lock); |
| control = ctx->walk_control; |
| mutex_unlock(&ctx->walk_control_lock); |
| |
| if (!control) |
| return; |
| control->canceled = true; |
| complete(&control->completion); |
| mutex_lock(&ctx->walk_control_lock); |
| ctx->walk_control = NULL; |
| mutex_unlock(&ctx->walk_control_lock); |
| } |
| |
| static void damos_charge_quota(struct damos_quota *quota, |
| unsigned long sz_region, unsigned long sz_applied) |
| { |
| /* |
| * sz_applied could be bigger than sz_region, depending on ops |
| * implementation of the action, e.g., damos_pa_pageout(). Charge only |
| * the region size in the case. |
| */ |
| if (!quota->fail_charge_denom || sz_applied > sz_region) |
| quota->charged_sz += sz_region; |
| else |
| quota->charged_sz += sz_applied + mult_frac( |
| (sz_region - sz_applied), |
| quota->fail_charge_num, |
| quota->fail_charge_denom); |
| } |
| |
| static bool damos_quota_is_full(struct damos_quota *quota, |
| unsigned long min_region_sz) |
| { |
| if (!damos_quota_is_set(quota)) |
| return false; |
| if (quota->charged_sz >= quota->esz) |
| return true; |
| /* |
| * DAMOS action is applied per region, so <min_region_sz remaining |
| * quota means the quota is effectively full. |
| */ |
| return quota->esz - quota->charged_sz < min_region_sz; |
| } |
| |
| static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, |
| struct damon_region *r, struct damos *s) |
| { |
| struct damos_quota *quota = &s->quota; |
| unsigned long sz = damon_sz_region(r); |
| struct timespec64 begin, end; |
| unsigned long sz_applied = 0; |
| unsigned long sz_ops_filter_passed = 0; |
| /* |
| * We plan to support multiple context per kdamond, as DAMON sysfs |
| * implies with 'nr_contexts' file. Nevertheless, only single context |
| * per kdamond is supported for now. So, we can simply use '0' context |
| * index here. |
| */ |
| unsigned int cidx = 0; |
| struct damos *siter; /* schemes iterator */ |
| unsigned int sidx = 0; |
| struct damon_target *titer; /* targets iterator */ |
| unsigned int tidx = 0, nr_accesses = 0; |
| bool do_trace = false; |
| |
| /* get indices for trace_damos_before_apply() */ |
| if (trace_damos_before_apply_enabled()) { |
| damon_for_each_scheme(siter, c) { |
| if (siter == s) |
| break; |
| sidx++; |
| } |
| damon_for_each_target(titer, c) { |
| if (titer == t) |
| break; |
| tidx++; |
| } |
| nr_accesses = damon_nr_accesses_mvsum(r, c); |
| do_trace = true; |
| } |
| |
| if (c->ops.apply_scheme) { |
| if (damos_quota_is_set(quota) && |
| quota->charged_sz + sz > quota->esz) { |
| sz = ALIGN_DOWN(quota->esz - quota->charged_sz, |
| c->min_region_sz); |
| if (!sz) |
| goto update_stat; |
| damon_split_region_at(t, r, sz); |
| } |
| if (damos_core_filter_out(c, t, r, s)) |
| return; |
| ktime_get_coarse_ts64(&begin); |
| trace_damos_before_apply(cidx, sidx, tidx, r, nr_accesses, |
| damon_nr_regions(t), do_trace); |
| sz_applied = c->ops.apply_scheme(c, t, r, s, |
| &sz_ops_filter_passed); |
| damos_walk_call_walk(c, t, r, s, sz_ops_filter_passed); |
| ktime_get_coarse_ts64(&end); |
| quota->total_charged_ns += timespec64_to_ns(&end) - |
| timespec64_to_ns(&begin); |
| damos_charge_quota(quota, sz, sz_applied); |
| if (damos_quota_is_full(quota, c->min_region_sz)) { |
| quota->charge_target_from = t; |
| quota->charge_addr_from = r->ar.end; |
| } |
| } |
| if (s->action != DAMOS_STAT) |
| r->age = 0; |
| |
| update_stat: |
| damos_update_stat(s, sz, sz_applied, sz_ops_filter_passed); |
| } |
| |
| static void damon_do_apply_schemes(struct damon_ctx *c, |
| struct damon_target *t, |
| struct damon_region *r) |
| { |
| struct damos *s; |
| |
| damon_for_each_scheme(s, c) { |
| struct damos_quota *quota = &s->quota; |
| |
| if (time_before(c->passed_sample_intervals, s->next_apply_sis)) |
| continue; |
| |
| if (!s->wmarks.activated) |
| continue; |
| |
| /* Check the quota */ |
| if (damos_quota_is_full(quota, c->min_region_sz)) |
| continue; |
| |
| if (damos_skip_charged_region(t, r, s, c->min_region_sz)) |
| continue; |
| |
| if (s->max_nr_snapshots && |
| s->max_nr_snapshots <= s->stat.nr_snapshots) |
| continue; |
| |
| if (damos_valid_target(c, r, s)) |
| damos_apply_scheme(c, t, r, s); |
| |
| if (damon_is_last_region(r, t)) |
| s->stat.nr_snapshots++; |
| } |
| } |
| |
| /* |
| * damos_apply_target() - Apply DAMOS schemes to a given target. |
| * @c: monitoring context to apply its DAMOS schemes to.. |
| * @t: monitoring target to apply the schemes to. |
| * @max_region_sz: maximum region size for @c. |
| * |
| * This function could split regions for keeping the quota. To minimize |
| * overhead from the split operations increased number of regions, this |
| * function will also merge regions after the schemes applying attempt is done, |
| * for each region. The merge operation is made only when it doesn't lose the |
| * monitoring information and not violating @max_region_sz. |
| * |
| * Hence, after this function is called, the total number of regions could |
| * be increased or reduced. The increase could make max_nr_regions temporarily |
| * be violated, until the next per-aggregation interval regions merge operation |
| * is executed. The decrease will not violate min_nr_regions though, since it |
| * keeps @max_region_sz. |
| */ |
| static void damos_apply_target(struct damon_ctx *c, struct damon_target *t, |
| unsigned long max_region_sz) |
| { |
| struct damon_region *r; |
| |
| damon_for_each_region(r, t) { |
| struct damon_region *prev_r; |
| |
| damon_do_apply_schemes(c, t, r); |
| /* |
| * damon_do_apply_scheems() could split the region for the |
| * quota. Keeping the new slices is an overhead. Merge back |
| * the slices into the previous region if it doesn't lose any |
| * information and not violating the max_region_sz. |
| */ |
| if (damon_first_region(t) == r) |
| continue; |
| prev_r = damon_prev_region(r); |
| if (prev_r->ar.end != r->ar.start) |
| continue; |
| if (prev_r->age != r->age) |
| continue; |
| if (prev_r->last_nr_accesses != r->last_nr_accesses) |
| continue; |
| if (prev_r->nr_accesses != r->nr_accesses) |
| continue; |
| if (r->ar.end - prev_r->ar.start > max_region_sz) |
| continue; |
| prev_r->ar.end = r->ar.end; |
| damon_destroy_region(r, t); |
| r = prev_r; |
| } |
| } |
| |
| /* |
| * damon_feed_loop_next_input() - get next input to achieve a target score. |
| * @last_input The last input. |
| * @score Current score that made with @last_input. |
| * |
| * Calculate next input to achieve the target score, based on the last input |
| * and current score. Assuming the input and the score are positively |
| * proportional, calculate how much compensation should be added to or |
| * subtracted from the last input as a proportion of the last input. Avoid |
| * next input always being zero by setting it non-zero always. In short form |
| * (assuming support of float and signed calculations), the algorithm is as |
| * below. |
| * |
| * next_input = max(last_input * ((goal - current) / goal + 1), 1) |
| * |
| * For simple implementation, we assume the target score is always 10,000. The |
| * caller should adjust @score for this. |
| * |
| * Returns next input that assumed to achieve the target score. |
| */ |
| static unsigned long damon_feed_loop_next_input(unsigned long last_input, |
| unsigned long score) |
| { |
| const unsigned long goal = 10000; |
| /* Set minimum input as 10000 to avoid compensation be zero */ |
| const unsigned long min_input = 10000; |
| unsigned long score_goal_diff, compensation; |
| bool over_achieving = score > goal; |
| |
| if (score == goal) |
| return last_input; |
| if (score >= goal * 2) |
| return min_input; |
| |
| if (over_achieving) |
| score_goal_diff = score - goal; |
| else |
| score_goal_diff = goal - score; |
| |
| if (last_input < ULONG_MAX / score_goal_diff) |
| compensation = last_input * score_goal_diff / goal; |
| else |
| compensation = last_input / goal * score_goal_diff; |
| |
| if (over_achieving) |
| return max(last_input - compensation, min_input); |
| if (last_input < ULONG_MAX - compensation) |
| return last_input + compensation; |
| return ULONG_MAX; |
| } |
| |
| #ifdef CONFIG_PSI |
| |
| static u64 damos_get_some_mem_psi_total(void) |
| { |
| if (static_branch_likely(&psi_disabled)) |
| return 0; |
| return div_u64(psi_system.total[PSI_AVGS][PSI_MEM * 2], |
| NSEC_PER_USEC); |
| } |
| |
| #else /* CONFIG_PSI */ |
| |
| static inline u64 damos_get_some_mem_psi_total(void) |
| { |
| return 0; |
| } |
| |
| #endif /* CONFIG_PSI */ |
| |
| #ifdef CONFIG_NUMA |
| static bool invalid_mem_node(int nid) |
| { |
| return nid < 0 || nid >= MAX_NUMNODES || !node_state(nid, N_MEMORY); |
| } |
| |
| static __kernel_ulong_t damos_get_node_mem_bp( |
| struct damos_quota_goal *goal) |
| { |
| struct sysinfo i; |
| __kernel_ulong_t numerator; |
| |
| if (invalid_mem_node(goal->nid)) { |
| if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP) |
| return 0; |
| else /* DAMOS_QUOTA_NODE_MEM_FREE_BP */ |
| return 10000; |
| } |
| |
| si_meminfo_node(&i, goal->nid); |
| if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP) |
| numerator = i.totalram - i.freeram; |
| else /* DAMOS_QUOTA_NODE_MEM_FREE_BP */ |
| numerator = i.freeram; |
| return mult_frac(numerator, 10000, i.totalram); |
| } |
| |
| static unsigned long damos_get_node_memcg_used_bp( |
| struct damos_quota_goal *goal) |
| { |
| struct mem_cgroup *memcg; |
| struct lruvec *lruvec; |
| unsigned long used_pages, numerator; |
| struct sysinfo i; |
| |
| if (invalid_mem_node(goal->nid)) { |
| if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) |
| return 0; |
| else /* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */ |
| return 10000; |
| } |
| |
| memcg = mem_cgroup_get_from_id(goal->memcg_id); |
| if (!memcg) { |
| if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) |
| return 0; |
| else /* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */ |
| return 10000; |
| } |
| |
| mem_cgroup_flush_stats(memcg); |
| lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(goal->nid)); |
| used_pages = lruvec_page_state(lruvec, NR_ACTIVE_ANON); |
| used_pages += lruvec_page_state(lruvec, NR_INACTIVE_ANON); |
| used_pages += lruvec_page_state(lruvec, NR_ACTIVE_FILE); |
| used_pages += lruvec_page_state(lruvec, NR_INACTIVE_FILE); |
| |
| mem_cgroup_put(memcg); |
| |
| si_meminfo_node(&i, goal->nid); |
| if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) |
| numerator = used_pages; |
| else /* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */ |
| numerator = i.totalram - used_pages; |
| return mult_frac(numerator, 10000, i.totalram); |
| } |
| |
| #ifdef CONFIG_DAMON_PADDR |
| /* |
| * damos_calc_eligible_bytes() - Calculate raw eligible bytes per node. |
| * @c: The DAMON context. |
| * @s: The scheme. |
| * @nid: The target NUMA node id. |
| * @total: Output for total eligible bytes across all nodes. |
| * |
| * Iterates through each folio in eligible regions to accurately determine |
| * which node the memory resides on. Returns eligible bytes on the specified |
| * node and sets *total to the sum across all nodes. |
| * |
| * Note: This function requires damon_get_folio() from ops-common.c, which is |
| * only available when CONFIG_DAMON_PADDR is enabled. It also requires the |
| * context to be using PADDR operations for meaningful results. |
| */ |
| static phys_addr_t damos_calc_eligible_bytes(struct damon_ctx *c, |
| struct damos *s, int nid, phys_addr_t *total) |
| { |
| struct damon_target *t; |
| struct damon_region *r; |
| phys_addr_t total_eligible = 0; |
| phys_addr_t node_eligible = 0; |
| |
| damon_for_each_target(t, c) { |
| damon_for_each_region(r, t) { |
| phys_addr_t addr, end_addr; |
| |
| if (!__damos_valid_target(r, s, c)) |
| continue; |
| |
| /* Convert from core address units to physical bytes */ |
| addr = (phys_addr_t)r->ar.start * c->addr_unit; |
| end_addr = (phys_addr_t)r->ar.end * c->addr_unit; |
| while (addr < end_addr) { |
| struct folio *folio; |
| phys_addr_t folio_start, folio_end; |
| phys_addr_t overlap_start, overlap_end; |
| phys_addr_t counted; |
| |
| folio = damon_get_folio(PHYS_PFN(addr)); |
| if (!folio) { |
| addr = PAGE_ALIGN_DOWN(addr + |
| PAGE_SIZE); |
| if (!addr) |
| break; |
| continue; |
| } |
| |
| /* |
| * Calculate exact overlap between the region |
| * [addr, end_addr) and the folio range. |
| * The folio may start before addr if addr is |
| * in the middle of a large folio. |
| */ |
| folio_start = PFN_PHYS(folio_pfn(folio)); |
| folio_end = folio_start + folio_size(folio); |
| |
| overlap_start = max(addr, folio_start); |
| overlap_end = min(end_addr, folio_end); |
| |
| if (overlap_end > overlap_start) { |
| counted = overlap_end - overlap_start; |
| total_eligible += counted; |
| if (folio_nid(folio) == nid) |
| node_eligible += counted; |
| } |
| |
| /* Advance past the entire folio */ |
| addr = folio_end; |
| folio_put(folio); |
| } |
| cond_resched(); |
| } |
| } |
| |
| *total = total_eligible; |
| return node_eligible; |
| } |
| |
| static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c, |
| struct damos *s, int nid) |
| { |
| phys_addr_t total_eligible = 0; |
| phys_addr_t node_eligible; |
| |
| if (c->ops.id != DAMON_OPS_PADDR) |
| return 0; |
| |
| if (nid < 0 || nid >= MAX_NUMNODES || !node_online(nid)) |
| return 0; |
| |
| node_eligible = damos_calc_eligible_bytes(c, s, nid, &total_eligible); |
| |
| if (!(unsigned long)total_eligible) |
| return 0; |
| |
| return mult_frac((unsigned long)node_eligible, 10000, |
| (unsigned long)total_eligible); |
| } |
| #else /* CONFIG_DAMON_PADDR */ |
| static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c, |
| struct damos *s, int nid) |
| { |
| return 0; |
| } |
| #endif /* CONFIG_DAMON_PADDR */ |
| #else /* CONFIG_NUMA */ |
| static __kernel_ulong_t damos_get_node_mem_bp( |
| struct damos_quota_goal *goal) |
| { |
| return 0; |
| } |
| |
| static unsigned long damos_get_node_memcg_used_bp( |
| struct damos_quota_goal *goal) |
| { |
| return 0; |
| } |
| |
| static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c, |
| struct damos *s, int nid) |
| { |
| return 0; |
| } |
| #endif /* CONFIG_NUMA */ |
| |
| /* |
| * Returns LRU-active or inactive memory to total LRU memory size ratio. |
| */ |
| static unsigned int damos_get_in_active_mem_bp(bool active_ratio) |
| { |
| unsigned long active, inactive, total; |
| |
| /* This should align with /proc/meminfo output */ |
| active = global_node_page_state(NR_LRU_BASE + LRU_ACTIVE_ANON) + |
| global_node_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE); |
| inactive = global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON) + |
| global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE); |
| total = active + inactive; |
| if (active_ratio) |
| return mult_frac(active, 10000, total); |
| return mult_frac(inactive, 10000, total); |
| } |
| |
| static void damos_set_quota_goal_current_value(struct damon_ctx *c, |
| struct damos *s, struct damos_quota_goal *goal) |
| { |
| u64 now_psi_total; |
| |
| switch (goal->metric) { |
| case DAMOS_QUOTA_USER_INPUT: |
| /* User should already set goal->current_value */ |
| break; |
| case DAMOS_QUOTA_SOME_MEM_PSI_US: |
| now_psi_total = damos_get_some_mem_psi_total(); |
| goal->current_value = now_psi_total - goal->last_psi_total; |
| goal->last_psi_total = now_psi_total; |
| break; |
| case DAMOS_QUOTA_NODE_MEM_USED_BP: |
| case DAMOS_QUOTA_NODE_MEM_FREE_BP: |
| goal->current_value = damos_get_node_mem_bp(goal); |
| break; |
| case DAMOS_QUOTA_NODE_MEMCG_USED_BP: |
| case DAMOS_QUOTA_NODE_MEMCG_FREE_BP: |
| goal->current_value = damos_get_node_memcg_used_bp(goal); |
| break; |
| case DAMOS_QUOTA_ACTIVE_MEM_BP: |
| case DAMOS_QUOTA_INACTIVE_MEM_BP: |
| goal->current_value = damos_get_in_active_mem_bp( |
| goal->metric == DAMOS_QUOTA_ACTIVE_MEM_BP); |
| break; |
| case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP: |
| goal->current_value = damos_get_node_eligible_mem_bp(c, s, |
| goal->nid); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Return the highest score since it makes schemes least aggressive */ |
| static unsigned long damos_quota_score(struct damon_ctx *c, struct damos *s) |
| { |
| struct damos_quota_goal *goal; |
| struct damos_quota *quota = &s->quota; |
| unsigned long highest_score = 0; |
| |
| damos_for_each_quota_goal(goal, quota) { |
| damos_set_quota_goal_current_value(c, s, goal); |
| highest_score = max(highest_score, |
| mult_frac(goal->current_value, 10000, |
| goal->target_value)); |
| } |
| |
| return highest_score; |
| } |
| |
| static void damos_goal_tune_esz_bp_consist(struct damon_ctx *c, struct damos *s) |
| { |
| struct damos_quota *quota = &s->quota; |
| unsigned long score = damos_quota_score(c, s); |
| |
| quota->esz_bp = damon_feed_loop_next_input( |
| max(quota->esz_bp, 10000UL), score); |
| } |
| |
| static void damos_goal_tune_esz_bp_temporal(struct damon_ctx *c, |
| struct damos *s) |
| { |
| struct damos_quota *quota = &s->quota; |
| unsigned long score = damos_quota_score(c, s); |
| |
| if (score >= 10000) |
| quota->esz_bp = 0; |
| else if (quota->sz) |
| quota->esz_bp = quota->sz * 10000; |
| else |
| quota->esz_bp = ULONG_MAX; |
| } |
| |
| /* |
| * Called only if quota->ms, or quota->sz are set, or quota->goals is not empty |
| */ |
| static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) |
| { |
| struct damos_quota *quota = &s->quota; |
| unsigned long throughput; |
| unsigned long esz = ULONG_MAX; |
| |
| if (!quota->ms && list_empty("a->goals)) { |
| quota->esz = quota->sz; |
| return; |
| } |
| |
| if (!list_empty("a->goals)) { |
| if (quota->goal_tuner == DAMOS_QUOTA_GOAL_TUNER_CONSIST) |
| damos_goal_tune_esz_bp_consist(ctx, s); |
| else if (quota->goal_tuner == DAMOS_QUOTA_GOAL_TUNER_TEMPORAL) |
| damos_goal_tune_esz_bp_temporal(ctx, s); |
| esz = quota->esz_bp / 10000; |
| } |
| |
| if (quota->ms) { |
| if (quota->total_charged_ns) |
| throughput = mult_frac(quota->total_charged_sz, |
| 1000000, quota->total_charged_ns); |
| else |
| throughput = PAGE_SIZE * 1024; |
| esz = min(throughput * quota->ms, esz); |
| esz = max(ctx->min_region_sz, esz); |
| } |
| |
| if (quota->sz && quota->sz < esz) |
| esz = quota->sz; |
| |
| quota->esz = esz; |
| } |
| |
| static void damos_trace_esz(struct damon_ctx *c, struct damos *s, |
| struct damos_quota *quota) |
| { |
| unsigned int cidx = 0, sidx = 0; |
| struct damos *siter; |
| |
| damon_for_each_scheme(siter, c) { |
| if (siter == s) |
| break; |
| sidx++; |
| } |
| trace_damos_esz(cidx, sidx, quota->esz); |
| } |
| |
| static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) |
| { |
| struct damos_quota *quota = &s->quota; |
| struct damon_target *t; |
| struct damon_region *r; |
| unsigned long cumulated_sz, cached_esz; |
| unsigned int score, max_score = 0; |
| |
| if (!quota->ms && !quota->sz && list_empty("a->goals)) |
| return; |
| |
| /* First charge window */ |
| if (!quota->total_charged_sz && !quota->charged_from) { |
| quota->charged_from = jiffies; |
| damos_set_effective_quota(c, s); |
| if (trace_damos_esz_enabled()) |
| damos_trace_esz(c, s, quota); |
| } |
| |
| /* New charge window starts */ |
| if (!time_in_range_open(jiffies, quota->charged_from, |
| quota->charged_from + |
| msecs_to_jiffies(quota->reset_interval))) { |
| if (damos_quota_is_full(quota, c->min_region_sz)) |
| s->stat.qt_exceeds++; |
| quota->total_charged_sz += quota->charged_sz; |
| quota->charged_from = jiffies; |
| quota->charged_sz = 0; |
| if (trace_damos_esz_enabled()) |
| cached_esz = quota->esz; |
| damos_set_effective_quota(c, s); |
| if (trace_damos_esz_enabled() && quota->esz != cached_esz) |
| damos_trace_esz(c, s, quota); |
| } |
| |
| if (!c->ops.get_scheme_score) |
| return; |
| |
| /* Fill up the score histogram */ |
| memset(c->regions_score_histogram, 0, |
| sizeof(*c->regions_score_histogram) * |
| (DAMOS_MAX_SCORE + 1)); |
| damon_for_each_target(t, c) { |
| damon_for_each_region(r, t) { |
| if (!__damos_valid_target(r, s, c)) |
| continue; |
| if (damos_core_filter_out(c, t, r, s)) |
| continue; |
| score = c->ops.get_scheme_score(c, r, s); |
| c->regions_score_histogram[score] += |
| damon_sz_region(r); |
| if (score > max_score) |
| max_score = score; |
| } |
| } |
| |
| /* Set the min score limit */ |
| for (cumulated_sz = 0, score = max_score; ; score--) { |
| cumulated_sz += c->regions_score_histogram[score]; |
| if (cumulated_sz >= quota->esz || !score) |
| break; |
| } |
| quota->min_score = score; |
| } |
| |
| static void damos_trace_stat(struct damon_ctx *c, struct damos *s) |
| { |
| unsigned int cidx = 0, sidx = 0; |
| struct damos *siter; |
| |
| if (!trace_damos_stat_after_apply_interval_enabled()) |
| return; |
| |
| damon_for_each_scheme(siter, c) { |
| if (siter == s) |
| break; |
| sidx++; |
| } |
| trace_call__damos_stat_after_apply_interval(cidx, sidx, &s->stat); |
| } |
| |
| static void kdamond_apply_schemes(struct damon_ctx *c) |
| { |
| struct damon_target *t; |
| struct damos *s; |
| bool has_schemes_to_apply = false; |
| unsigned long max_region_sz; |
| |
| damon_for_each_scheme(s, c) { |
| if (time_before(c->passed_sample_intervals, s->next_apply_sis)) |
| continue; |
| |
| if (!s->wmarks.activated) |
| continue; |
| |
| has_schemes_to_apply = true; |
| |
| damos_adjust_quota(c, s); |
| } |
| |
| if (!has_schemes_to_apply) |
| return; |
| |
| max_region_sz = damon_region_sz_limit(c); |
| mutex_lock(&c->walk_control_lock); |
| damon_for_each_target(t, c) { |
| if (c->ops.target_valid && c->ops.target_valid(t) == false) |
| continue; |
| damos_apply_target(c, t, max_region_sz); |
| } |
| |
| damon_for_each_scheme(s, c) { |
| if (time_before(c->passed_sample_intervals, s->next_apply_sis)) |
| continue; |
| damos_walk_complete(c, s); |
| damos_set_next_apply_sis(s, c); |
| s->last_applied = NULL; |
| damos_trace_stat(c, s); |
| } |
| mutex_unlock(&c->walk_control_lock); |
| } |
| |
| #ifdef CONFIG_DAMON_DEBUG_SANITY |
| static void damon_verify_merge_two_regions( |
| struct damon_region *l, struct damon_region *r) |
| { |
| /* damon_merge_two_regions() may created incorrect left region */ |
| WARN_ONCE(l->ar.start >= l->ar.end, "l: %lu-%lu, r: %lu-%lu\n", |
| l->ar.start, l->ar.end, r->ar.start, r->ar.end); |
| } |
| #else |
| static void damon_verify_merge_two_regions( |
| struct damon_region *l, struct damon_region *r) |
| { |
| } |
| #endif |
| |
| /* |
| * Merge two adjacent regions into one region |
| */ |
| static void damon_merge_two_regions(struct damon_target *t, |
| struct damon_region *l, struct damon_region *r) |
| { |
| unsigned long sz_l = damon_sz_region(l), sz_r = damon_sz_region(r); |
| int i; |
| |
| l->nr_accesses = (l->nr_accesses * sz_l + r->nr_accesses * sz_r) / |
| (sz_l + sz_r); |
| l->age = (l->age * sz_l + r->age * sz_r) / (sz_l + sz_r); |
| l->ar.end = r->ar.end; |
| /* todo: do this for only installed probes */ |
| for (i = 0; i < DAMON_MAX_PROBES; i++) |
| l->probe_hits[i] = (l->probe_hits[i] * sz_l + r->probe_hits[i] |
| * sz_r) / (sz_l + sz_r); |
| damon_verify_merge_two_regions(l, r); |
| damon_destroy_region(r, t); |
| } |
| |
| static unsigned int damon_merge_score(struct damon_region *r, bool last, |
| struct damon_ctx *ctx, bool use_probe_hits) |
| { |
| if (use_probe_hits) |
| return damon_probe_hits_wsum(r, last, ctx); |
| if (last) |
| return r->last_nr_accesses; |
| return r->nr_accesses; |
| } |
| |
| /* |
| * Merge adjacent regions having similar access frequencies |
| * |
| * t target affected by this merge operation |
| * thres '->nr_accesses' diff threshold for the merge |
| * sz_limit size upper limit of each region |
| */ |
| static void damon_merge_regions_of(struct damon_target *t, unsigned int thres, |
| unsigned long sz_limit, struct damon_ctx *ctx, bool count_age) |
| { |
| struct damon_region *r, *prev = NULL, *next; |
| bool use_probe_hits = damon_has_probe_weights(ctx); |
| |
| damon_for_each_region_safe(r, next, t) { |
| unsigned int score, last_score, diff; |
| |
| score = damon_merge_score(r, false, ctx, use_probe_hits); |
| last_score = damon_merge_score(r, true, ctx, use_probe_hits); |
| |
| if (count_age) { |
| if (abs_diff(score, last_score) > thres) |
| r->age = 0; |
| else if ((score == 0) != (last_score == 0)) |
| r->age = 0; |
| else |
| r->age++; |
| } |
| |
| if (!prev) |
| goto set_prev_continue; |
| if (prev->ar.end != r->ar.start) |
| goto set_prev_continue; |
| diff = abs_diff(score, damon_merge_score(prev, false, ctx, |
| use_probe_hits)); |
| if (diff > thres) |
| goto set_prev_continue; |
| if (damon_sz_region(prev) + damon_sz_region(r) > sz_limit) |
| goto set_prev_continue; |
| damon_merge_two_regions(t, prev, r); |
| continue; |
| set_prev_continue: |
| prev = r; |
| } |
| } |
| |
| /* |
| * Merge adjacent regions having similar access frequencies |
| * |
| * threshold '->nr_accesses' diff threshold for the merge |
| * sz_limit size upper limit of each region |
| * |
| * This function merges monitoring target regions which are adjacent and their |
| * access frequencies are similar. This is for minimizing the monitoring |
| * overhead under the dynamically changeable access pattern. If a merge was |
| * unnecessarily made, later 'kdamond_split_regions()' will revert it. |
| * |
| * The total number of regions could be higher than the user-defined limit, |
| * max_nr_regions for some cases. For example, the user can update |
| * max_nr_regions to a number that lower than the current number of regions |
| * while DAMON is running. For such a case, repeat merging until the limit is |
| * met while increasing @threshold up to possible maximum level. |
| */ |
| static void kdamond_merge_regions(struct damon_ctx *c, unsigned int threshold, |
| unsigned long sz_limit) |
| { |
| struct damon_target *t; |
| unsigned int nr_regions; |
| unsigned int max_thres; |
| bool count_age = true; |
| |
| max_thres = c->attrs.aggr_interval / |
| (c->attrs.sample_interval ? c->attrs.sample_interval : 1); |
| while (true) { |
| nr_regions = 0; |
| damon_for_each_target(t, c) { |
| damon_merge_regions_of(t, threshold, sz_limit, c, |
| count_age); |
| nr_regions += damon_nr_regions(t); |
| } |
| count_age = false; |
| if (nr_regions <= c->attrs.max_nr_regions || |
| max_thres <= threshold) |
| break; |
| if (threshold < max_thres / 2) |
| threshold = max(1, threshold * 2); |
| else |
| threshold = max_thres; |
| } |
| } |
| |
| #ifdef CONFIG_DAMON_DEBUG_SANITY |
| static void damon_verify_split_region_at(struct damon_region *r, |
| unsigned long sz_r) |
| { |
| WARN_ONCE(sz_r == 0 || sz_r >= damon_sz_region(r), |
| "sz_r: %lu r: %lu-%lu (%lu)\n", |
| sz_r, r->ar.start, r->ar.end, damon_sz_region(r)); |
| } |
| #else |
| static void damon_verify_split_region_at(struct damon_region *r, |
| unsigned long sz_r) |
| { |
| } |
| #endif |
| |
| /* |
| * Split a region in two |
| * |
| * r the region to be split |
| * sz_r size of the first sub-region that will be made |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| static int damon_split_region_at(struct damon_target *t, |
| struct damon_region *r, unsigned long sz_r) |
| { |
| struct damon_region *new; |
| |
| damon_verify_split_region_at(r, sz_r); |
| new = damon_new_region(r->ar.start + sz_r, r->ar.end); |
| if (!new) |
| return -ENOMEM; |
| |
| r->ar.end = new->ar.start; |
| |
| new->age = r->age; |
| new->last_nr_accesses = r->last_nr_accesses; |
| new->nr_accesses = r->nr_accesses; |
| /* todo: do this for only installed probes */ |
| memcpy(new->probe_hits, r->probe_hits, sizeof(r->probe_hits)); |
| memcpy(new->last_probe_hits, r->last_probe_hits, |
| sizeof(r->last_probe_hits)); |
| |
| damon_insert_region(new, r, damon_next_region(r), t); |
| return 0; |
| } |
| |
| /* Split every region in the given target into 'nr_subs' regions */ |
| static void damon_split_regions_of(struct damon_ctx *ctx, |
| struct damon_target *t, int nr_subs, |
| unsigned long min_region_sz) |
| { |
| struct damon_region *r, *next; |
| unsigned long sz_region, sz_sub = 0; |
| int i; |
| |
| damon_for_each_region_safe(r, next, t) { |
| sz_region = damon_sz_region(r); |
| |
| for (i = 0; i < nr_subs - 1 && |
| sz_region > 2 * min_region_sz; i++) { |
| /* |
| * Randomly select size of left sub-region to be at |
| * least 10 percent and at most 90% of original region |
| */ |
| sz_sub = ALIGN_DOWN(damon_rand(ctx, 1, 10) * |
| sz_region / 10, min_region_sz); |
| /* Do not allow blank region */ |
| if (sz_sub == 0 || sz_sub >= sz_region) |
| continue; |
| |
| damon_split_region_at(t, r, sz_sub); |
| sz_region = sz_sub; |
| } |
| } |
| } |
| |
| /* Split one in every @split_step regions into two, from a rotating offset */ |
| static void damon_split_some_regions(struct damon_ctx *ctx, |
| unsigned long split_step) |
| { |
| static unsigned long rotation; |
| struct damon_target *t; |
| struct damon_region *r, *next; |
| unsigned long offset = rotation++ % split_step; |
| unsigned long idx = 0; |
| |
| damon_for_each_target(t, ctx) { |
| damon_for_each_region_safe(r, next, t) { |
| unsigned long sz_region, sz_sub; |
| |
| if (idx++ % split_step != offset) |
| continue; |
| sz_region = damon_sz_region(r); |
| if (sz_region < 2 * ctx->min_region_sz) |
| continue; |
| |
| sz_sub = ALIGN_DOWN(damon_rand(ctx, 1, 10) * |
| sz_region / 10, ctx->min_region_sz); |
| /* Do not allow blank region */ |
| if (sz_sub == 0 || sz_sub >= sz_region) |
| continue; |
| |
| damon_split_region_at(t, r, sz_sub); |
| } |
| } |
| } |
| |
| /* |
| * Split every target region into randomly-sized small regions |
| * |
| * This function splits every target region into random-sized small regions if |
| * current total number of the regions is equal or smaller than half of the |
| * user-specified maximum number of regions. This is for maximizing the |
| * monitoring accuracy under the dynamically changeable access patterns. If a |
| * split was unnecessarily made, later 'kdamond_merge_regions()' will revert |
| * it. |
| */ |
| static void kdamond_split_regions(struct damon_ctx *ctx) |
| { |
| struct damon_target *t; |
| unsigned long nr_regions = 0; |
| unsigned long max_nr_regions = ctx->attrs.max_nr_regions; |
| static unsigned long last_nr_regions; |
| int nr_subregions = 2; |
| |
| damon_for_each_target(t, ctx) |
| nr_regions += damon_nr_regions(t); |
| |
| if (nr_regions >= max_nr_regions) |
| goto done; |
| |
| if (nr_regions > max_nr_regions / 2) { |
| damon_split_some_regions(ctx, |
| max_nr_regions / (max_nr_regions - nr_regions)); |
| goto done; |
| } |
| |
| /* Maybe the middle of the region has different access frequency */ |
| if (last_nr_regions == nr_regions && |
| nr_regions < max_nr_regions / 3) |
| nr_subregions = 3; |
| |
| damon_for_each_target(t, ctx) |
| damon_split_regions_of(ctx, t, nr_subregions, |
| ctx->min_region_sz); |
| |
| done: |
| last_nr_regions = nr_regions; |
| } |
| |
| /* |
| * Check whether current monitoring should be stopped |
| * |
| * The monitoring is stopped when either the user requested to stop, or all |
| * monitoring targets are invalid. |
| * |
| * Returns true if need to stop current monitoring. |
| */ |
| static bool kdamond_need_stop(struct damon_ctx *ctx) |
| { |
| struct damon_target *t; |
| |
| if (kthread_should_stop()) |
| return true; |
| |
| if (!ctx->ops.target_valid) |
| return false; |
| |
| damon_for_each_target(t, ctx) { |
| if (ctx->ops.target_valid(t)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int damos_get_wmark_metric_value(enum damos_wmark_metric metric, |
| unsigned long *metric_value) |
| { |
| switch (metric) { |
| case DAMOS_WMARK_FREE_MEM_RATE: |
| *metric_value = global_zone_page_state(NR_FREE_PAGES) * 1000 / |
| totalram_pages(); |
| return 0; |
| default: |
| break; |
| } |
| return -EINVAL; |
| } |
| |
| /* |
| * Returns zero if the scheme is active. Else, returns time to wait for next |
| * watermark check in micro-seconds. |
| */ |
| static unsigned long damos_wmark_wait_us(struct damos *scheme) |
| { |
| unsigned long metric; |
| |
| if (damos_get_wmark_metric_value(scheme->wmarks.metric, &metric)) |
| return 0; |
| |
| /* higher than high watermark or lower than low watermark */ |
| if (metric > scheme->wmarks.high || scheme->wmarks.low > metric) { |
| if (scheme->wmarks.activated) |
| pr_debug("deactivate a scheme (%d) for %s wmark\n", |
| scheme->action, |
| str_high_low(metric > scheme->wmarks.high)); |
| scheme->wmarks.activated = false; |
| return scheme->wmarks.interval; |
| } |
| |
| /* inactive and higher than middle watermark */ |
| if ((scheme->wmarks.high >= metric && metric >= scheme->wmarks.mid) && |
| !scheme->wmarks.activated) |
| return scheme->wmarks.interval; |
| |
| if (!scheme->wmarks.activated) |
| pr_debug("activate a scheme (%d)\n", scheme->action); |
| scheme->wmarks.activated = true; |
| return 0; |
| } |
| |
| static void kdamond_usleep(unsigned long usecs) |
| { |
| if (usecs >= USLEEP_RANGE_UPPER_BOUND) |
| schedule_timeout_idle(usecs_to_jiffies(usecs)); |
| else |
| usleep_range_idle(usecs, usecs + 1); |
| } |
| |
| #ifdef CONFIG_DAMON_DEBUG_SANITY |
| static void damon_verify_ctx(struct damon_ctx *c) |
| { |
| struct damon_target *t; |
| struct damon_region *r; |
| |
| damon_for_each_target(t, c) { |
| struct damon_region *prev_r = NULL; |
| unsigned int nr_regions = 0; |
| |
| damon_for_each_region(r, t) { |
| WARN_ONCE(r->ar.start >= r->ar.end, |
| "region start (%lu) >= end (%lu)\n", |
| r->ar.start, r->ar.end); |
| WARN_ONCE(prev_r && prev_r->ar.end > r->ar.start, |
| "region overlap (%lu > %lu)\n", |
| prev_r->ar.end, r->ar.start); |
| prev_r = r; |
| nr_regions++; |
| } |
| WARN_ONCE(damon_nr_regions(t) != nr_regions, |
| "nr_regions mismatch: %u != %u\n", |
| damon_nr_regions(t), nr_regions); |
| } |
| } |
| #else |
| static void damon_verify_ctx(struct damon_ctx *c) |
| { |
| } |
| #endif |
| |
| /* |
| * kdamond_call() - handle damon_call_control objects. |
| * @ctx: The &struct damon_ctx of the kdamond. |
| * @cancel: Whether to cancel the invocation of the function. |
| * |
| * If there are &struct damon_call_control requests that registered via |
| * &damon_call() on @ctx, do or cancel the invocation of the function depending |
| * on @cancel. @cancel is set when the kdamond is already out of the main loop |
| * and therefore will be terminated. |
| */ |
| static void kdamond_call(struct damon_ctx *ctx, bool cancel) |
| { |
| struct damon_call_control *control, *next; |
| LIST_HEAD(controls); |
| |
| damon_verify_ctx(ctx); |
| |
| mutex_lock(&ctx->call_controls_lock); |
| list_splice_tail_init(&ctx->call_controls, &controls); |
| mutex_unlock(&ctx->call_controls_lock); |
| |
| list_for_each_entry_safe(control, next, &controls, list) { |
| if (!control->repeat || cancel) |
| list_del(&control->list); |
| |
| if (cancel) |
| control->canceled = true; |
| else |
| control->return_code = control->fn(control->data); |
| |
| if (!control->repeat) |
| complete(&control->completion); |
| else if (control->canceled && control->dealloc_on_cancel) |
| kfree(control); |
| if (!cancel && ctx->maybe_corrupted) |
| break; |
| } |
| |
| mutex_lock(&ctx->call_controls_lock); |
| list_splice_tail(&controls, &ctx->call_controls); |
| mutex_unlock(&ctx->call_controls_lock); |
| } |
| |
| /* Returns negative error code if it's not activated but should return */ |
| static int kdamond_wait_activation(struct damon_ctx *ctx) |
| { |
| struct damos *s; |
| unsigned long wait_time; |
| unsigned long min_wait_time = 0; |
| bool init_wait_time = false; |
| |
| while (!kdamond_need_stop(ctx)) { |
| damon_for_each_scheme(s, ctx) { |
| wait_time = damos_wmark_wait_us(s); |
| if (!init_wait_time || wait_time < min_wait_time) { |
| init_wait_time = true; |
| min_wait_time = wait_time; |
| } |
| } |
| if (!min_wait_time) |
| return 0; |
| |
| kdamond_usleep(min_wait_time); |
| |
| kdamond_call(ctx, false); |
| if (ctx->maybe_corrupted) |
| return -EINVAL; |
| damos_walk_cancel(ctx); |
| } |
| return -EBUSY; |
| } |
| |
| static void kdamond_init_ctx(struct damon_ctx *ctx) |
| { |
| unsigned long sample_interval = ctx->attrs.sample_interval ? |
| ctx->attrs.sample_interval : 1; |
| struct damos *scheme; |
| |
| ctx->passed_sample_intervals = 0; |
| ctx->next_aggregation_sis = ctx->attrs.aggr_interval / sample_interval; |
| ctx->next_ops_update_sis = ctx->attrs.ops_update_interval / |
| sample_interval; |
| ctx->next_intervals_tune_sis = ctx->next_aggregation_sis * |
| ctx->attrs.intervals_goal.aggrs; |
| |
| damon_for_each_scheme(scheme, ctx) { |
| damos_set_next_apply_sis(scheme, ctx); |
| damos_set_filters_default_reject(scheme); |
| } |
| } |
| |
| /* |
| * The monitoring daemon that runs as a kernel thread |
| */ |
| static int kdamond_fn(void *data) |
| { |
| struct damon_ctx *ctx = data; |
| unsigned long sz_limit = 0; |
| |
| pr_debug("kdamond (%d) starts\n", current->pid); |
| |
| mutex_lock(&ctx->call_controls_lock); |
| ctx->call_controls_obsolete = false; |
| mutex_unlock(&ctx->call_controls_lock); |
| mutex_lock(&ctx->walk_control_lock); |
| ctx->walk_control_obsolete = false; |
| mutex_unlock(&ctx->walk_control_lock); |
| complete(&ctx->kdamond_started); |
| kdamond_init_ctx(ctx); |
| |
| if (ctx->ops.init) |
| ctx->ops.init(ctx); |
| ctx->regions_score_histogram = kmalloc_array(DAMOS_MAX_SCORE + 1, |
| sizeof(*ctx->regions_score_histogram), GFP_KERNEL); |
| if (!ctx->regions_score_histogram) |
| goto done; |
| |
| sz_limit = damon_apply_min_nr_regions(ctx); |
| |
| while (!kdamond_need_stop(ctx)) { |
| /* |
| * ctx->attrs and ctx->next_{aggregation,ops_update}_sis could |
| * be changed from kdamond_call(). Read the values here, and |
| * use those for this iteration. That is, damon_set_attrs() |
| * updated new values are respected from next iteration. |
| */ |
| unsigned long next_aggregation_sis = ctx->next_aggregation_sis; |
| unsigned long next_ops_update_sis = ctx->next_ops_update_sis; |
| unsigned long sample_interval = ctx->attrs.sample_interval; |
| bool access_check_disabled = damon_has_probe_weights(ctx); |
| unsigned int max_merge_score = 0, max_wsum; |
| bool get_max_wsum; |
| |
| if (kdamond_wait_activation(ctx)) |
| break; |
| |
| if (!access_check_disabled && ctx->ops.prepare_access_checks) |
| ctx->ops.prepare_access_checks(ctx); |
| |
| kdamond_usleep(sample_interval); |
| ctx->passed_sample_intervals++; |
| |
| if (!access_check_disabled && ctx->ops.check_accesses) |
| max_merge_score = ctx->ops.check_accesses(ctx); |
| if (ctx->ops.apply_probes) { |
| if (time_after_eq(ctx->passed_sample_intervals, |
| next_aggregation_sis) && |
| access_check_disabled) |
| get_max_wsum = true; |
| else |
| get_max_wsum = false; |
| max_wsum = ctx->ops.apply_probes(ctx, |
| access_check_disabled, get_max_wsum); |
| if (get_max_wsum) |
| max_merge_score = max_wsum; |
| } |
| |
| if (time_after_eq(ctx->passed_sample_intervals, |
| next_aggregation_sis)) { |
| kdamond_merge_regions(ctx, |
| max_merge_score / 10, |
| sz_limit); |
| /* online updates might be made */ |
| sz_limit = damon_apply_min_nr_regions(ctx); |
| } |
| |
| /* |
| * do kdamond_call() and kdamond_apply_schemes() after |
| * kdamond_merge_regions() if possible, to reduce overhead |
| */ |
| kdamond_call(ctx, false); |
| if (ctx->maybe_corrupted) |
| break; |
| while (ctx->pause) { |
| damos_walk_cancel(ctx); |
| kdamond_usleep(ctx->attrs.sample_interval); |
| /* allow caller unset pause via damon_call() */ |
| kdamond_call(ctx, false); |
| if (kdamond_need_stop(ctx) || ctx->maybe_corrupted) |
| goto done; |
| } |
| if (!list_empty(&ctx->schemes)) |
| kdamond_apply_schemes(ctx); |
| else |
| damos_walk_cancel(ctx); |
| |
| sample_interval = ctx->attrs.sample_interval ? |
| ctx->attrs.sample_interval : 1; |
| if (time_after_eq(ctx->passed_sample_intervals, |
| next_aggregation_sis)) { |
| if (ctx->attrs.intervals_goal.aggrs && |
| time_after_eq( |
| ctx->passed_sample_intervals, |
| ctx->next_intervals_tune_sis)) { |
| /* |
| * ctx->next_aggregation_sis might be updated |
| * from kdamond_call(). In the case, |
| * damon_set_attrs() which will be called from |
| * kdamond_tune_interval() may wrongly think |
| * this is in the middle of the current |
| * aggregation, and make aggregation |
| * information reset for all regions. Then, |
| * following kdamond_reset_aggregated() call |
| * will make the region information invalid. |
| * |
| * Reset ->next_aggregation_sis to avoid that. |
| * It will anyway correctly updated after this |
| * if clause. |
| */ |
| ctx->next_aggregation_sis = |
| next_aggregation_sis; |
| ctx->next_intervals_tune_sis += |
| ctx->attrs.aggr_samples * |
| ctx->attrs.intervals_goal.aggrs; |
| kdamond_tune_intervals(ctx); |
| sample_interval = ctx->attrs.sample_interval ? |
| ctx->attrs.sample_interval : 1; |
| |
| } |
| ctx->next_aggregation_sis = next_aggregation_sis + |
| ctx->attrs.aggr_interval / sample_interval; |
| |
| kdamond_reset_aggregated(ctx); |
| kdamond_split_regions(ctx); |
| } |
| |
| if (time_after_eq(ctx->passed_sample_intervals, |
| next_ops_update_sis)) { |
| ctx->next_ops_update_sis = next_ops_update_sis + |
| ctx->attrs.ops_update_interval / |
| sample_interval; |
| if (ctx->ops.update) |
| ctx->ops.update(ctx); |
| } |
| } |
| done: |
| damon_destroy_targets(ctx); |
| |
| kfree(ctx->regions_score_histogram); |
| mutex_lock(&ctx->call_controls_lock); |
| ctx->call_controls_obsolete = true; |
| mutex_unlock(&ctx->call_controls_lock); |
| kdamond_call(ctx, true); |
| mutex_lock(&ctx->walk_control_lock); |
| ctx->walk_control_obsolete = true; |
| mutex_unlock(&ctx->walk_control_lock); |
| damos_walk_cancel(ctx); |
| |
| pr_debug("kdamond (%d) finishes\n", current->pid); |
| mutex_lock(&ctx->kdamond_lock); |
| ctx->kdamond = NULL; |
| mutex_unlock(&ctx->kdamond_lock); |
| |
| mutex_lock(&damon_lock); |
| nr_running_ctxs--; |
| if (!nr_running_ctxs && running_exclusive_ctxs) |
| running_exclusive_ctxs = false; |
| mutex_unlock(&damon_lock); |
| |
| return 0; |
| } |
| |
| struct damon_system_ram_range_walk_arg { |
| bool walked; |
| struct resource res; |
| }; |
| |
| static int damon_system_ram_walk_fn(struct resource *res, void *arg) |
| { |
| struct damon_system_ram_range_walk_arg *a = arg; |
| |
| if (!a->walked) { |
| a->walked = true; |
| a->res.start = res->start; |
| } |
| a->res.end = res->end; |
| return 0; |
| } |
| |
| static unsigned long damon_res_to_core_addr(resource_size_t ra, |
| unsigned long addr_unit) |
| { |
| /* |
| * Use div_u64() for avoiding linking errors related with __udivdi3, |
| * __aeabi_uldivmod, or similar problems. This should also improve the |
| * performance optimization (read div_u64() comment for the detail). |
| */ |
| if (sizeof(ra) == 8 && sizeof(addr_unit) == 4) |
| return div_u64(ra, addr_unit); |
| return ra / addr_unit; |
| } |
| |
| static bool damon_find_system_rams_range(unsigned long *start, |
| unsigned long *end, unsigned long addr_unit) |
| { |
| struct damon_system_ram_range_walk_arg arg = {}; |
| |
| walk_system_ram_res(0, -1, &arg, damon_system_ram_walk_fn); |
| if (!arg.walked) |
| return false; |
| *start = damon_res_to_core_addr(arg.res.start, addr_unit); |
| *end = damon_res_to_core_addr(arg.res.end + 1, addr_unit); |
| if (*end <= *start) |
| return false; |
| return true; |
| } |
| |
| /** |
| * damon_set_region_system_rams_default() - Set the region of the given |
| * monitoring target as requested, or to cover all 'System RAM' resources. |
| * @t: The monitoring target to set the region. |
| * @start: The pointer to the start address of the region. |
| * @end: The pointer to the end address of the region. |
| * @addr_unit: The address unit for the damon_ctx of @t. |
| * @min_region_sz: Minimum region size. |
| * |
| * This function sets the region of @t as requested by @start and @end. If the |
| * values of @start and @end are zero, however, this function finds 'System |
| * RAM' resources and sets the region to cover all the resource. In the latter |
| * case, this function saves the start and the end addresseses of the first and |
| * the last resources in @start and @end, respectively. |
| * |
| * Return: 0 on success, negative error code otherwise. |
| */ |
| int damon_set_region_system_rams_default(struct damon_target *t, |
| unsigned long *start, unsigned long *end, |
| unsigned long addr_unit, unsigned long min_region_sz) |
| { |
| struct damon_addr_range addr_range; |
| |
| if (!*start && !*end && |
| !damon_find_system_rams_range(start, end, addr_unit)) |
| return -EINVAL; |
| |
| addr_range.start = *start; |
| addr_range.end = *end; |
| return damon_set_regions(t, &addr_range, 1, min_region_sz); |
| } |
| |
| /** |
| * damon_update_region_access_rate() - Update the access rate of a region. |
| * @r: The DAMON region to update for its access check result. |
| * @accessed: Whether the region has accessed during last sampling interval. |
| * |
| * Update the access rate of a region with the region's last sampling interval |
| * access check result. |
| * |
| * Usually this will be called by &damon_operations->check_accesses callback. |
| */ |
| void damon_update_region_access_rate(struct damon_region *r, bool accessed) |
| { |
| if (accessed) |
| r->nr_accesses++; |
| } |
| |
| /** |
| * damon_initialized() - Return if DAMON is ready to be used. |
| * |
| * Return: true if DAMON is ready to be used, false otherwise. |
| */ |
| bool damon_initialized(void) |
| { |
| return damon_region_cache != NULL; |
| } |
| |
| static int __init damon_init(void) |
| { |
| damon_region_cache = KMEM_CACHE(damon_region, 0); |
| if (unlikely(!damon_region_cache)) { |
| pr_err("creating damon_region_cache fails\n"); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| subsys_initcall(damon_init); |
| |
| #include "tests/core-kunit.h" |