#! /bin/sh # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (c) 2012 FUJITSU LIMITED # Copyright (c) 2014-2019 Linux Test Project # Copyright (c) 2021 Joerg Vehlow # # Author: Peng Haitao TST_NEEDS_CHECKPOINTS=1 TST_NEEDS_ROOT=1 TST_NEEDS_TMPDIR=1 TST_NEEDS_CMDS="killall find kill" TST_CLEANUP=memcg_cleanup TST_SETUP=memcg_setup TST_TESTFUNC=memcg_testfunc MEMCG_SHMMAX=${MEMCG_SHMMAX:-0} MEMCG_TESTFUNC=${MEMCG_TESTFUNC:-memcg_no_testfunc} . cgroup_lib.sh PAGESIZE=$(tst_getconf PAGESIZE) if [ $? -ne 0 ]; then tst_brk TBROK "tst_getconf PAGESIZE failed" fi # Post 4.16 kernel updates stat in batch (> 32 pages) every time PAGESIZES=$(($PAGESIZE * 33)) HUGEPAGESIZE=$(awk '/Hugepagesize/ {print $2}' /proc/meminfo) [ -z $HUGEPAGESIZE ] && HUGEPAGESIZE=0 HUGEPAGESIZE=$(($HUGEPAGESIZE * 1024)) orig_memory_use_hierarchy= orig_shmmax= memcg_require_memsw() { if ! [ -e /dev/memcg/memory.limit_in_bytes ]; then tst_brk TBROK "/dev/memcg must be mounted before calling memcg_require_memsw" fi if ! [ -e /dev/memcg/memory.memsw.limit_in_bytes ]; then tst_brk TCONF "mem+swap is not enabled" fi } memcg_require_hierarchy_disabled() { if [ ! -e "/dev/memcg/memory.use_hierarchy" ]; then tst_brk TBROK "/dev/memcg must be mounted before calling memcg_require_hierarchy_disabled" fi if [ $(cat /dev/memcg/memory.use_hierarchy) -eq 1 ]; then tst_brk TCONF "Test requires root cgroup memory.use_hierarchy=0" fi } memcg_setup() { if ! is_cgroup_subsystem_available_and_enabled "memory"; then tst_brk TCONF "Either kernel does not support Memory Resource Controller or feature not enabled" fi # Setup IPC LTP_IPC_PATH="/dev/shm/ltp_${TCID}_$$" LTP_IPC_SIZE=$PAGESIZE ROD_SILENT dd if=/dev/zero of="$LTP_IPC_PATH" bs="$LTP_IPC_SIZE" count=1 ROD_SILENT chmod 600 "$LTP_IPC_PATH" export LTP_IPC_PATH # Setup IPC end ROD mkdir /dev/memcg ROD mount -t cgroup -omemory memcg /dev/memcg # The default value for memory.use_hierarchy is 0 and some of tests # (memcg_stat_test.sh and memcg_use_hierarchy_test.sh) expect it so # while there are distributions (RHEL7U0Beta for example) that sets # it to 1. # Note: If there are already subgroups created it is not possible, # to set this back to 0. # This seems to be the default for all systems using systemd. orig_memory_use_hierarchy=$(cat /dev/memcg/memory.use_hierarchy) if [ -z "$orig_memory_use_hierarchy" ];then tst_res TINFO "cat /dev/memcg/ failed" elif [ "$orig_memory_use_hierarchy" = "0" ];then orig_memory_use_hierarchy="" else echo 0 > /dev/memcg/memory.use_hierarchy 2>/dev/null if [ $? -ne 0 ];then tst_res TINFO "set /dev/memcg/memory.use_hierarchy to 0 failed" fi fi [ "$MEMCG_SHMMAX" = "1" ] && shmmax_setup } memcg_cleanup() { kill -9 $MEMCG_PROCESS_PID 2> /dev/null cd $TST_TMPDIR # In order to remove all subgroups, we have to remove them recursively if [ -e /dev/memcg/ltp_$$ ]; then ROD find /dev/memcg/ltp_$$ -depth -type d -delete fi if [ -n "$orig_memory_use_hierarchy" ];then echo $orig_memory_use_hierarchy > /dev/memcg/memory.use_hierarchy if [ $? -ne 0 ];then tst_res TINFO "restore /dev/memcg/memory.use_hierarchy failed" fi orig_memory_use_hierarchy="" fi if [ -e "/dev/memcg" ]; then umount /dev/memcg rmdir /dev/memcg fi [ "$MEMCG_SHMMAX" = "1" ] && shmmax_cleanup } shmmax_setup() { tst_require_cmds bc tst_res TINFO "Setting shmmax" orig_shmmax=$(cat /proc/sys/kernel/shmmax) if [ $(echo "$orig_shmmax < $HUGEPAGESIZE" | bc) -eq 1 ]; then ROD echo "$HUGEPAGESIZE" \> /proc/sys/kernel/shmmax fi } shmmax_cleanup() { if [ -n "$orig_shmmax" ]; then echo "$orig_shmmax" > /proc/sys/kernel/shmmax fi } # Check size in memcg # $1 - Item name # $2 - Expected size check_mem_stat() { local item_size if [ -e $1 ]; then item_size=$(cat $1) else item_size=$(grep -w $1 memory.stat | cut -d " " -f 2) fi if [ "$2" = "$item_size" ]; then tst_res TPASS "$1 is $2 as expected" else tst_res TFAIL "$1 is $item_size, $2 expected" fi } start_memcg_process() { tst_res TINFO "Running memcg_process $@" memcg_process "$@" & MEMCG_PROCESS_PID=$! ROD tst_checkpoint wait 10000 0 } signal_memcg_process() { local size=$1 local path=$2 local usage_start=$(cat ${path}memory.usage_in_bytes) kill -s USR1 $MEMCG_PROCESS_PID 2> /dev/null if [ -z "$size" ]; then return fi local loops=100 while kill -0 $MEMCG_PROCESS_PID 2> /dev/null; do local usage=$(cat ${path}memory.usage_in_bytes) local diff_a=$((usage_start - usage)) local diff_b=$((usage - usage_start)) if [ "$diff_a" -ge "$size" -o "$diff_b" -ge "$size" ]; then return fi tst_sleep 100ms loops=$((loops - 1)) if [ $loops -le 0 ]; then tst_brk TBROK "timed out on memory.usage_in_bytes" fi done } stop_memcg_process() { [ -z "$MEMCG_PROCESS_PID" ] && return kill -s INT $MEMCG_PROCESS_PID 2> /dev/null wait $MEMCG_PROCESS_PID MEMCG_PROCESS_PID= } warmup() { tst_res TINFO "Warming up pid: $MEMCG_PROCESS_PID" signal_memcg_process signal_memcg_process sleep 1 if ! kill -0 $MEMCG_PROCESS_PID; then wait $MEMCG_PROCESS_PID tst_res TFAIL "Process $MEMCG_PROCESS_PID exited with $? after warm up" return 1 else tst_res TINFO "Process is still here after warm up: $MEMCG_PROCESS_PID" fi return 0 } # Run test cases which checks memory.stat after make # some memory allocation test_mem_stat() { local memtypes="$1" local size=$2 local total_size=$3 local stat_name=$4 local exp_stat_size=$5 local check_after_free=$6 start_memcg_process $memtypes -s $size if ! warmup; then return fi echo $MEMCG_PROCESS_PID > tasks signal_memcg_process $size check_mem_stat $stat_name $exp_stat_size signal_memcg_process $size if $check_after_free; then check_mem_stat $stat_name 0 fi stop_memcg_process } # Test process will be killed due to exceed memory limit # $1 - the value of memory.limit_in_bytes # $2 - the parameters of 'process', such as --shm # $3 - the -s parameter of 'process', such as 4096 # $4 - use mem+swap limitation test_proc_kill() { local limit=$1 local memtypes="$2" local size=$3 local use_memsw=$4 local tpk_iter echo $limit > memory.limit_in_bytes if [ $use_memsw -eq 1 ]; then memcg_require_memsw echo $limit > memory.memsw.limit_in_bytes fi start_memcg_process $memtypes -s $size echo $MEMCG_PROCESS_PID > tasks signal_memcg_process $size local tpk_pid_exists=1 for tpk_iter in $(seq 20); do if [ ! -d "/proc/$MEMCG_PROCESS_PID" ] || grep -q 'Z (zombie)' "/proc/$MEMCG_PROCESS_PID/status"; then tpk_pid_exists=0 break fi tst_sleep 250ms done if [ $tpk_pid_exists -eq 0 ]; then wait $MEMCG_PROCESS_PID ret=$? if [ $ret -eq 1 ]; then tst_res TFAIL "process $MEMCG_PROCESS_PID is killed by error" elif [ $ret -eq 2 ]; then tst_res TPASS "Failed to lock memory" else tst_res TPASS "process $MEMCG_PROCESS_PID is killed" fi else stop_memcg_process tst_res TFAIL "process $MEMCG_PROCESS_PID is not killed" fi } # Test limit_in_bytes will be aligned to PAGESIZE # $1 - user input value # $2 - use mem+swap limitation test_limit_in_bytes() { local limit=$1 local use_memsw=$2 local elimit echo $limit > memory.limit_in_bytes if [ $use_memsw -eq 1 ]; then memcg_require_memsw echo $limit > memory.memsw.limit_in_bytes elimit=$(cat memory.memsw.limit_in_bytes) else elimit=$(cat memory.limit_in_bytes) fi # Kernels prior to 3.19 were rounding up, # but newer kernels are rounding down local limit_up=$(( PAGESIZE * (limit / PAGESIZE) )) local limit_down=$(( PAGESIZE * ((limit + PAGESIZE - 1) / PAGESIZE) )) if [ $limit_up -eq $elimit ] || [ $limit_down -eq $elimit ]; then tst_res TPASS "input=$limit, limit_in_bytes=$elimit" else tst_res TFAIL "input=$limit, limit_in_bytes=$elimit" fi } memcg_testfunc() { ROD mkdir /dev/memcg/ltp_$$ cd /dev/memcg/ltp_$$ if type ${MEMCG_TESTFUNC}1 > /dev/null 2>&1; then ${MEMCG_TESTFUNC}$1 $1 "$2" else ${MEMCG_TESTFUNC} $1 "$2" fi cd $TST_TMPDIR ROD rmdir /dev/memcg/ltp_$$ } memcg_no_testfunc() { tst_brk TBROK "No testfunc specified, set MEMCG_TESTFUNC" }