1#! /bin/sh
2
3################################################################################
4##                                                                            ##
5## Copyright (c) 2012 FUJITSU LIMITED                                         ##
6##                                                                            ##
7## This program is free software;  you can redistribute it and#or modify      ##
8## it under the terms of the GNU General Public License as published by       ##
9## the Free Software Foundation; either version 2 of the License, or          ##
10## (at your option) any later version.                                        ##
11##                                                                            ##
12## This program is distributed in the hope that it will be useful, but        ##
13## WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ##
14## or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License   ##
15## for more details.                                                          ##
16##                                                                            ##
17## You should have received a copy of the GNU General Public License          ##
18## along with this program;  if not, write to the Free Software               ##
19## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA    ##
20##                                                                            ##
21## Author: Peng Haitao <penght@cn.fujitsu.com>                                ##
22##                                                                            ##
23################################################################################
24
25TST_NEEDS_CHECKPOINTS=1
26. test.sh
27
28if [ "x$(grep -w memory /proc/cgroups | cut -f4)" != "x1" ]; then
29	tst_brkm TCONF "Kernel does not support the memory resource controller"
30fi
31
32PAGESIZE=$(tst_getconf PAGESIZE)
33if [ $? -ne 0 ]; then
34	tst_brkm TBROK "tst_getconf PAGESIZE failed"
35fi
36
37# Check for dependencies
38tst_require_cmds killall
39
40# Post 4.16 kernel updates stat in batch (> 32 pages) every time
41PAGESIZES=$(( $PAGESIZE * 33 ))
42
43HUGEPAGESIZE=$(awk '/Hugepagesize/ {print $2}' /proc/meminfo)
44[ -z $HUGEPAGESIZE ] && HUGEPAGESIZE=0
45HUGEPAGESIZE=$(( $HUGEPAGESIZE * 1024 ))
46orig_memory_use_hierarchy=""
47
48MEMSW_USAGE_FLAG=0
49MEMSW_LIMIT_FLAG=0
50
51tst_tmpdir
52TMP_DIR="$PWD"
53
54cleanup()
55{
56	if [ -n "$LOCAL_CLEANUP" ]; then
57		$LOCAL_CLEANUP
58	fi
59
60	killall -9 memcg_process 2> /dev/null
61	wait
62
63	cd "$TMP_DIR"
64
65	if [ -n "$TEST_ID" -a -d "/dev/memcg/$TEST_ID" ]; then
66		for i in "/dev/memcg/$TEST_ID/"*; do
67			if [ -d "$i" ]; then
68				rmdir "$i"
69			fi
70		done
71
72		rmdir "/dev/memcg/$TEST_ID"
73	fi
74
75	if [ -d "/dev/memcg" ]; then
76		umount /dev/memcg
77		rmdir /dev/memcg
78	fi
79
80	tst_rmdir
81}
82TST_CLEANUP=cleanup
83
84shmmax_setup()
85{
86	tst_require_cmds bc
87
88	shmmax=`cat /proc/sys/kernel/shmmax`
89	if [ $(echo "$shmmax < $HUGEPAGESIZE" |bc) -eq 1 ]; then
90		ROD echo "$HUGEPAGESIZE" \> /proc/sys/kernel/shmmax
91	fi
92}
93
94shmmax_cleanup()
95{
96	if [ -n "$shmmax" ]; then
97		echo "$shmmax" > /proc/sys/kernel/shmmax
98	fi
99}
100
101# Check size in memcg
102# $1 - Item name
103# $2 - Expected size
104check_mem_stat()
105{
106	if [ -e $1 ]; then
107		item_size=`cat $1`
108	else
109		item_size=`grep -w $1 memory.stat | cut -d " " -f 2`
110	fi
111
112	if [ "$2" = "$item_size" ]; then
113		tst_resm TPASS "$1 is $2 as expected"
114	else
115		tst_resm TFAIL "$1 is $item_size, $2 expected"
116	fi
117}
118
119signal_memcg_process()
120{
121	local pid=$1
122	local size=$2
123	local path=$3
124	local usage_start=$(cat ${path}memory.usage_in_bytes)
125
126	kill -s USR1 $pid 2> /dev/null
127
128	if [ -z "$size" ]; then
129		return
130	fi
131
132	local loops=100
133
134	while kill -0 $pid 2> /dev/null; do
135		local usage=$(cat ${path}memory.usage_in_bytes)
136		local diff_a=$((usage_start - usage))
137		local diff_b=$((usage - usage_start))
138
139		if [ "$diff_a" -ge "$size" -o "$diff_b" -ge "$size" ]; then
140			return
141		fi
142
143		tst_sleep 100ms
144
145		loops=$((loops - 1))
146		if [ $loops -le 0 ]; then
147			tst_brkm TBROK "timeouted on memory.usage_in_bytes"
148		fi
149	done
150}
151
152stop_memcg_process()
153{
154	local pid=$1
155	kill -s INT $pid 2> /dev/null
156	wait $pid
157}
158
159warmup()
160{
161	local pid=$1
162
163	tst_resm TINFO "Warming up pid: $pid"
164	signal_memcg_process $pid
165	signal_memcg_process $pid
166	sleep 1
167
168	kill -0 $pid
169	if [ $? -ne 0 ]; then
170		wait $pid
171		tst_resm TFAIL "Process $pid exited with $? after warm up"
172		return 1
173	else
174		tst_resm TINFO "Process is still here after warm up: $pid"
175	fi
176
177	return 0
178}
179
180# Run test cases which checks memory.stat after make
181# some memory allocation
182test_mem_stat()
183{
184	local memtypes="$1"
185	local size=$2
186	local total_size=$3
187	local stat_name=$4
188	local exp_stat_size=$5
189	local check_after_free=$6
190
191	tst_resm TINFO "Running memcg_process $memtypes -s $size"
192	memcg_process $memtypes -s $size &
193	TST_CHECKPOINT_WAIT 0
194
195	warmup $!
196	if [ $? -ne 0 ]; then
197		return
198	fi
199
200	echo $! > tasks
201	signal_memcg_process $! $size
202
203	check_mem_stat $stat_name $exp_stat_size
204
205	signal_memcg_process $! $size
206	if $check_after_free; then
207		check_mem_stat $stat_name 0
208	fi
209
210	stop_memcg_process $!
211}
212
213# Run test cases which checks memory.max_usage_in_bytes after make
214# some memory allocation
215# $1 - the parameters of 'process', such as --shm
216# $2 - the -s parameter of 'process', such as 4096
217# $3 - item name
218# $4 - the expected size
219# $5 - check after free ?
220test_max_usage_in_bytes()
221{
222	tst_resm TINFO "Running memcg_process $1 -s $2"
223	memcg_process $1 -s $2 &
224	TST_CHECKPOINT_WAIT 0
225
226	warmup $!
227	if [ $? -ne 0 ]; then
228		return
229	fi
230
231	echo $! > tasks
232	signal_memcg_process $! $2
233	signal_memcg_process $! $2
234
235	check_mem_stat $3 $4
236
237	if [ $5 -eq 1 ]; then
238		echo 0 > $3
239		check_mem_stat $3 0
240	fi
241
242	stop_memcg_process $!
243}
244
245# make some memory allocation
246# $1 - the parameters of 'process', such as --shm
247# $2 - the -s parameter of 'process', such as 4096
248malloc_free_memory()
249{
250	tst_resm TINFO "Running memcg_process $1 -s $2"
251	memcg_process $1 -s $2 &
252	TST_CHECKPOINT_WAIT 0
253
254	echo $! > tasks
255	signal_memcg_process $! $2
256	signal_memcg_process $! $2
257
258	stop_memcg_process $!
259}
260
261# Test if failcnt > 0, which means page reclamation occured
262# $1 - item name in memcg
263test_failcnt()
264{
265	failcnt=`cat $1`
266	if [ $failcnt -gt 0 ]; then
267		tst_resm TPASS "$1 is $failcnt, > 0 as expected"
268	else
269		tst_resm TFAIL "$1 is $failcnt, <= 0 expected"
270	fi
271}
272
273# Test process will be killed due to exceed memory limit
274# $1 - the value of memory.limit_in_bytes
275# $2 - the parameters of 'process', such as --shm
276# $3 - the -s parameter of 'process', such as 4096
277# $4 - use mem+swap limitation
278test_proc_kill()
279{
280	echo $1 > memory.limit_in_bytes
281	if [ $4 -eq 1 ]; then
282		if [ -e memory.memsw.limit_in_bytes ]; then
283			echo $1 > memory.memsw.limit_in_bytes
284		else
285			tst_resm TCONF "mem+swap is not enabled"
286			return
287		fi
288	fi
289
290	memcg_process $2 -s $3 &
291	pid=$!
292	TST_CHECKPOINT_WAIT 0
293	echo $pid > tasks
294
295	signal_memcg_process $pid $3
296
297	tpk_pid_exists=1
298	for tpk_iter in $(seq 20); do
299		if [ ! -d "/proc/$pid" ] ||
300			grep -q 'Z (zombie)' "/proc/$pid/status"; then
301			tpk_pid_exists=0
302			break
303		fi
304
305		tst_sleep 250ms
306	done
307
308	if [ $tpk_pid_exists -eq 0 ]; then
309		wait $pid
310		ret=$?
311		if [ $ret -eq 1 ]; then
312			tst_resm TFAIL "process $pid is killed by error"
313		elif [ $ret -eq 2 ]; then
314			tst_resm TPASS "Failed to lock memory"
315		else
316			tst_resm TPASS "process $pid is killed"
317		fi
318	else
319		stop_memcg_process $!
320		tst_resm TFAIL "process $pid is not killed"
321	fi
322}
323
324# Test limit_in_bytes will be aligned to PAGESIZE
325# $1 - user input value
326# $2 - use mem+swap limitation
327test_limit_in_bytes()
328{
329	echo $1 > memory.limit_in_bytes
330	if [ $2 -eq 1 ]; then
331		if [ -e memory.memsw.limit_in_bytes ]; then
332			echo $1 > memory.memsw.limit_in_bytes
333			limit=`cat memory.memsw.limit_in_bytes`
334		else
335			tst_resm TCONF "mem+swap is not enabled"
336			return
337		fi
338	else
339		limit=`cat memory.limit_in_bytes`
340	fi
341
342	# Kernels prior to 3.19 were rounding up but newer kernels
343	# are rounding down
344	if [ \( $(($PAGESIZE*($1/$PAGESIZE))) -eq $limit \) \
345	    -o \( $(($PAGESIZE*(($1+$PAGESIZE-1)/$PAGESIZE))) -eq $limit \) ]; then
346		tst_resm TPASS "input=$1, limit_in_bytes=$limit"
347	else
348		tst_resm TFAIL "input=$1, limit_in_bytes=$limit"
349	fi
350}
351
352# Never used, so untested
353#
354# Test memory controller doesn't charge hugepage
355# $1 - the value of /proc/sys/vm/nr_hugepages
356# $2 - the parameters of 'process', --mmap-file or --shm
357# $3 - the -s parameter of 'process', such as $HUGEPAGESIZE
358# $4 - 0: expected failure, 1: expected success
359test_hugepage()
360{
361	TMP_FILE="$TMP_DIR/tmp"
362	nr_hugepages=`cat /proc/sys/vm/nr_hugepages`
363
364	mkdir /hugetlb
365	mount -t hugetlbfs none /hugetlb
366
367	echo $1 > /proc/sys/vm/nr_hugepages
368
369	memcg_process $2 --hugepage -s $3 > $TMP_FILE 2>&1 &
370	TST_CHECKPOINT_WAIT 0
371
372	signal_memcg_process $! $3
373
374	check_mem_stat "rss" 0
375
376	echo "TMP_FILE:"
377	cat $TMP_FILE
378
379	if [ $4 -eq 0 ]; then
380		test -s $TMP_FILE
381		if [ $? -eq 0 ]; then
382			tst_resm TPASS "allocate hugepage failed as expected"
383		else
384			signal_memcg_process $! $3
385			stop_memcg_process $!
386			tst_resm TFAIL "allocate hugepage should fail"
387		fi
388	else
389		test ! -s $TMP_FILE
390		if [ $? -eq 0 ]; then
391			signal_memcg_process $! $3
392			stop_memcg_process $!
393			tst_resm TPASS "allocate hugepage succeeded"
394		else
395			tst_resm TFAIL "allocate hugepage failed"
396		fi
397	fi
398
399	sleep 1
400	rm -rf $TMP_FILE
401	umount /hugetlb
402	rmdir /hugetlb
403	echo $nr_hugepages > /proc/sys/vm/nr_hugepages
404}
405
406# Test the memory charge won't move to subgroup
407# $1 - memory.limit_in_bytes in parent group
408# $2 - memory.limit_in_bytes in sub group
409test_subgroup()
410{
411	mkdir subgroup
412	echo $1 > memory.limit_in_bytes
413	echo $2 > subgroup/memory.limit_in_bytes
414
415	tst_resm TINFO "Running memcg_process --mmap-anon -s $PAGESIZES"
416	memcg_process --mmap-anon -s $PAGESIZES &
417	TST_CHECKPOINT_WAIT 0
418
419	warmup $! $PAGESIZES
420	if [ $? -ne 0 ]; then
421		return
422	fi
423
424	echo $! > tasks
425	signal_memcg_process $! $PAGESIZES
426	check_mem_stat "rss" $PAGESIZES
427
428	cd subgroup
429	echo $! > tasks
430	check_mem_stat "rss" 0
431
432	# cleanup
433	cd ..
434	echo $! > tasks
435	stop_memcg_process $!
436	rmdir subgroup
437}
438
439# Run test cases which test memory.move_charge_at_immigrate
440test_move_charge()
441{
442	local memtypes="$1"
443	local size=$2
444	local total_size=$3
445	local move_charge_mask=$4
446	local b_rss=$5
447	local b_cache=$6
448	local a_rss=$7
449	local a_cache=$8
450
451	mkdir subgroup_a
452
453	tst_resm TINFO "Running memcg_process $memtypes -s $size"
454	memcg_process $memtypes -s $size &
455	TST_CHECKPOINT_WAIT 0
456	warmup $!
457	if [ $? -ne 0 ]; then
458		rmdir subgroup_a
459		return
460	fi
461
462	echo $! > subgroup_a/tasks
463	signal_memcg_process $! $total_size "subgroup_a/"
464
465	mkdir subgroup_b
466	echo $move_charge_mask > subgroup_b/memory.move_charge_at_immigrate
467	echo $! > subgroup_b/tasks
468
469	cd subgroup_b
470	check_mem_stat "rss" $b_rss
471	check_mem_stat "cache" $b_cache
472	cd ../subgroup_a
473	check_mem_stat "rss" $a_rss
474	check_mem_stat "cache" $a_cache
475	cd ..
476	stop_memcg_process $!
477	rmdir subgroup_a subgroup_b
478}
479
480cleanup_test()
481{
482	TEST_ID="$1"
483
484	if [ -n "$orig_memory_use_hierarchy" ];then
485		echo $orig_memory_use_hierarchy > \
486		     /dev/memcg/memory.use_hierarchy
487		if [ $? -ne 0 ];then
488			tst_resm TINFO "restore "\
489				 "/dev/memcg/memory.use_hierarchy failed"
490		fi
491		orig_memory_use_hierarchy=""
492	fi
493
494	killall -9 memcg_process 2>/dev/null
495	wait
496
497	ROD cd "$TMP_DIR"
498
499	ROD rmdir "/dev/memcg/$TEST_ID"
500	TEST_ID=""
501	ROD umount /dev/memcg
502	ROD rmdir /dev/memcg
503}
504
505setup_test()
506{
507	TEST_ID="$1"
508
509	ROD mkdir /dev/memcg
510	ROD mount -t cgroup -omemory memcg /dev/memcg
511
512	# The default value for memory.use_hierarchy is 0 and some of tests
513	# (memcg_stat_test.sh and memcg_use_hierarchy_test.sh) expect it so
514	# while there are distributions (RHEL7U0Beta for example) that sets
515	# it to 1.
516	orig_memory_use_hierarchy=$(cat /dev/memcg/memory.use_hierarchy)
517	if [ -z "$orig_memory_use_hierarchy" ];then
518		tst_resm TINFO "cat /dev/memcg/memory.use_hierarchy failed"
519	elif [ "$orig_memory_use_hierarchy" = "0" ];then
520		orig_memory_use_hierarchy=""
521	else
522		echo 0 > /dev/memcg/memory.use_hierarchy
523		if [ $? -ne 0 ];then
524			tst_resm TINFO "set /dev/memcg/memory.use_hierarchy" \
525				"to 0 failed"
526		fi
527	fi
528
529	ROD mkdir "/dev/memcg/$TEST_ID"
530	ROD cd "/dev/memcg/$TEST_ID"
531}
532
533# Run all the test cases
534run_tests()
535{
536	for i in $(seq 1 $TST_TOTAL); do
537
538		tst_resm TINFO "Starting test $i"
539
540		setup_test $i
541
542		if [ -e memory.memsw.limit_in_bytes ]; then
543			MEMSW_LIMIT_FLAG=1
544		fi
545
546		if [ -e memory.memsw.max_usage_in_bytes ]; then
547			MEMSW_USAGE_FLAG=1
548		fi
549
550		testcase_$i
551
552		cleanup_test $i
553	done
554}
555