Re: [PATCH v23 02/15] mm/damon/core: Implement region-based sampling

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Dec 15, 2020 at 3:56 AM SeongJae Park <sjpark@xxxxxxxxxx> wrote:
>
> From: SeongJae Park <sjpark@xxxxxxxxx>
>
> To avoid the unbounded increase of the overhead, DAMON groups adjacent
> pages that assumed to have the same access frequencies into a region.

'that are assumed'

> As long as the assumption (pages in a region have the same access
> frequencies) is kept, only one page in the region is required to be
> checked.  Thus, for each ``sampling interval``,
>
>  1. the 'prepare_access_checks' primitive picks one page in each region,
>  2. waits for one ``sampling interval``,
>  3. checks whether the page is accessed meanwhile, and
>  4. increases the access frequency of the region if so.

I think you meant increasing the access 'count' or something.
Increasing the access frequency somewhat conveys that the sampling
interval is being decreased.

>
> Therefore, the monitoring overhead is controllable by adjusting the
> number of regions.  DAMON allows both the underlying primitives and user
> callbacks adjust regions for the trade-off.  In other words, this commit

'callbacks to adjust'


> makes DAMON to use not only time-based sampling but also space-based
> sampling.
>
> This scheme, however, cannot preserve the quality of the output if the
> assumption is not guaranteed.  Next commit will address this problem.
>
> Another problem of this region abstraction is additional memory space
> overhead for the regions metadata.  For example, suppose page
> granularity monitoring that doesn't want to know fine-grained access
> frequency but only if each page accessed or not.

You mean when the sampling interval is equal to the aggregation interval, right?

> Then, we can do that
> by directly resetting and reading the PG_Idle flags and/or the PTE
> Accessed bits.  The metadata for the region abstraction is only burden
> in the case.  For the reason, this commit makes DAMON to support the
> user-defined arbitrary target, which could be stored in a void pointer
> of the monitoring context with specific target type.

Sorry I didn't follow. How does sampling interval equal to aggregation
interval require user-defined arbitrary targets?

>
> Signed-off-by: SeongJae Park <sjpark@xxxxxxxxx>
> Reviewed-by: Leonard Foerster <foersleo@xxxxxxxxx>
> ---
>  include/linux/damon.h | 109 ++++++++++++++++++++++++++++++--
>  mm/damon/core.c       | 142 +++++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 243 insertions(+), 8 deletions(-)
>
> diff --git a/include/linux/damon.h b/include/linux/damon.h
> index 387fa4399fc8..7d4685adc8a9 100644
> --- a/include/linux/damon.h
> +++ b/include/linux/damon.h
> @@ -12,6 +12,48 @@
>  #include <linux/time64.h>
>  #include <linux/types.h>
>
> +/**
> + * struct damon_addr_range - Represents an address region of [@start, @end).
> + * @start:     Start address of the region (inclusive).
> + * @end:       End address of the region (exclusive).
> + */
> +struct damon_addr_range {
> +       unsigned long start;
> +       unsigned long end;
> +};
> +
> +/**
> + * struct damon_region - Represents a monitoring target region.
> + * @ar:                        The address range of the region.
> + * @sampling_addr:     Address of the sample for the next access check.
> + * @nr_accesses:       Access frequency of this region.
> + * @list:              List head for siblings.
> + */
> +struct damon_region {
> +       struct damon_addr_range ar;
> +       unsigned long sampling_addr;
> +       unsigned int nr_accesses;
> +       struct list_head list;
> +};
> +
> +/**
> + * struct damon_target - Represents a monitoring target.
> + * @id:                        Unique identifier for this target.
> + * @regions_list:      Head of the monitoring target regions of this target.
> + * @list:              List head for siblings.
> + *
> + * Each monitoring context could have multiple targets.  For example, a context
> + * for virtual memory address spaces could have multiple target processes.  The
> + * @id of each target should be unique among the targets of the context.  For
> + * example, in the virtual address monitoring context, it could be a pidfd or
> + * an address of an mm_struct.
> + */
> +struct damon_target {
> +       unsigned long id;
> +       struct list_head regions_list;
> +       struct list_head list;
> +};
> +
>  struct damon_ctx;
>
>  /**
> @@ -36,7 +78,8 @@ struct damon_ctx;
>   *
>   * @init_target_regions should construct proper monitoring target regions and
>   * link those to the DAMON context struct.  The regions should be defined by
> - * user and saved in @damon_ctx.target.
> + * user and saved in @damon_ctx.arbitrary_target if @damon_ctx.target_type is
> + * &DAMON_ARBITRARY_TARGET.  Otherwise, &struct damon_region should be used.
>   * @update_target_regions should update the monitoring target regions for
>   * current status.
>   * @prepare_access_checks should manipulate the monitoring regions to be
> @@ -46,7 +89,8 @@ struct damon_ctx;
>   * @reset_aggregated should reset the access monitoring results that aggregated
>   * by @check_accesses.
>   * @target_valid should check whether the target is still valid for the
> - * monitoring.
> + * monitoring.  It receives &damon_ctx.arbitrary_target or &struct damon_target
> + * pointer depends on &damon_ctx.target_type.
>   * @cleanup is called from @kdamond just before its termination.  After this
>   * call, only @kdamond_lock and @kdamond will be touched.
>   */
> @@ -91,6 +135,17 @@ struct damon_callback {
>         int (*before_terminate)(struct damon_ctx *context);
>  };
>
> +/**
> + * enum damon_target_type - Represents the type of the monitoring target.
> + *
> + * @DAMON_REGION_SAMPLING_TARGET:      Region based sampling target.
> + * @DAMON_ARBITRARY_TARGET:            User-defined arbitrary type target.
> + */
> +enum damon_target_type {
> +       DAMON_REGION_SAMPLING_TARGET,
> +       DAMON_ARBITRARY_TARGET,

I would suggest removing the arbitrary target in this pathset. This
patchset is only adding the region sampling target, so no need to add
the arbitrary target here.

> +};
> +
>  /**
>   * struct damon_ctx - Represents a context for each monitoring.  This is the
>   * main interface that allows users to set the attributes and get the results
> @@ -130,7 +185,15 @@ struct damon_callback {
>   * @primitive: Set of monitoring primitives for given use cases.
>   * @callback:  Set of callbacks for monitoring events notifications.
>   *
> - * @target:    Pointer to the user-defined monitoring target.
> + * @target_type:       Type of the monitoring target.
> + *
> + * @region_targets:    Head of monitoring targets (&damon_target) list.
> + *
> + * @arbitrary_target:  Pointer to arbitrary type target.
> + *
> + * @region_targets are valid only if @target_type is
> + * DAMON_REGION_SAMPLING_TARGET.  @arbitrary_target is valid only if
> + * @target_type is DAMON_ARBITRARY_TARGET.
>   */
>  struct damon_ctx {
>         unsigned long sample_interval;
> @@ -149,12 +212,48 @@ struct damon_ctx {
>         struct damon_primitive primitive;
>         struct damon_callback callback;
>
> -       void *target;
> +       enum damon_target_type target_type;
> +       union {
> +               /* DAMON_REGION_SAMPLING_TARGET */
> +               struct list_head region_targets;
> +
> +               /* DAMON_ARBITRARY_TARGET */
> +               void *arbitrary_target;
> +       };
>  };
>
> +#define damon_next_region(r) \
> +       (container_of(r->list.next, struct damon_region, list))
> +
> +#define damon_prev_region(r) \
> +       (container_of(r->list.prev, struct damon_region, list))
> +
> +#define damon_for_each_region(r, t) \
> +       list_for_each_entry(r, &t->regions_list, list)
> +
> +#define damon_for_each_region_safe(r, next, t) \
> +       list_for_each_entry_safe(r, next, &t->regions_list, list)
> +
> +#define damon_for_each_target(t, ctx) \
> +       list_for_each_entry(t, &(ctx)->region_targets, list)
> +
> +#define damon_for_each_target_safe(t, next, ctx)       \
> +       list_for_each_entry_safe(t, next, &(ctx)->region_targets, list)
> +
>  #ifdef CONFIG_DAMON
>
> -struct damon_ctx *damon_new_ctx(void);
> +struct damon_region *damon_new_region(unsigned long start, unsigned long end);
> +inline void damon_insert_region(struct damon_region *r,
> +               struct damon_region *prev, struct damon_region *next);
> +void damon_add_region(struct damon_region *r, struct damon_target *t);
> +void damon_destroy_region(struct damon_region *r);
> +
> +struct damon_target *damon_new_target(unsigned long id);
> +void damon_add_target(struct damon_ctx *ctx, struct damon_target *t);
> +void damon_free_target(struct damon_target *t);
> +void damon_destroy_target(struct damon_target *t);
> +
> +struct damon_ctx *damon_new_ctx(enum damon_target_type type);
>  void damon_destroy_ctx(struct damon_ctx *ctx);
>  int damon_set_attrs(struct damon_ctx *ctx, unsigned long sample_int,
>                 unsigned long aggr_int, unsigned long regions_update_int);
> diff --git a/mm/damon/core.c b/mm/damon/core.c
> index 8963804efdf9..167487e75737 100644
> --- a/mm/damon/core.c
> +++ b/mm/damon/core.c
> @@ -15,7 +15,102 @@
>  static DEFINE_MUTEX(damon_lock);
>  static int nr_running_ctxs;
>
> -struct damon_ctx *damon_new_ctx(void)
> +/*
> + * 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;
> +
> +       region = kmalloc(sizeof(*region), GFP_KERNEL);
> +       if (!region)
> +               return NULL;
> +
> +       region->ar.start = start;
> +       region->ar.end = end;
> +       region->nr_accesses = 0;
> +       INIT_LIST_HEAD(&region->list);
> +
> +       return region;
> +}
> +
> +/*
> + * Add a region between two other regions
> + */
> +inline void damon_insert_region(struct damon_region *r,
> +               struct damon_region *prev, struct damon_region *next)
> +{
> +       __list_add(&r->list, &prev->list, &next->list);
> +}
> +
> +void damon_add_region(struct damon_region *r, struct damon_target *t)
> +{
> +       list_add_tail(&r->list, &t->regions_list);
> +}
> +

I don't see the benefit of these one line functions at least the following two.


> +static void damon_del_region(struct damon_region *r)
> +{
> +       list_del(&r->list);
> +}
> +
> +static void damon_free_region(struct damon_region *r)
> +{
> +       kfree(r);
> +}
> +
> +void damon_destroy_region(struct damon_region *r)
> +{
> +       damon_del_region(r);
> +       damon_free_region(r);
> +}
> +
> +/*
> + * Construct a damon_target struct
> + *
> + * Returns the pointer to the new struct if success, or NULL otherwise
> + */
> +struct damon_target *damon_new_target(unsigned long id)
> +{
> +       struct damon_target *t;
> +
> +       t = kmalloc(sizeof(*t), GFP_KERNEL);
> +       if (!t)
> +               return NULL;
> +
> +       t->id = id;
> +       INIT_LIST_HEAD(&t->regions_list);
> +
> +       return t;
> +}
> +
> +void damon_add_target(struct damon_ctx *ctx, struct damon_target *t)
> +{
> +       list_add_tail(&t->list, &ctx->region_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)
> +{
> +       damon_del_target(t);
> +       damon_free_target(t);
> +}
> +
> +struct damon_ctx *damon_new_ctx(enum damon_target_type type)
>  {
>         struct damon_ctx *ctx;
>
> @@ -32,13 +127,20 @@ struct damon_ctx *damon_new_ctx(void)
>
>         mutex_init(&ctx->kdamond_lock);
>
> -       ctx->target = NULL;
> +       ctx->target_type = type;
> +       if (type != DAMON_ARBITRARY_TARGET)
> +               INIT_LIST_HEAD(&ctx->region_targets);
>
>         return ctx;
>  }
>
>  void damon_destroy_ctx(struct damon_ctx *ctx)
>  {
> +       struct damon_target *t, *next_t;
> +
> +       damon_for_each_target_safe(t, next_t, ctx)
> +               damon_destroy_target(t);
> +
>         kfree(ctx);
>  }
>
> @@ -215,6 +317,21 @@ static bool kdamond_aggregate_interval_passed(struct damon_ctx *ctx)
>                         ctx->aggr_interval);
>  }
>
> +/*
> + * Reset the aggregated monitoring results ('nr_accesses' of each region).
> + */
> +static void kdamond_reset_aggregated(struct damon_ctx *c)
> +{
> +       struct damon_target *t;
> +
> +       damon_for_each_target(t, c) {
> +               struct damon_region *r;
> +
> +               damon_for_each_region(r, t)
> +                       r->nr_accesses = 0;
> +       }
> +}
> +
>  /*
>   * Check whether it is time to check and apply the target monitoring regions
>   *
> @@ -236,6 +353,7 @@ static bool kdamond_need_update_regions(struct damon_ctx *ctx)
>   */
>  static bool kdamond_need_stop(struct damon_ctx *ctx)
>  {
> +       struct damon_target *t;
>         bool stop;
>
>         mutex_lock(&ctx->kdamond_lock);
> @@ -247,7 +365,15 @@ static bool kdamond_need_stop(struct damon_ctx *ctx)
>         if (!ctx->primitive.target_valid)
>                 return false;
>
> -       return !ctx->primitive.target_valid(ctx->target);
> +       if (ctx->target_type == DAMON_ARBITRARY_TARGET)
> +               return !ctx->primitive.target_valid(ctx->arbitrary_target);
> +
> +       damon_for_each_target(t, ctx) {
> +               if (ctx->primitive.target_valid(t))
> +                       return false;
> +       }
> +
> +       return true;
>  }
>
>  static void set_kdamond_stop(struct damon_ctx *ctx)
> @@ -263,6 +389,8 @@ static void set_kdamond_stop(struct damon_ctx *ctx)
>  static int kdamond_fn(void *data)
>  {
>         struct damon_ctx *ctx = (struct damon_ctx *)data;
> +       struct damon_target *t;
> +       struct damon_region *r, *next;
>
>         pr_info("kdamond (%d) starts\n", ctx->kdamond->pid);
>
> @@ -287,6 +415,8 @@ static int kdamond_fn(void *data)
>                         if (ctx->callback.after_aggregation &&
>                                         ctx->callback.after_aggregation(ctx))
>                                 set_kdamond_stop(ctx);
> +                       if (ctx->target_type != DAMON_ARBITRARY_TARGET)
> +                               kdamond_reset_aggregated(ctx);
>                         if (ctx->primitive.reset_aggregated)
>                                 ctx->primitive.reset_aggregated(ctx);
>                 }
> @@ -296,6 +426,12 @@ static int kdamond_fn(void *data)
>                                 ctx->primitive.update_target_regions(ctx);
>                 }
>         }
> +       if (ctx->target_type != DAMON_ARBITRARY_TARGET) {
> +               damon_for_each_target(t, ctx) {
> +                       damon_for_each_region_safe(r, next, t)
> +                               damon_destroy_region(r);
> +               }
> +       }
>
>         if (ctx->callback.before_terminate &&
>                         ctx->callback.before_terminate(ctx))
> --
> 2.17.1
>



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux