[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