mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
Everything: Total patches: 121 Reviews/patch: 2.11 Reviewed rate: 90% Excluding DAMON: Total patches: 113 Reviews/patch: 2.25 Reviewed rate: 96% - The 33 patch series "Eliminate Dying Memory Cgroup" from Qi Zheng and Muchun Song addresses the longstanding "dying memcg problem". A situation wherein a no-longer-used memory control group will hang around for an extended period pointlessly consuming memory. The [0/N] changelog has a good overview of this work. - The 3 patch series "fix unexpected type conversions and potential overflows" from Qi Zheng fixes a couple of potential 32-bit/64-bit issues which were identified during review of the "Eliminate Dying Memory Cgroup" series. - The 6 patch series "kho: history: track previous kernel version and kexec boot count" from Breno Leitao uses Kexec Handover (KHO) to pass the previous kernel's version string and the number of kexec reboots since the last cold boot to the next kernel, and prints it at boot time. - The 4 patch series "liveupdate: prevent double preservation" from Pasha Tatashin teaches LUO to avoid managing the same file across different active sessions. - The 10 patch series "liveupdate: Fix module unloading and unregister API" from Pasha Tatashin addresses an issue with how LUO handles module reference counting and unregistration during module unloading. - The 2 patch series "zswap pool per-CPU acomp_ctx simplifications" from Kanchana Sridhar simplifies and cleans up the zswap crypto compression handling and improves the lifecycle management of zswap pool's per-CPU acomp_ctx resources. - The 2 patch series "mm/damon/core: fix damon_call()/damos_walk() vs kdmond exit race" from SeongJae Park addresses unlikely but possible leaks and deadlocks in damon_call() and damon_walk(). - The 2 patch series "mm/damon/core: validate damos_quota_goal->nid" from SeongJae Park fixes a couple of root-only wild pointer dereferences. - The 2 patch series "Docs/admin-guide/mm/damon: warn commit_inputs vs other params race" from SeongJae Park updates the DAMON documentation to warn operators about potential races which can occur if the commit_inputs parameter is altered at the wrong time. - The 3 patch series "Minor hmm_test fixes and cleanups" from Alistair Popple implements two bugfixes a cleanup for the HMM kernel selftests. - The 6 patch series "Modify memfd_luo code" from Chenghao Duan provides cleanups, simplifications and speedups in the memfd_lou code. - The 4 patch series "mm, kvm: allow uffd support in guest_memfd" from Mike Rapoport enables support for userfaultfd in guest_memfd. - The 6 patch series "selftests/mm: skip several tests when thp is not available" from Chunyu Hu fixes several issues in the selftests code which were causing breakage when the tests were run on CONFIG_THP=n kernels. - The 2 patch series "mm/mprotect: micro-optimization work" from Pedro Falcato implements a couple of nice speedups for mprotect(). - The 3 patch series "MAINTAINERS: update KHO and LIVE UPDATE entries" from Pratyush Yadav reflects upcoming changes in the maintenance of KHO, LUO, memfd_luo, kexec, crash, kdump and probably other kexec-based things - they are being moved out of mm.git and into a new git tree. -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQTTMBEPP41GrTpTJgfdBJ7gKXxAjgUCaeNL/wAKCRDdBJ7gKXxA jt7EAQCEEQvYYTjld+8HJKsCbavY4pEfci7z4SBiQyIPjRracQD/ZfjXnzL7ucc1 b6q6G4TcslvIDBgzVkk9G2BVn2oCoAg= =3ozv -----END PGP SIGNATURE----- Merge tag 'mm-stable-2026-04-18-02-14' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull more MM updates from Andrew Morton: - "Eliminate Dying Memory Cgroup" (Qi Zheng and Muchun Song) Address the longstanding "dying memcg problem". A situation wherein a no-longer-used memory control group will hang around for an extended period pointlessly consuming memory - "fix unexpected type conversions and potential overflows" (Qi Zheng) Fix a couple of potential 32-bit/64-bit issues which were identified during review of the "Eliminate Dying Memory Cgroup" series - "kho: history: track previous kernel version and kexec boot count" (Breno Leitao) Use Kexec Handover (KHO) to pass the previous kernel's version string and the number of kexec reboots since the last cold boot to the next kernel, and print it at boot time - "liveupdate: prevent double preservation" (Pasha Tatashin) Teach LUO to avoid managing the same file across different active sessions - "liveupdate: Fix module unloading and unregister API" (Pasha Tatashin) Address an issue with how LUO handles module reference counting and unregistration during module unloading - "zswap pool per-CPU acomp_ctx simplifications" (Kanchana Sridhar) Simplify and clean up the zswap crypto compression handling and improve the lifecycle management of zswap pool's per-CPU acomp_ctx resources - "mm/damon/core: fix damon_call()/damos_walk() vs kdmond exit race" (SeongJae Park) Address unlikely but possible leaks and deadlocks in damon_call() and damon_walk() - "mm/damon/core: validate damos_quota_goal->nid" (SeongJae Park) Fix a couple of root-only wild pointer dereferences - "Docs/admin-guide/mm/damon: warn commit_inputs vs other params race" (SeongJae Park) Update the DAMON documentation to warn operators about potential races which can occur if the commit_inputs parameter is altered at the wrong time - "Minor hmm_test fixes and cleanups" (Alistair Popple) Bugfixes and a cleanup for the HMM kernel selftests - "Modify memfd_luo code" (Chenghao Duan) Cleanups, simplifications and speedups to the memfd_lou code - "mm, kvm: allow uffd support in guest_memfd" (Mike Rapoport) Support for userfaultfd in guest_memfd - "selftests/mm: skip several tests when thp is not available" (Chunyu Hu) Fix several issues in the selftests code which were causing breakage when the tests were run on CONFIG_THP=n kernels - "mm/mprotect: micro-optimization work" (Pedro Falcato) A couple of nice speedups for mprotect() - "MAINTAINERS: update KHO and LIVE UPDATE entries" (Pratyush Yadav) Document upcoming changes in the maintenance of KHO, LUO, memfd_luo, kexec, crash, kdump and probably other kexec-based things - they are being moved out of mm.git and into a new git tree * tag 'mm-stable-2026-04-18-02-14' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (121 commits) MAINTAINERS: add page cache reviewer mm/vmscan: avoid false-positive -Wuninitialized warning MAINTAINERS: update Dave's kdump reviewer email address MAINTAINERS: drop include/linux/liveupdate from LIVE UPDATE MAINTAINERS: drop include/linux/kho/abi/ from KHO MAINTAINERS: update KHO and LIVE UPDATE maintainers MAINTAINERS: update kexec/kdump maintainers entries mm/migrate_device: remove dead migration entry check in migrate_vma_collect_huge_pmd() selftests: mm: skip charge_reserved_hugetlb without killall userfaultfd: allow registration of ranges below mmap_min_addr mm/vmstat: fix vmstat_shepherd double-scheduling vmstat_update mm/hugetlb: fix early boot crash on parameters without '=' separator zram: reject unrecognized type= values in recompress_store() docs: proc: document ProtectionKey in smaps mm/mprotect: special-case small folios when applying permissions mm/mprotect: move softleaf code out of the main function mm: remove '!root_reclaim' checking in should_abort_scan() mm/sparse: fix comment for section map alignment mm/page_io: use sio->len for PSWPIN accounting in sio_read_complete() selftests/mm: transhuge_stress: skip the test when thp not available ...
326 lines
8.1 KiB
C
326 lines
8.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Shows data access monitoring results in simple metrics.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "damon-stat: " fmt
|
|
|
|
#include <linux/damon.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sort.h>
|
|
|
|
#ifdef MODULE_PARAM_PREFIX
|
|
#undef MODULE_PARAM_PREFIX
|
|
#endif
|
|
#define MODULE_PARAM_PREFIX "damon_stat."
|
|
|
|
static int damon_stat_enabled_store(
|
|
const char *val, const struct kernel_param *kp);
|
|
|
|
static const struct kernel_param_ops enabled_param_ops = {
|
|
.set = damon_stat_enabled_store,
|
|
.get = param_get_bool,
|
|
};
|
|
|
|
static bool enabled __read_mostly = IS_ENABLED(
|
|
CONFIG_DAMON_STAT_ENABLED_DEFAULT);
|
|
module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
|
|
MODULE_PARM_DESC(enabled, "Enable of disable DAMON_STAT");
|
|
|
|
static unsigned long estimated_memory_bandwidth __read_mostly;
|
|
module_param(estimated_memory_bandwidth, ulong, 0400);
|
|
MODULE_PARM_DESC(estimated_memory_bandwidth,
|
|
"Estimated memory bandwidth usage in bytes per second");
|
|
|
|
static long memory_idle_ms_percentiles[101] = {0,};
|
|
module_param_array(memory_idle_ms_percentiles, long, NULL, 0400);
|
|
MODULE_PARM_DESC(memory_idle_ms_percentiles,
|
|
"Memory idle time percentiles in milliseconds");
|
|
|
|
static unsigned long aggr_interval_us;
|
|
module_param(aggr_interval_us, ulong, 0400);
|
|
MODULE_PARM_DESC(aggr_interval_us,
|
|
"Current tuned aggregation interval in microseconds");
|
|
|
|
static struct damon_ctx *damon_stat_context;
|
|
|
|
static unsigned long damon_stat_last_refresh_jiffies;
|
|
|
|
static void damon_stat_set_estimated_memory_bandwidth(struct damon_ctx *c)
|
|
{
|
|
struct damon_target *t;
|
|
struct damon_region *r;
|
|
unsigned long access_bytes = 0;
|
|
|
|
damon_for_each_target(t, c) {
|
|
damon_for_each_region(r, t)
|
|
access_bytes += (r->ar.end - r->ar.start) *
|
|
r->nr_accesses;
|
|
}
|
|
estimated_memory_bandwidth = access_bytes * USEC_PER_MSEC *
|
|
MSEC_PER_SEC / c->attrs.aggr_interval;
|
|
}
|
|
|
|
static int damon_stat_idletime(const struct damon_region *r)
|
|
{
|
|
if (r->nr_accesses)
|
|
return -1 * (r->age + 1);
|
|
return r->age + 1;
|
|
}
|
|
|
|
static int damon_stat_cmp_regions(const void *a, const void *b)
|
|
{
|
|
const struct damon_region *ra = *(const struct damon_region **)a;
|
|
const struct damon_region *rb = *(const struct damon_region **)b;
|
|
|
|
return damon_stat_idletime(ra) - damon_stat_idletime(rb);
|
|
}
|
|
|
|
static int damon_stat_sort_regions(struct damon_ctx *c,
|
|
struct damon_region ***sorted_ptr, int *nr_regions_ptr,
|
|
unsigned long *total_sz_ptr)
|
|
{
|
|
struct damon_target *t;
|
|
struct damon_region *r;
|
|
struct damon_region **region_pointers;
|
|
unsigned int nr_regions = 0;
|
|
unsigned long total_sz = 0;
|
|
|
|
damon_for_each_target(t, c) {
|
|
/* there is only one target */
|
|
region_pointers = kmalloc_objs(*region_pointers,
|
|
damon_nr_regions(t));
|
|
if (!region_pointers)
|
|
return -ENOMEM;
|
|
damon_for_each_region(r, t) {
|
|
region_pointers[nr_regions++] = r;
|
|
total_sz += r->ar.end - r->ar.start;
|
|
}
|
|
}
|
|
sort(region_pointers, nr_regions, sizeof(*region_pointers),
|
|
damon_stat_cmp_regions, NULL);
|
|
*sorted_ptr = region_pointers;
|
|
*nr_regions_ptr = nr_regions;
|
|
*total_sz_ptr = total_sz;
|
|
return 0;
|
|
}
|
|
|
|
static void damon_stat_set_idletime_percentiles(struct damon_ctx *c)
|
|
{
|
|
struct damon_region **sorted_regions, *region;
|
|
int nr_regions;
|
|
unsigned long total_sz, accounted_bytes = 0;
|
|
int err, i, next_percentile = 0;
|
|
|
|
err = damon_stat_sort_regions(c, &sorted_regions, &nr_regions,
|
|
&total_sz);
|
|
if (err)
|
|
return;
|
|
for (i = 0; i < nr_regions; i++) {
|
|
region = sorted_regions[i];
|
|
accounted_bytes += region->ar.end - region->ar.start;
|
|
while (next_percentile <= accounted_bytes * 100 / total_sz)
|
|
memory_idle_ms_percentiles[next_percentile++] =
|
|
damon_stat_idletime(region) *
|
|
(long)c->attrs.aggr_interval / USEC_PER_MSEC;
|
|
}
|
|
kfree(sorted_regions);
|
|
}
|
|
|
|
static int damon_stat_damon_call_fn(void *data)
|
|
{
|
|
struct damon_ctx *c = data;
|
|
|
|
/* avoid unnecessarily frequent stat update */
|
|
if (time_before_eq(jiffies, damon_stat_last_refresh_jiffies +
|
|
msecs_to_jiffies(5 * MSEC_PER_SEC)))
|
|
return 0;
|
|
damon_stat_last_refresh_jiffies = jiffies;
|
|
|
|
aggr_interval_us = c->attrs.aggr_interval;
|
|
damon_stat_set_estimated_memory_bandwidth(c);
|
|
damon_stat_set_idletime_percentiles(c);
|
|
return 0;
|
|
}
|
|
|
|
struct damon_stat_system_ram_range_walk_arg {
|
|
bool walked;
|
|
struct resource res;
|
|
};
|
|
|
|
static int damon_stat_system_ram_walk_fn(struct resource *res, void *arg)
|
|
{
|
|
struct damon_stat_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_stat_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 int damon_stat_set_monitoring_region(struct damon_target *t,
|
|
unsigned long addr_unit, unsigned long min_region_sz)
|
|
{
|
|
struct damon_addr_range addr_range;
|
|
struct damon_stat_system_ram_range_walk_arg arg = {};
|
|
|
|
walk_system_ram_res(0, -1, &arg, damon_stat_system_ram_walk_fn);
|
|
if (!arg.walked)
|
|
return -EINVAL;
|
|
addr_range.start = damon_stat_res_to_core_addr(
|
|
arg.res.start, addr_unit);
|
|
addr_range.end = damon_stat_res_to_core_addr(
|
|
arg.res.end + 1, addr_unit);
|
|
if (addr_range.end <= addr_range.start)
|
|
return -EINVAL;
|
|
return damon_set_regions(t, &addr_range, 1, min_region_sz);
|
|
}
|
|
|
|
static struct damon_ctx *damon_stat_build_ctx(void)
|
|
{
|
|
struct damon_ctx *ctx;
|
|
struct damon_attrs attrs;
|
|
struct damon_target *target;
|
|
|
|
ctx = damon_new_ctx();
|
|
if (!ctx)
|
|
return NULL;
|
|
attrs = (struct damon_attrs) {
|
|
.sample_interval = 5 * USEC_PER_MSEC,
|
|
.aggr_interval = 100 * USEC_PER_MSEC,
|
|
.ops_update_interval = 60 * USEC_PER_MSEC * MSEC_PER_SEC,
|
|
.min_nr_regions = 10,
|
|
.max_nr_regions = 1000,
|
|
};
|
|
/*
|
|
* auto-tune sampling and aggregation interval aiming 4% DAMON-observed
|
|
* accesses ratio, keeping sampling interval in [5ms, 10s] range.
|
|
*/
|
|
attrs.intervals_goal = (struct damon_intervals_goal) {
|
|
.access_bp = 400, .aggrs = 3,
|
|
.min_sample_us = 5000, .max_sample_us = 10000000,
|
|
};
|
|
if (damon_set_attrs(ctx, &attrs))
|
|
goto free_out;
|
|
|
|
if (damon_select_ops(ctx, DAMON_OPS_PADDR))
|
|
goto free_out;
|
|
|
|
target = damon_new_target();
|
|
if (!target)
|
|
goto free_out;
|
|
damon_add_target(ctx, target);
|
|
if (damon_stat_set_monitoring_region(target, ctx->addr_unit,
|
|
ctx->min_region_sz))
|
|
goto free_out;
|
|
return ctx;
|
|
free_out:
|
|
damon_destroy_ctx(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
static struct damon_call_control call_control = {
|
|
.fn = damon_stat_damon_call_fn,
|
|
.repeat = true,
|
|
};
|
|
|
|
static int damon_stat_start(void)
|
|
{
|
|
int err;
|
|
|
|
if (damon_stat_context) {
|
|
if (damon_is_running(damon_stat_context))
|
|
return -EAGAIN;
|
|
damon_destroy_ctx(damon_stat_context);
|
|
}
|
|
|
|
damon_stat_context = damon_stat_build_ctx();
|
|
if (!damon_stat_context)
|
|
return -ENOMEM;
|
|
err = damon_start(&damon_stat_context, 1, true);
|
|
if (err) {
|
|
damon_destroy_ctx(damon_stat_context);
|
|
damon_stat_context = NULL;
|
|
return err;
|
|
}
|
|
|
|
damon_stat_last_refresh_jiffies = jiffies;
|
|
call_control.data = damon_stat_context;
|
|
return damon_call(damon_stat_context, &call_control);
|
|
}
|
|
|
|
static void damon_stat_stop(void)
|
|
{
|
|
damon_stop(&damon_stat_context, 1);
|
|
damon_destroy_ctx(damon_stat_context);
|
|
damon_stat_context = NULL;
|
|
}
|
|
|
|
static int damon_stat_enabled_store(
|
|
const char *val, const struct kernel_param *kp)
|
|
{
|
|
bool is_enabled = enabled;
|
|
int err;
|
|
|
|
err = kstrtobool(val, &enabled);
|
|
if (err)
|
|
return err;
|
|
|
|
if (is_enabled == enabled)
|
|
return 0;
|
|
|
|
if (!damon_initialized())
|
|
/*
|
|
* probably called from command line parsing (parse_args()).
|
|
* Cannot call damon_new_ctx(). Let damon_stat_init() handle.
|
|
*/
|
|
return 0;
|
|
|
|
if (enabled) {
|
|
err = damon_stat_start();
|
|
if (err)
|
|
enabled = false;
|
|
return err;
|
|
}
|
|
damon_stat_stop();
|
|
return 0;
|
|
}
|
|
|
|
static int __init damon_stat_init(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!damon_initialized()) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* probably set via command line */
|
|
if (enabled)
|
|
err = damon_stat_start();
|
|
|
|
out:
|
|
if (err && enabled)
|
|
enabled = false;
|
|
return err;
|
|
}
|
|
|
|
module_init(damon_stat_init);
|