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