[LTP] [PATCH v4] memory: rewrite memcg_stress_test into C API
Li Wang
liwang@redhat.com
Fri Nov 28 11:24:09 CET 2025
Hi Andrea,
Thanks for rewriting the shell test, here I can see a few issues in this
stress test:
1. Unbounded memory demand
mem_per_proc is computed as “all available memory / number of cgroups.”
On a machine with, e.g., 512 GB free RAM, each child ends up trying to touch
~51 GB. With iter_per_cgroup = 100, that’s huge and easily exceeds the
default
runtime budget.
A better approach is to create a parent cgroup during setup() and cap its
memory.max to whatever total we’re willing to burn (for example, mem_avail/2
or a hard 4 GB ceiling). Place every child cgroup under that parent so they
inherit the aggregate limit, per-child limits can still apply, but the
parent cap
keeps the entire test bounded on multi-terabyte hosts.
2. Unhandled OOM kills
As written, each child allocates until the system OOM-killer intervenes, but
the parent just reaps them and reports TPASS. Instead, give each child
cgroup its own memory.max (slightly below the intended per-cgroup quota)
so allocations fail with ENOMEM rather than triggering global OOM.
Alternatively, enable existing LTP function such as
tst_enable_oom_protection()
to ensure failures are localized and handled gracefully. This lets you
detect
OOM conditions explicitly—treating SIGKILL + memcg OOM as expected,
while safeguarding the host.
Andrea Cervesato <andrea.cervesato@suse.de> wrote:
> --- /dev/null
> +++ b/testcases/kernel/controllers/memcg/stress/memcg_stress01.c
> @@ -0,0 +1,127 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 SUSE LLC Andrea Cervesato <
> andrea.cervesato@suse.com>
> + */
> +
> +/*\
> + * This test will stress the cgroup implementation by allocating the whole
> + * free system memory inside multiple containers, one page at time.
> + */
> +
> +#include <math.h>
> +#include "tst_test.h"
> +#include "tst_cgroup.h"
> +
> +#define MAX_RUNTIME 900
> +#define MAX_CGROUPS 1000
> +#define MAX_ITERATION 1000
> +
> +static char *str_cgroups_num;
> +static char *str_iter_per_cgroup;
> +static int cgroups_num = 10;
> +static int iter_per_cgroup = 100;
> +static long pages_num;
> +
> +static void run_child(struct tst_cg_group *cg_child)
> +{
> + char **pages;
> + int page_size;
> +
> + SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
> +
> + page_size = getpagesize();
> + pages = SAFE_CALLOC(pages_num, sizeof(char *));
> +
> + for (int i = 0; i < iter_per_cgroup; i++) {
> + for (int j = 0; j < pages_num; j++) {
> + pages[j] = (char *)SAFE_MMAP(NULL, page_size,
> + PROT_WRITE | PROT_READ,
> + MAP_PRIVATE | MAP_ANONYMOUS, 0,
> 0);
> + }
> +
> + for (int j = 0; j < pages_num; j++) {
> + memset(pages[j], 0xef, page_size);
> + SAFE_MLOCK(pages[j], page_size);
> + }
> +
> + for (int j = 0; j < pages_num; j++)
> + SAFE_MUNMAP(pages[j], sizeof(char));
>
SAFE_MUNMAP(pages[j], page_size);
+
> + if (!tst_remaining_runtime())
> + break;
> + }
> +}
> +
> +static void run(void)
> +{
> + struct tst_cg_group *cg_child[cgroups_num];
> + pid_t child_pid;
> +
> + for (int i = 0; i < cgroups_num; i++) {
> + cg_child[i] = tst_cg_group_mk(tst_cg,
> "ltp_memcg_stress_%d", i);
> +
> + child_pid = SAFE_FORK();
> + if (!child_pid) {
> + run_child(cg_child[i]);
> + exit(0);
> + }
> + }
> +
> + tst_reap_children();
> +
> + for (int i = 0; i < cgroups_num; i++)
> + cg_child[i] = tst_cg_group_rm(cg_child[i]);
> +
> + tst_res(TPASS, "Stress test has passed");
> +}
> +
> +static void setup(void)
> +{
> + unsigned long reserved_mem, mem_avail, swap_free, mem_min;
> + unsigned long mem_per_proc;
> + int page_size;
> +
> + if (tst_parse_int(str_cgroups_num, &cgroups_num, 1, MAX_CGROUPS))
> + tst_brk(TCONF, "Invalid number of cgroups: %s",
> str_cgroups_num);
> +
> + if (tst_parse_int(str_iter_per_cgroup, &iter_per_cgroup, 1,
> MAX_ITERATION))
> + tst_brk(TCONF, "Invalid iteration per cgroup: %s",
> str_iter_per_cgroup);
> +
> + SAFE_FILE_PRINTF("/proc/sys/vm/drop_caches", "3");
> +
> + mem_avail = SAFE_READ_MEMINFO("MemAvailable:");
> + swap_free = SAFE_READ_MEMINFO("SwapFree:");
> +
> + SAFE_FILE_SCANF("/proc/sys/vm/min_free_kbytes", "%zi", &mem_min);
> +
> + mem_min = mem_min + mem_min / 10;
> + reserved_mem = swap_free > mem_min ? 0 : mem_min;
> +
> + mem_per_proc = mem_avail - reserved_mem;
> + mem_per_proc /= cgroups_num;
> + mem_per_proc *= TST_KB;
> +
> + if (!mem_per_proc)
> + tst_brk(TCONF, "System memory has not enough available
> memory");
> +
> + page_size = getpagesize();
> + pages_num = ceil((double)mem_per_proc / page_size);
> +
> + tst_res(TINFO, "Testing %d cgroups allocating %ld MB for %d
> iteration",
> + cgroups_num, mem_per_proc / TST_MB, iter_per_cgroup);
> +}
> +
> +static struct tst_test test = {
> + .setup = setup,
> + .test_all = run,
> + .needs_root = 1,
> + .forks_child = 1,
> + .runtime = MAX_RUNTIME,
> + .needs_cgroup_ver = TST_CG_V2,
> + .needs_cgroup_ctrls = (const char *const []){ "memory", NULL },
> + .options = (struct tst_option []) {
> + {"n:", &str_cgroups_num, "Number of cgroups (default:
> 10)"},
> + {"m:", &str_iter_per_cgroup, "Number of iterations per
> cgroup (default: 100)"},
> + {}
> + },
> +};
>
--
Regards,
Li Wang
More information about the ltp
mailing list