[LTP] [PATCH v3] memory: rewrite memcg_stress_test into C API
Andrea Cervesato
andrea.cervesato@suse.de
Tue Nov 11 14:46:23 CET 2025
From: 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.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
The previous test was buggy and up to failures. This new implementation
deletes shell script code and the C utility used by it, merging the code
into C API libray.
---
Changes in v3:
- set runtime to 15 minutes
- run cgroups allocations in parallel
- each cgroup will perform M iterations
- Link to v2: https://lore.kernel.org/r/20251106-b4-memcg_stress_rewrite-v2-1-a82b0f3f09ac@suse.com
Changes in v2:
- runtime 30 minutes to make sure we can finish the test inside systems
with lots of RAM
- use mem_avail only
- remove sleep() after cgroup creation
- fix controllers runtime
- better doc
- Link to v1: https://lore.kernel.org/r/20251105-b4-memcg_stress_rewrite-v1-1-5e354dd5439f@suse.com
---
runtest/controllers | 3 +-
.../kernel/controllers/memcg/stress/.gitignore | 1 +
testcases/kernel/controllers/memcg/stress/Makefile | 6 +-
.../memcg/stress/memcg_process_stress.c | 96 -------------
.../controllers/memcg/stress/memcg_stress01.c | 149 +++++++++++++++++++++
.../controllers/memcg/stress/memcg_stress_test.sh | 108 ---------------
6 files changed, 153 insertions(+), 210 deletions(-)
diff --git a/runtest/controllers b/runtest/controllers
index 93c52c439..0526ac683 100644
--- a/runtest/controllers
+++ b/runtest/controllers
@@ -16,7 +16,8 @@ memcg_memsw_limit_in_bytes memcg_memsw_limit_in_bytes_test.sh
memcg_stat memcg_stat_test.sh
memcg_use_hierarchy memcg_use_hierarchy_test.sh
memcg_usage_in_bytes memcg_usage_in_bytes_test.sh
-memcg_stress memcg_stress_test.sh
+memcg_stress01_1 memcg_stress01 -n 1
+memcg_stress01_2 memcg_stress01 -n 150
memcg_control memcg_control_test.sh
# kselftest ports
diff --git a/testcases/kernel/controllers/memcg/stress/.gitignore b/testcases/kernel/controllers/memcg/stress/.gitignore
new file mode 100644
index 000000000..17df954ed
--- /dev/null
+++ b/testcases/kernel/controllers/memcg/stress/.gitignore
@@ -0,0 +1 @@
+memcg_stress01
diff --git a/testcases/kernel/controllers/memcg/stress/Makefile b/testcases/kernel/controllers/memcg/stress/Makefile
index a9678bf3b..8413e98f9 100644
--- a/testcases/kernel/controllers/memcg/stress/Makefile
+++ b/testcases/kernel/controllers/memcg/stress/Makefile
@@ -6,10 +6,6 @@ top_srcdir ?= ../../../../..
include $(top_srcdir)/include/mk/testcases.mk
-CPPFLAGS += -I$(abs_srcdir)/../../libcontrollers
-
-INSTALL_TARGETS := *.sh
-
LDLIBS += -lm
-include $(top_srcdir)/include/mk/generic_leaf_target.mk
+include $(top_srcdir)/include/mk/generic_trunk_target.mk
diff --git a/testcases/kernel/controllers/memcg/stress/memcg_process_stress.c b/testcases/kernel/controllers/memcg/stress/memcg_process_stress.c
deleted file mode 100644
index 422deaeee..000000000
--- a/testcases/kernel/controllers/memcg/stress/memcg_process_stress.c
+++ /dev/null
@@ -1,96 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright (c) 2009 FUJITSU LIMITED
- * Author: Li Zefan <lizf@cn.fujitsu.com>
- */
-
-#include <sys/mman.h>
-#include <err.h>
-#include <math.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-int flag_exit;
-int flag_ready;
-
-int interval;
-unsigned long memsize;
-
-char **pages;
-int nr_page;
-
-void touch_memory(void)
-{
- int i;
-
- for (i = 0; i < nr_page; i++)
- pages[i][0] = 0xef;
-}
-
-void sigusr_handler(int __attribute__ ((unused)) signo)
-{
- int i;
- int pagesize;
-
- pagesize = getpagesize();
-
- nr_page = ceil((double)memsize / pagesize);
-
- pages = calloc(nr_page, sizeof(char *));
- if (pages == NULL)
- errx(1, "calloc failed");
-
- for (i = 0; i < nr_page; i++) {
- pages[i] = mmap(NULL, pagesize, PROT_WRITE | PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
- if (pages[i] == MAP_FAILED)
- err(1, "map failed\n");
- }
-
- flag_ready = 1;
-}
-
-void sigint_handler(int __attribute__ ((unused)) signo)
-{
- flag_exit = 1;
-}
-
-int main(int argc, char *argv[])
-{
- char *end;
- struct sigaction sigint_action;
- struct sigaction sigusr_action;
-
- if (argc != 3)
- errx(1, "wrong argument num");
-
- memsize = strtoul(argv[1], &end, 10);
- if (*end != '\0')
- errx(1, "wrong memsize");
- memsize = memsize * 1024 * 1024;
-
- interval = atoi(argv[2]);
- if (interval <= 0)
- interval = 1;
-
- memset(&sigint_action, 0, sizeof(sigint_action));
- sigint_action.sa_handler = &sigint_handler;
- if (sigaction(SIGINT, &sigint_action, NULL))
- err(1, "sigaction(%s) failed", "SIGINT");
-
- memset(&sigusr_action, 0, sizeof(sigusr_action));
- sigusr_action.sa_handler = &sigusr_handler;
- if (sigaction(SIGUSR1, &sigusr_action, NULL))
- err(1, "sigaction(%s) failed", "SIGUSR1");
-
- while (!flag_exit) {
- sleep(interval);
-
- if (flag_ready)
- touch_memory();
- }
-
- return 0;
-}
diff --git a/testcases/kernel/controllers/memcg/stress/memcg_stress01.c b/testcases/kernel/controllers/memcg/stress/memcg_stress01.c
new file mode 100644
index 000000000..56873ab4d
--- /dev/null
+++ b/testcases/kernel/controllers/memcg/stress/memcg_stress01.c
@@ -0,0 +1,149 @@
+// 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 tst_atomic_t *cgroups_avail;
+
+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++) {
+ while (!*cgroups_avail)
+ sched_yield();
+
+ tst_atomic_dec(cgroups_avail);
+
+ 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++)
+ *(pages[j]) = 0xef;
+
+ for (int j = 0; j < pages_num; j++)
+ SAFE_MUNMAP(pages[j], sizeof(char));
+
+ tst_atomic_inc(cgroups_avail);
+
+ 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);
+
+ cgroups_avail = SAFE_MMAP(NULL, sizeof(int *),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS,
+ -1, 0);
+
+ *cgroups_avail = cgroups_num;
+}
+
+static void cleanup(void)
+{
+ if (cgroups_avail)
+ SAFE_MUNMAP(cgroups_avail, sizeof(int *));
+}
+
+static struct tst_test test = {
+ .setup = setup,
+ .cleanup = cleanup,
+ .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)"},
+ {}
+ },
+};
diff --git a/testcases/kernel/controllers/memcg/stress/memcg_stress_test.sh b/testcases/kernel/controllers/memcg/stress/memcg_stress_test.sh
deleted file mode 100755
index 47cac9af8..000000000
--- a/testcases/kernel/controllers/memcg/stress/memcg_stress_test.sh
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0-or-later
-# Copyright (c) 2009 FUJITSU LIMITED
-# Copyright (c) 2018-2019 ARM Ltd. All Rights Reserved.
-# Copyright (c) 2019-2022 Petr Vorel <pvorel@suse.cz>
-#
-# Author: Li Zefan <lizf@cn.fujitsu.com>
-# Restructure for LTP: Shi Weihua <shiwh@cn.fujitsu.com>
-# Added memcg enable/disable functionality: Rishikesh K Rajak <risrajak@linux.vnet.ibm.com>
-
-TST_TESTFUNC=test
-TST_SETUP=setup
-TST_CLEANUP=cleanup
-TST_CNT=2
-TST_NEEDS_ROOT=1
-TST_NEEDS_CMDS="mount umount cat kill mkdir rmdir grep awk cut"
-
-# Each test case runs for 900 secs when everything fine
-# therefore the default 5 mins timeout is not enough.
-TST_TIMEOUT=2100
-
-setup()
-{
- cgroup_require "memory"
- cgroup_version=$(cgroup_get_version "memory")
- test_path=$(cgroup_get_test_path "memory")
- task_list=$(cgroup_get_task_list "memory")
- if [ "$cgroup_version" = "2" ]; then
- ROD echo "+memory" \> "$test_path/cgroup.subtree_control"
- fi
-
- tst_res TINFO "test starts with cgroup version $cgroup_version"
-
- echo 3 > /proc/sys/vm/drop_caches
- sleep 2
- local mem_free=$(awk '/MemFree/ {print $2}' /proc/meminfo)
- local mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
- local swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo)
- local mem_min=$(cat /proc/sys/vm/min_free_kbytes)
-
- mem_min=$(( $mem_min + $mem_min/10 ))
- [ $swap_free -gt $mem_min ] && RESERVED_MEM=0 || RESERVED_MEM=$mem_min
- [ $mem_free -lt $mem_available ] && MEM=$mem_free || MEM=$mem_available
- MEM=$(( $MEM - $RESERVED_MEM ))
- MEM=$(( $MEM / 1024 ))
- RUN_TIME=$(( 15 * 60 ))
-
- tst_res TINFO "Calculated available memory $MEM MB"
-}
-
-cleanup()
-{
- cgroup_cleanup
-}
-
-# $1 Number of cgroups
-# $2 Allocated MB memory in one process
-# $3 The interval to touch memory in a process
-# $4 Test duration (sec)
-run_stress()
-{
- local cgroups="$1"
- local mem_size="$2"
- local interval="$3"
- local timeout="$4"
- local i pid pids
-
- tst_res TINFO "Testing $cgroups cgroups, using $mem_size MB, interval $interval"
-
- tst_res TINFO "Starting cgroups"
- for i in $(seq 0 $(($cgroups-1))); do
- ROD mkdir "$test_path/$i"
- memcg_process_stress $mem_size $interval &
- ROD echo $! \> "$test_path/$i/$task_list"
- pids="$pids $!"
- done
-
- for pid in $pids; do
- kill -USR1 $pid 2> /dev/null
- done
-
- tst_res TINFO "Testing cgroups for ${timeout}s"
- sleep $timeout
-
- tst_res TINFO "Killing groups"
- i=0
- for pid in $pids; do
- kill -KILL $pid 2> /dev/null
- wait $pid 2> /dev/null
- ROD rmdir "$test_path/$i"
- i=$((i+1))
- done
-
- tst_res TPASS "Test passed"
-}
-
-test1()
-{
- run_stress 150 $(( $MEM / 150 )) 5 $RUN_TIME
-}
-
-test2()
-{
- run_stress 1 $MEM 5 $RUN_TIME
-}
-
-. cgroup_lib.sh
-tst_run
---
base-commit: 69e0ff4e7f8a99761e9c8cb5699b1e966a1f1e27
change-id: 20251105-b4-memcg_stress_rewrite-8a133288f7d0
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
More information about the ltp
mailing list