• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /bin/sh
2# SPDX-License-Identifier: GPL-2.0-or-later
3# Copyright (c) 2012 FUJITSU LIMITED
4# Copyright (c) 2014-2019 Linux Test Project
5# Copyright (c) 2021 Joerg Vehlow <joerg.vehlow@aox-tech.de>
6#
7# Author: Peng Haitao <penght@cn.fujitsu.com>
8
9TST_NEEDS_CHECKPOINTS=1
10TST_NEEDS_ROOT=1
11TST_NEEDS_TMPDIR=1
12TST_NEEDS_CMDS="killall find kill"
13TST_CLEANUP=memcg_cleanup
14TST_SETUP=memcg_setup
15TST_TESTFUNC=memcg_testfunc
16
17MEMCG_SHMMAX=${MEMCG_SHMMAX:-0}
18MEMCG_TESTFUNC=${MEMCG_TESTFUNC:-memcg_no_testfunc}
19
20. cgroup_lib.sh
21
22PAGESIZE=$(tst_getconf PAGESIZE)
23if [ $? -ne 0 ]; then
24	tst_brk TBROK "tst_getconf PAGESIZE failed"
25fi
26
27# Post 4.16 kernel updates stat in batch (> 32 pages) every time
28PAGESIZES=$(($PAGESIZE * 33))
29
30# On recent Linux kernels (at least v5.4) updating stats happens in batches
31# (PAGESIZES) and also might depend on workload and number of CPUs.  The kernel
32# caches the data and does not prioritize stats precision.  This is especially
33# visible for max_usage_in_bytes where it usually exceeds
34# actual memory allocation.
35# When checking for usage_in_bytes and max_usage_in_bytes accept also higher values
36# from given range:
37MEM_USAGE_RANGE=$((PAGESIZES))
38
39HUGEPAGESIZE=$(awk '/Hugepagesize/ {print $2}' /proc/meminfo)
40[ -z $HUGEPAGESIZE ] && HUGEPAGESIZE=0
41HUGEPAGESIZE=$(($HUGEPAGESIZE * 1024))
42
43orig_memory_use_hierarchy=
44orig_shmmax=
45
46memcg_require_memsw()
47{
48	if ! [ -e /dev/memcg/memory.limit_in_bytes ]; then
49		tst_brk TBROK "/dev/memcg must be mounted before calling memcg_require_memsw"
50	fi
51	if ! [ -e /dev/memcg/memory.memsw.limit_in_bytes ]; then
52		tst_brk TCONF "mem+swap is not enabled"
53	fi
54}
55
56memcg_require_hierarchy_disabled()
57{
58	if [ ! -e "/dev/memcg/memory.use_hierarchy" ]; then
59		tst_brk TBROK "/dev/memcg must be mounted before calling memcg_require_hierarchy_disabled"
60	fi
61	if [ $(cat /dev/memcg/memory.use_hierarchy) -eq 1 ]; then
62		tst_brk TCONF "Test requires root cgroup memory.use_hierarchy=0"
63	fi
64}
65
66# Kernel memory allocated for the process is also charged.  It might depend on
67# the number of CPUs and number of nodes. For example on kernel v5.11
68# additionally total_cpus (plus 1 or 2) pages are charged to the group via
69# kernel memory.  For a two-node machine, additional 108 pages kernel memory
70# are charged to the group.
71#
72# Adjust the limit to account such per-CPU and per-node kernel memory.
73# $1 - expected cgroup memory limit value to adjust
74memcg_adjust_limit_for_kmem()
75{
76	[ $# -ne 1 ] && tst_brk TBROK "memcg_adjust_limit_for_kmem expects 1 parameter"
77
78	local limit=$1
79
80	# Total number of CPUs
81	local total_cpus=`tst_ncpus`
82
83	# Total number of nodes
84	if [ ! -d /sys/devices/system/node/node0 ]; then
85		total_nodes=1
86	else
87		total_nodes=`ls /sys/devices/system/node/ | grep -c "node[0-9][0-9]*"`
88	fi
89
90	local node_mem=0
91	if [ $total_nodes -gt 1 ]; then
92		node_mem=$((total_nodes - 1))
93		node_mem=$((node_mem * PAGESIZE * 128))
94	fi
95
96	limit=$((limit + 4 * PAGESIZE + total_cpus * PAGESIZE + node_mem))
97
98	echo $limit
99}
100
101memcg_setup()
102{
103	if ! is_cgroup_subsystem_available_and_enabled "memory"; then
104		tst_brk TCONF "Either kernel does not support Memory Resource Controller or feature not enabled"
105	fi
106
107	ROD mkdir /dev/memcg
108	ROD mount -t cgroup -omemory memcg /dev/memcg
109
110	# For kernels older than v5.11 the default value for
111	# memory.use_hierarchy is 0 and some of tests (memcg_stat_test.sh and
112	# memcg_use_hierarchy_test.sh) expect it so while there are
113	# distributions (RHEL7U0Beta for example) that sets it to 1.
114	# Note: If there are already subgroups created it is not possible,
115	# to set this back to 0.
116	# This seems to be the default for all systems using systemd.
117	#
118	# Starting with kernel v5.11, the non-hierarchical mode is not
119	# available. See Linux kernel commit bef8620cd8e0 ("mm: memcg:
120	# deprecate the non-hierarchical mode").
121	orig_memory_use_hierarchy=$(cat /dev/memcg/memory.use_hierarchy)
122	if [ -z "$orig_memory_use_hierarchy" ];then
123		tst_res TINFO "cat /dev/memcg/ failed"
124	elif [ "$orig_memory_use_hierarchy" = "0" ];then
125		orig_memory_use_hierarchy=""
126	else
127		echo 0 > /dev/memcg/memory.use_hierarchy 2>/dev/null
128		if [ $? -ne 0 ];then
129			tst_res TINFO "set /dev/memcg/memory.use_hierarchy to 0 failed"
130		fi
131	fi
132
133	[ "$MEMCG_SHMMAX" = "1" ] && shmmax_setup
134}
135
136memcg_cleanup()
137{
138	kill -9 $MEMCG_PROCESS_PID 2> /dev/null
139
140	cd $TST_TMPDIR
141	# In order to remove all subgroups, we have to remove them recursively
142	if [ -e /dev/memcg/ltp_$$ ]; then
143		ROD find /dev/memcg/ltp_$$ -depth -type d -delete
144	fi
145
146	if [ -n "$orig_memory_use_hierarchy" ];then
147		echo $orig_memory_use_hierarchy > /dev/memcg/memory.use_hierarchy
148		if [ $? -ne 0 ];then
149			tst_res TINFO "restore /dev/memcg/memory.use_hierarchy failed"
150		fi
151		orig_memory_use_hierarchy=""
152	fi
153
154	if [ -e "/dev/memcg" ]; then
155		umount /dev/memcg
156		rmdir /dev/memcg
157	fi
158
159	[ "$MEMCG_SHMMAX" = "1" ] && shmmax_cleanup
160}
161
162shmmax_setup()
163{
164	tst_require_cmds bc
165
166	tst_res TINFO "Setting shmmax"
167
168	orig_shmmax=$(cat /proc/sys/kernel/shmmax)
169	if [ $(echo "$orig_shmmax < $HUGEPAGESIZE" | bc) -eq 1 ]; then
170		ROD echo "$HUGEPAGESIZE" \> /proc/sys/kernel/shmmax
171	fi
172}
173
174shmmax_cleanup()
175{
176	if [ -n "$orig_shmmax" ]; then
177		echo "$orig_shmmax" > /proc/sys/kernel/shmmax
178	fi
179}
180
181# Check size in memcg
182# $1 - Item name
183# $2 - Expected size lower bound
184# $3 - Expected size upper bound (optional)
185check_mem_stat()
186{
187	local item_size
188
189	if [ -e $1 ]; then
190		item_size=$(cat $1)
191	else
192		item_size=$(grep -w $1 memory.stat | cut -d " " -f 2)
193	fi
194
195	if [ "$3" ]; then
196		if [ $item_size -ge $2 ] && [ $item_size -le $3 ]; then
197			tst_res TPASS "$1 is ${2}-${3} as expected"
198		else
199			tst_res TFAIL "$1 is $item_size, ${2}-${3} expected"
200		fi
201	elif [ "$2" = "$item_size" ]; then
202		tst_res TPASS "$1 is $2 as expected"
203	else
204		tst_res TFAIL "$1 is $item_size, $2 expected"
205	fi
206}
207
208start_memcg_process()
209{
210	tst_res TINFO "Running memcg_process $@"
211	memcg_process "$@" &
212	MEMCG_PROCESS_PID=$!
213	ROD tst_checkpoint wait 10000 0
214}
215
216signal_memcg_process()
217{
218	local size=$1
219	local path=$2
220	local usage_start=$(cat ${path}memory.usage_in_bytes)
221
222	kill -s USR1 $MEMCG_PROCESS_PID 2> /dev/null
223
224	if [ -z "$size" ]; then
225		return
226	fi
227
228	local loops=100
229
230	while kill -0 $MEMCG_PROCESS_PID 2> /dev/null; do
231		local usage=$(cat ${path}memory.usage_in_bytes)
232		local diff_a=$((usage_start - usage))
233		local diff_b=$((usage - usage_start))
234
235		if [ "$diff_a" -ge "$size" -o "$diff_b" -ge "$size" ]; then
236			return
237		fi
238
239		tst_sleep 100ms
240
241		loops=$((loops - 1))
242		if [ $loops -le 0 ]; then
243			tst_brk TBROK "timed out on memory.usage_in_bytes" $usage $usage_start $size
244		fi
245	done
246}
247
248stop_memcg_process()
249{
250	[ -z "$MEMCG_PROCESS_PID" ] && return
251	kill -s INT $MEMCG_PROCESS_PID 2> /dev/null
252	wait $MEMCG_PROCESS_PID
253	MEMCG_PROCESS_PID=
254}
255
256warmup()
257{
258	tst_res TINFO "Warming up pid: $MEMCG_PROCESS_PID"
259	signal_memcg_process
260	signal_memcg_process
261	sleep 1
262
263	if ! kill -0 $MEMCG_PROCESS_PID; then
264		wait $MEMCG_PROCESS_PID
265		tst_res TFAIL "Process $MEMCG_PROCESS_PID exited with $? after warm up"
266		return 1
267	else
268		tst_res TINFO "Process is still here after warm up: $MEMCG_PROCESS_PID"
269	fi
270
271	return 0
272}
273
274# Run test cases which checks memory.stat after make
275# some memory allocation
276test_mem_stat()
277{
278	local memtypes="$1"
279	local size=$2
280	local total_size=$3
281	local stat_name=$4
282	local exp_stat_size_low=$5
283	local exp_stat_size_up=$6
284	local check_after_free=$7
285	local kmem_stat_name="${stat_name##*.}"
286
287	start_memcg_process $memtypes -s $size
288
289	if ! warmup; then
290		return
291	fi
292
293	echo $MEMCG_PROCESS_PID > tasks
294	signal_memcg_process $size
295
296	if [ "$kmem_stat_name" = "max_usage_in_bytes" ] ||
297	   [ "$kmem_stat_name" = "usage_in_bytes" ]; then
298		local kmem=$(cat "memory.kmem.${kmem_stat_name}")
299		if [ $? -eq 0 ]; then
300			exp_stat_size_low=$((exp_stat_size_low + kmem))
301			exp_stat_size_up=$((exp_stat_size_up + kmem))
302		fi
303	fi
304
305	if [ "$exp_stat_size_low" = "$exp_stat_size_up" ]; then
306		check_mem_stat $stat_name $exp_stat_size_low
307	else
308		check_mem_stat $stat_name $exp_stat_size_low $exp_stat_size_up
309	fi
310
311	signal_memcg_process $size
312	if $check_after_free; then
313		check_mem_stat $stat_name 0
314	fi
315
316	stop_memcg_process
317}
318
319# Test process will be killed due to exceed memory limit
320# $1 - the value of memory.limit_in_bytes
321# $2 - the parameters of 'process', such as --shm
322# $3 - the -s parameter of 'process', such as 4096
323# $4 - use mem+swap limitation
324test_proc_kill()
325{
326	local limit=$1
327	local memtypes="$2"
328	local size=$3
329	local use_memsw=$4
330	local tpk_iter
331
332	echo $limit > memory.limit_in_bytes
333	if [ $use_memsw -eq 1 ]; then
334		memcg_require_memsw
335		echo $limit > memory.memsw.limit_in_bytes
336	fi
337
338	start_memcg_process $memtypes -s $size
339	echo $MEMCG_PROCESS_PID > tasks
340
341	signal_memcg_process $size
342
343	local tpk_pid_exists=1
344	for tpk_iter in $(seq 20); do
345		if [ ! -d "/proc/$MEMCG_PROCESS_PID" ] ||
346			grep -q 'Z (zombie)' "/proc/$MEMCG_PROCESS_PID/status"; then
347			tpk_pid_exists=0
348			break
349		fi
350
351		tst_sleep 250ms
352	done
353
354	if [ $tpk_pid_exists -eq 0 ]; then
355		wait $MEMCG_PROCESS_PID
356		ret=$?
357		if [ $ret -eq 1 ]; then
358			tst_res TFAIL "process $MEMCG_PROCESS_PID is killed by error"
359		elif [ $ret -eq 2 ]; then
360			tst_res TPASS "Failed to lock memory"
361		else
362			tst_res TPASS "process $MEMCG_PROCESS_PID is killed"
363		fi
364	else
365		stop_memcg_process
366		tst_res TFAIL "process $MEMCG_PROCESS_PID is not killed"
367	fi
368}
369
370# Test limit_in_bytes will be aligned to PAGESIZE
371# $1 - user input value
372# $2 - use mem+swap limitation
373test_limit_in_bytes()
374{
375	local limit=$1
376	local use_memsw=$2
377	local elimit
378
379	EXPECT_PASS echo $limit \> memory.limit_in_bytes
380	if [ $use_memsw -eq 1 ]; then
381		memcg_require_memsw
382		echo $limit > memory.memsw.limit_in_bytes
383		elimit=$(cat memory.memsw.limit_in_bytes)
384	else
385		elimit=$(cat memory.limit_in_bytes)
386	fi
387
388	# Kernels prior to 3.19 were rounding up,
389	# but newer kernels are rounding down
390	local limit_up=$(( PAGESIZE * (limit / PAGESIZE) ))
391	local limit_down=$(( PAGESIZE * ((limit + PAGESIZE - 1) / PAGESIZE) ))
392	if [ $limit_up -eq $elimit ] || [ $limit_down -eq $elimit ]; then
393		tst_res TPASS "input=$limit, limit_in_bytes=$elimit"
394	else
395		tst_res TFAIL "input=$limit, limit_in_bytes=$elimit"
396	fi
397}
398
399memcg_testfunc()
400{
401	ROD mkdir /dev/memcg/ltp_$$
402	cd /dev/memcg/ltp_$$
403
404	if type ${MEMCG_TESTFUNC}1 > /dev/null 2>&1; then
405		${MEMCG_TESTFUNC}$1 $1 "$2"
406	else
407		${MEMCG_TESTFUNC} $1 "$2"
408	fi
409
410	cd $TST_TMPDIR
411	ROD rmdir /dev/memcg/ltp_$$
412}
413
414memcg_no_testfunc()
415{
416	tst_brk TBROK "No testfunc specified, set MEMCG_TESTFUNC"
417}
418