1369c415eSSeongJae Park // SPDX-License-Identifier: GPL-2.0 2369c415eSSeongJae Park /* 3369c415eSSeongJae Park * Shows data access monitoring resutls in simple metrics. 4369c415eSSeongJae Park */ 5369c415eSSeongJae Park 6369c415eSSeongJae Park #define pr_fmt(fmt) "damon-stat: " fmt 7369c415eSSeongJae Park 8369c415eSSeongJae Park #include <linux/damon.h> 9369c415eSSeongJae Park #include <linux/init.h> 10369c415eSSeongJae Park #include <linux/kernel.h> 11369c415eSSeongJae Park #include <linux/module.h> 12369c415eSSeongJae Park #include <linux/sort.h> 13369c415eSSeongJae Park 14369c415eSSeongJae Park #ifdef MODULE_PARAM_PREFIX 15369c415eSSeongJae Park #undef MODULE_PARAM_PREFIX 16369c415eSSeongJae Park #endif 17369c415eSSeongJae Park #define MODULE_PARAM_PREFIX "damon_stat." 18369c415eSSeongJae Park 19369c415eSSeongJae Park static int damon_stat_enabled_store( 20369c415eSSeongJae Park const char *val, const struct kernel_param *kp); 21369c415eSSeongJae Park 22369c415eSSeongJae Park static const struct kernel_param_ops enabled_param_ops = { 23369c415eSSeongJae Park .set = damon_stat_enabled_store, 24369c415eSSeongJae Park .get = param_get_bool, 25369c415eSSeongJae Park }; 26369c415eSSeongJae Park 27369c415eSSeongJae Park static bool enabled __read_mostly = IS_ENABLED( 28369c415eSSeongJae Park CONFIG_DAMON_STAT_ENABLED_DEFAULT); 29369c415eSSeongJae Park module_param_cb(enabled, &enabled_param_ops, &enabled, 0600); 30369c415eSSeongJae Park MODULE_PARM_DESC(enabled, "Enable of disable DAMON_STAT"); 31369c415eSSeongJae Park 32fabdd1e9SSeongJae Park static unsigned long estimated_memory_bandwidth __read_mostly; 33fabdd1e9SSeongJae Park module_param(estimated_memory_bandwidth, ulong, 0400); 34fabdd1e9SSeongJae Park MODULE_PARM_DESC(estimated_memory_bandwidth, 35fabdd1e9SSeongJae Park "Estimated memory bandwidth usage in bytes per second"); 36fabdd1e9SSeongJae Park 37e5d2585dSSeongJae Park static unsigned long memory_idle_ms_percentiles[101] __read_mostly = {0,}; 38e5d2585dSSeongJae Park module_param_array(memory_idle_ms_percentiles, ulong, NULL, 0400); 39e5d2585dSSeongJae Park MODULE_PARM_DESC(memory_idle_ms_percentiles, 40e5d2585dSSeongJae Park "Memory idle time percentiles in milliseconds"); 41e5d2585dSSeongJae Park 42369c415eSSeongJae Park static struct damon_ctx *damon_stat_context; 43369c415eSSeongJae Park 44fabdd1e9SSeongJae Park static void damon_stat_set_estimated_memory_bandwidth(struct damon_ctx *c) 45fabdd1e9SSeongJae Park { 46fabdd1e9SSeongJae Park struct damon_target *t; 47fabdd1e9SSeongJae Park struct damon_region *r; 48fabdd1e9SSeongJae Park unsigned long access_bytes = 0; 49fabdd1e9SSeongJae Park 50fabdd1e9SSeongJae Park damon_for_each_target(t, c) { 51fabdd1e9SSeongJae Park damon_for_each_region(r, t) 52fabdd1e9SSeongJae Park access_bytes += (r->ar.end - r->ar.start) * 53fabdd1e9SSeongJae Park r->nr_accesses; 54fabdd1e9SSeongJae Park } 55fabdd1e9SSeongJae Park estimated_memory_bandwidth = access_bytes * USEC_PER_MSEC * 56fabdd1e9SSeongJae Park MSEC_PER_SEC / c->attrs.aggr_interval; 57fabdd1e9SSeongJae Park } 58fabdd1e9SSeongJae Park 59e5d2585dSSeongJae Park static unsigned int damon_stat_idletime(const struct damon_region *r) 60e5d2585dSSeongJae Park { 61e5d2585dSSeongJae Park if (r->nr_accesses) 62e5d2585dSSeongJae Park return 0; 63e5d2585dSSeongJae Park return r->age + 1; 64e5d2585dSSeongJae Park } 65e5d2585dSSeongJae Park 66e5d2585dSSeongJae Park static int damon_stat_cmp_regions(const void *a, const void *b) 67e5d2585dSSeongJae Park { 68e5d2585dSSeongJae Park const struct damon_region *ra = *(const struct damon_region **)a; 69e5d2585dSSeongJae Park const struct damon_region *rb = *(const struct damon_region **)b; 70e5d2585dSSeongJae Park 71e5d2585dSSeongJae Park return damon_stat_idletime(ra) - damon_stat_idletime(rb); 72e5d2585dSSeongJae Park } 73e5d2585dSSeongJae Park 74e5d2585dSSeongJae Park static int damon_stat_sort_regions(struct damon_ctx *c, 75e5d2585dSSeongJae Park struct damon_region ***sorted_ptr, int *nr_regions_ptr, 76e5d2585dSSeongJae Park unsigned long *total_sz_ptr) 77e5d2585dSSeongJae Park { 78e5d2585dSSeongJae Park struct damon_target *t; 79e5d2585dSSeongJae Park struct damon_region *r; 80e5d2585dSSeongJae Park struct damon_region **region_pointers; 81e5d2585dSSeongJae Park unsigned int nr_regions = 0; 82e5d2585dSSeongJae Park unsigned long total_sz = 0; 83e5d2585dSSeongJae Park 84e5d2585dSSeongJae Park damon_for_each_target(t, c) { 85e5d2585dSSeongJae Park /* there is only one target */ 86e5d2585dSSeongJae Park region_pointers = kmalloc_array(damon_nr_regions(t), 87e5d2585dSSeongJae Park sizeof(*region_pointers), GFP_KERNEL); 88e5d2585dSSeongJae Park if (!region_pointers) 89e5d2585dSSeongJae Park return -ENOMEM; 90e5d2585dSSeongJae Park damon_for_each_region(r, t) { 91e5d2585dSSeongJae Park region_pointers[nr_regions++] = r; 92e5d2585dSSeongJae Park total_sz += r->ar.end - r->ar.start; 93e5d2585dSSeongJae Park } 94e5d2585dSSeongJae Park } 95e5d2585dSSeongJae Park sort(region_pointers, nr_regions, sizeof(*region_pointers), 96e5d2585dSSeongJae Park damon_stat_cmp_regions, NULL); 97e5d2585dSSeongJae Park *sorted_ptr = region_pointers; 98e5d2585dSSeongJae Park *nr_regions_ptr = nr_regions; 99e5d2585dSSeongJae Park *total_sz_ptr = total_sz; 100e5d2585dSSeongJae Park return 0; 101e5d2585dSSeongJae Park } 102e5d2585dSSeongJae Park 103e5d2585dSSeongJae Park static void damon_stat_set_idletime_percentiles(struct damon_ctx *c) 104e5d2585dSSeongJae Park { 105e5d2585dSSeongJae Park struct damon_region **sorted_regions, *region; 106e5d2585dSSeongJae Park int nr_regions; 107e5d2585dSSeongJae Park unsigned long total_sz, accounted_bytes = 0; 108e5d2585dSSeongJae Park int err, i, next_percentile = 0; 109e5d2585dSSeongJae Park 110e5d2585dSSeongJae Park err = damon_stat_sort_regions(c, &sorted_regions, &nr_regions, 111e5d2585dSSeongJae Park &total_sz); 112e5d2585dSSeongJae Park if (err) 113e5d2585dSSeongJae Park return; 114e5d2585dSSeongJae Park for (i = 0; i < nr_regions; i++) { 115e5d2585dSSeongJae Park region = sorted_regions[i]; 116e5d2585dSSeongJae Park accounted_bytes += region->ar.end - region->ar.start; 117e5d2585dSSeongJae Park while (next_percentile <= accounted_bytes * 100 / total_sz) 118e5d2585dSSeongJae Park memory_idle_ms_percentiles[next_percentile++] = 119e5d2585dSSeongJae Park damon_stat_idletime(region) * 120e5d2585dSSeongJae Park c->attrs.aggr_interval / USEC_PER_MSEC; 121e5d2585dSSeongJae Park } 122e5d2585dSSeongJae Park kfree(sorted_regions); 123e5d2585dSSeongJae Park } 124e5d2585dSSeongJae Park 125*405f6199SSeongJae Park static int damon_stat_damon_call_fn(void *data) 126fabdd1e9SSeongJae Park { 127*405f6199SSeongJae Park struct damon_ctx *c = data; 128fabdd1e9SSeongJae Park static unsigned long last_refresh_jiffies; 129fabdd1e9SSeongJae Park 130fabdd1e9SSeongJae Park /* avoid unnecessarily frequent stat update */ 131fabdd1e9SSeongJae Park if (time_before_eq(jiffies, last_refresh_jiffies + 132fabdd1e9SSeongJae Park msecs_to_jiffies(5 * MSEC_PER_SEC))) 133fabdd1e9SSeongJae Park return 0; 134fabdd1e9SSeongJae Park last_refresh_jiffies = jiffies; 135fabdd1e9SSeongJae Park 136fabdd1e9SSeongJae Park damon_stat_set_estimated_memory_bandwidth(c); 137e5d2585dSSeongJae Park damon_stat_set_idletime_percentiles(c); 138fabdd1e9SSeongJae Park return 0; 139fabdd1e9SSeongJae Park } 140fabdd1e9SSeongJae Park 141369c415eSSeongJae Park static struct damon_ctx *damon_stat_build_ctx(void) 142369c415eSSeongJae Park { 143369c415eSSeongJae Park struct damon_ctx *ctx; 144369c415eSSeongJae Park struct damon_attrs attrs; 145369c415eSSeongJae Park struct damon_target *target; 146369c415eSSeongJae Park unsigned long start = 0, end = 0; 147369c415eSSeongJae Park 148369c415eSSeongJae Park ctx = damon_new_ctx(); 149369c415eSSeongJae Park if (!ctx) 150369c415eSSeongJae Park return NULL; 151369c415eSSeongJae Park attrs = (struct damon_attrs) { 152369c415eSSeongJae Park .sample_interval = 5 * USEC_PER_MSEC, 153369c415eSSeongJae Park .aggr_interval = 100 * USEC_PER_MSEC, 154369c415eSSeongJae Park .ops_update_interval = 60 * USEC_PER_MSEC * MSEC_PER_SEC, 155369c415eSSeongJae Park .min_nr_regions = 10, 156369c415eSSeongJae Park .max_nr_regions = 1000, 157369c415eSSeongJae Park }; 158369c415eSSeongJae Park /* 159369c415eSSeongJae Park * auto-tune sampling and aggregation interval aiming 4% DAMON-observed 160369c415eSSeongJae Park * accesses ratio, keeping sampling interval in [5ms, 10s] range. 161369c415eSSeongJae Park */ 162369c415eSSeongJae Park attrs.intervals_goal = (struct damon_intervals_goal) { 163369c415eSSeongJae Park .access_bp = 400, .aggrs = 3, 164369c415eSSeongJae Park .min_sample_us = 5000, .max_sample_us = 10000000, 165369c415eSSeongJae Park }; 166369c415eSSeongJae Park if (damon_set_attrs(ctx, &attrs)) 167369c415eSSeongJae Park goto free_out; 168369c415eSSeongJae Park 169369c415eSSeongJae Park /* 170369c415eSSeongJae Park * auto-tune sampling and aggregation interval aiming 4% DAMON-observed 171369c415eSSeongJae Park * accesses ratio, keeping sampling interval in [5ms, 10s] range. 172369c415eSSeongJae Park */ 173369c415eSSeongJae Park ctx->attrs.intervals_goal = (struct damon_intervals_goal) { 174369c415eSSeongJae Park .access_bp = 400, .aggrs = 3, 175369c415eSSeongJae Park .min_sample_us = 5000, .max_sample_us = 10000000, 176369c415eSSeongJae Park }; 177369c415eSSeongJae Park if (damon_select_ops(ctx, DAMON_OPS_PADDR)) 178369c415eSSeongJae Park goto free_out; 179369c415eSSeongJae Park 180369c415eSSeongJae Park target = damon_new_target(); 181369c415eSSeongJae Park if (!target) 182369c415eSSeongJae Park goto free_out; 183369c415eSSeongJae Park damon_add_target(ctx, target); 184369c415eSSeongJae Park if (damon_set_region_biggest_system_ram_default(target, &start, &end)) 185369c415eSSeongJae Park goto free_out; 186369c415eSSeongJae Park return ctx; 187369c415eSSeongJae Park free_out: 188369c415eSSeongJae Park damon_destroy_ctx(ctx); 189369c415eSSeongJae Park return NULL; 190369c415eSSeongJae Park } 191369c415eSSeongJae Park 192*405f6199SSeongJae Park static struct damon_call_control call_control = { 193*405f6199SSeongJae Park .fn = damon_stat_damon_call_fn, 194*405f6199SSeongJae Park .repeat = true, 195*405f6199SSeongJae Park }; 196*405f6199SSeongJae Park 197369c415eSSeongJae Park static int damon_stat_start(void) 198369c415eSSeongJae Park { 199*405f6199SSeongJae Park int err; 200*405f6199SSeongJae Park 201369c415eSSeongJae Park damon_stat_context = damon_stat_build_ctx(); 202369c415eSSeongJae Park if (!damon_stat_context) 203369c415eSSeongJae Park return -ENOMEM; 204*405f6199SSeongJae Park err = damon_start(&damon_stat_context, 1, true); 205*405f6199SSeongJae Park if (err) 206*405f6199SSeongJae Park return err; 207*405f6199SSeongJae Park call_control.data = damon_stat_context; 208*405f6199SSeongJae Park return damon_call(damon_stat_context, &call_control); 209369c415eSSeongJae Park } 210369c415eSSeongJae Park 211369c415eSSeongJae Park static void damon_stat_stop(void) 212369c415eSSeongJae Park { 213369c415eSSeongJae Park damon_stop(&damon_stat_context, 1); 214369c415eSSeongJae Park damon_destroy_ctx(damon_stat_context); 215369c415eSSeongJae Park } 216369c415eSSeongJae Park 217369c415eSSeongJae Park static bool damon_stat_init_called; 218369c415eSSeongJae Park 219369c415eSSeongJae Park static int damon_stat_enabled_store( 220369c415eSSeongJae Park const char *val, const struct kernel_param *kp) 221369c415eSSeongJae Park { 222369c415eSSeongJae Park bool is_enabled = enabled; 223369c415eSSeongJae Park int err; 224369c415eSSeongJae Park 225369c415eSSeongJae Park err = kstrtobool(val, &enabled); 226369c415eSSeongJae Park if (err) 227369c415eSSeongJae Park return err; 228369c415eSSeongJae Park 229369c415eSSeongJae Park if (is_enabled == enabled) 230369c415eSSeongJae Park return 0; 231369c415eSSeongJae Park 232369c415eSSeongJae Park if (!damon_stat_init_called) 233369c415eSSeongJae Park /* 234369c415eSSeongJae Park * probably called from command line parsing (parse_args()). 235369c415eSSeongJae Park * Cannot call damon_new_ctx(). Let damon_stat_init() handle. 236369c415eSSeongJae Park */ 237369c415eSSeongJae Park return 0; 238369c415eSSeongJae Park 239369c415eSSeongJae Park if (enabled) { 240369c415eSSeongJae Park err = damon_stat_start(); 241369c415eSSeongJae Park if (err) 242369c415eSSeongJae Park enabled = false; 243369c415eSSeongJae Park return err; 244369c415eSSeongJae Park } 245369c415eSSeongJae Park damon_stat_stop(); 246369c415eSSeongJae Park return 0; 247369c415eSSeongJae Park } 248369c415eSSeongJae Park 249369c415eSSeongJae Park static int __init damon_stat_init(void) 250369c415eSSeongJae Park { 251369c415eSSeongJae Park int err = 0; 252369c415eSSeongJae Park 253369c415eSSeongJae Park damon_stat_init_called = true; 254369c415eSSeongJae Park 255369c415eSSeongJae Park /* probably set via command line */ 256369c415eSSeongJae Park if (enabled) 257369c415eSSeongJae Park err = damon_stat_start(); 258369c415eSSeongJae Park 259369c415eSSeongJae Park if (err && enabled) 260369c415eSSeongJae Park enabled = false; 261369c415eSSeongJae Park return err; 262369c415eSSeongJae Park } 263369c415eSSeongJae Park 264369c415eSSeongJae Park module_init(damon_stat_init); 265