[LTP] [PATCH v4] memory: rewrite memcg_stress_test into C API

Li Wang liwang@redhat.com
Sat Nov 29 06:58:34 CET 2025


On Fri, Nov 28, 2025 at 6:24 PM Li Wang <liwang@redhat.com> wrote:

> 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.
>


Also, we could use a reasonable heuristic way to scale concurrency
with CPU count, which keeps the configuration simple for getting
more cgroup load on bigger machines:
-
    cgroups_num = MAX(cgrups_num, tst_ncpus());

The key to the design is to increase the number of Cgroups while
allocating a smaller chunk of memory per iteration, so that
tst_remaing_runtime() can work properly.

Then, we can replace `iter_per_cgroup` with an infinite loop,
so that memory allocation for a single Cgroup continues until the
LTP runtime gracefully exits.



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
>


-- 
Regards,
Li Wang


More information about the ltp mailing list