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

Andrea Cervesato andrea.cervesato@suse.de
Thu Nov 6 09:16:06 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 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      | 118 +++++++++++++++++++++
 .../controllers/memcg/stress/memcg_stress_test.sh  | 108 -------------------
 6 files changed, 122 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..e6f9be1a7
--- /dev/null
+++ b/testcases/kernel/controllers/memcg/stress/memcg_stress01.c
@@ -0,0 +1,118 @@
+// 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.
+ * Containers are allocating memory in serial.
+ */
+
+#include <math.h>
+#include "tst_test.h"
+#include "tst_cgroup.h"
+
+#define MAX_CGROUPS 1000
+#define RUNTIME (30 * 60)
+
+static char *str_cgroups_num;
+static int cgroups_num = 1;
+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 < pages_num; i++) {
+		pages[i] = (char *)SAFE_MMAP(NULL, page_size,
+				   PROT_WRITE | PROT_READ,
+				   MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+	}
+
+	for (int i = 0; i < pages_num; i++)
+		*(pages[i]) = 0xef;
+}
+
+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);
+		}
+
+		SAFE_WAITPID(child_pid, NULL, 0);
+
+		if (!tst_remaining_runtime()) {
+			tst_res(TINFO, "Reached maximum runtime");
+			break;
+		}
+	}
+
+	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);
+
+	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, using %ld MB",
+		cgroups_num, mem_per_proc / TST_MB);
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.test_all = run,
+	.needs_root = 1,
+	.forks_child = 1,
+	.runtime = 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: 1)"},
+		{}
+	},
+};
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: 19083415169a3c5f0e07a74bea07f3690e0d041c
change-id: 20251105-b4-memcg_stress_rewrite-8a133288f7d0

Best regards,
-- 
Andrea Cervesato <andrea.cervesato@suse.com>



More information about the ltp mailing list