xref: /linux/tools/testing/selftests/mm/charge_reserved_hugetlb.sh (revision 0fc8f6200d2313278fbf4539bbab74677c685531)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# Kselftest framework requirement - SKIP code is 4.
5ksft_skip=4
6
7set -e
8
9if [[ $(id -u) -ne 0 ]]; then
10  echo "This test must be run as root. Skipping..."
11  exit $ksft_skip
12fi
13
14if ! command -v killall >/dev/null 2>&1; then
15  echo "killall not available. Skipping..."
16  exit $ksft_skip
17fi
18
19nr_hugepgs=$(cat /proc/sys/vm/nr_hugepages)
20
21fault_limit_file=limit_in_bytes
22reservation_limit_file=rsvd.limit_in_bytes
23fault_usage_file=usage_in_bytes
24reservation_usage_file=rsvd.usage_in_bytes
25
26if [[ "$1" == "-cgroup-v2" ]]; then
27  cgroup2=1
28  fault_limit_file=max
29  reservation_limit_file=rsvd.max
30  fault_usage_file=current
31  reservation_usage_file=rsvd.current
32fi
33
34if [[ $cgroup2 ]]; then
35  cgroup_path=$(mount -t cgroup2 | head -1 | awk '{print $3}')
36  if [[ -z "$cgroup_path" ]]; then
37    cgroup_path=$(mktemp -d)
38    mount -t cgroup2 none $cgroup_path
39    do_umount=1
40  fi
41  echo "+hugetlb" >$cgroup_path/cgroup.subtree_control
42else
43  cgroup_path=$(mount -t cgroup | grep ",hugetlb" | awk '{print $3}')
44  if [[ -z "$cgroup_path" ]]; then
45    cgroup_path=$(mktemp -d)
46    mount -t cgroup memory,hugetlb $cgroup_path
47    do_umount=1
48  fi
49fi
50export cgroup_path
51
52function cleanup() {
53  if [[ $cgroup2 ]]; then
54    echo $$ >$cgroup_path/cgroup.procs
55  else
56    echo $$ >$cgroup_path/tasks
57  fi
58
59  if [[ -e /mnt/huge ]]; then
60    rm -rf /mnt/huge/*
61    umount /mnt/huge || echo error
62    rmdir /mnt/huge
63  fi
64  if [[ -e $cgroup_path/hugetlb_cgroup_test ]]; then
65    rmdir $cgroup_path/hugetlb_cgroup_test
66  fi
67  if [[ -e $cgroup_path/hugetlb_cgroup_test1 ]]; then
68    rmdir $cgroup_path/hugetlb_cgroup_test1
69  fi
70  if [[ -e $cgroup_path/hugetlb_cgroup_test2 ]]; then
71    rmdir $cgroup_path/hugetlb_cgroup_test2
72  fi
73  echo 0 >/proc/sys/vm/nr_hugepages
74  echo CLEANUP DONE
75}
76
77function expect_equal() {
78  local expected="$1"
79  local actual="$2"
80  local error="$3"
81
82  if [[ "$expected" != "$actual" ]]; then
83    echo "expected ($expected) != actual ($actual): $3"
84    cleanup
85    exit 1
86  fi
87}
88
89function get_machine_hugepage_size() {
90  hpz=$(grep -i hugepagesize /proc/meminfo)
91  kb=${hpz:14:-3}
92  mb=$(($kb / 1024))
93  echo $mb
94}
95
96MB=$(get_machine_hugepage_size)
97
98function setup_cgroup() {
99  local name="$1"
100  local cgroup_limit="$2"
101  local reservation_limit="$3"
102
103  mkdir $cgroup_path/$name
104
105  echo writing cgroup limit: "$cgroup_limit"
106  echo "$cgroup_limit" >$cgroup_path/$name/hugetlb.${MB}MB.$fault_limit_file
107
108  echo writing reservation limit: "$reservation_limit"
109  echo "$reservation_limit" > \
110    $cgroup_path/$name/hugetlb.${MB}MB.$reservation_limit_file
111
112  if [ -e "$cgroup_path/$name/cpuset.cpus" ]; then
113    echo 0 >$cgroup_path/$name/cpuset.cpus
114  fi
115  if [ -e "$cgroup_path/$name/cpuset.mems" ]; then
116    echo 0 >$cgroup_path/$name/cpuset.mems
117  fi
118}
119
120function wait_for_file_value() {
121  local path="$1"
122  local expect="$2"
123  local max_tries=60
124
125  if [[ ! -r "$path" ]]; then
126    echo "ERROR: cannot read '$path', missing or permission denied"
127    return 1
128  fi
129
130  for ((i=1; i<=max_tries; i++)); do
131    local cur="$(cat "$path")"
132    if [[ "$cur" == "$expect" ]]; then
133      return 0
134    fi
135    echo "Waiting for $path to become '$expect' (current: '$cur') (try $i/$max_tries)"
136    sleep 1
137  done
138
139  echo "ERROR: timeout waiting for $path to become '$expect'"
140  return 1
141}
142
143function wait_for_hugetlb_memory_to_get_depleted() {
144  local cgroup="$1"
145  local path="$cgroup_path/$cgroup/hugetlb.${MB}MB.$reservation_usage_file"
146
147  wait_for_file_value "$path" "0"
148}
149
150function wait_for_hugetlb_memory_to_get_reserved() {
151  local cgroup="$1"
152  local size="$2"
153  local path="$cgroup_path/$cgroup/hugetlb.${MB}MB.$reservation_usage_file"
154
155  wait_for_file_value "$path" "$size"
156}
157
158function wait_for_hugetlb_memory_to_get_written() {
159  local cgroup="$1"
160  local size="$2"
161  local path="$cgroup_path/$cgroup/hugetlb.${MB}MB.$fault_usage_file"
162
163  wait_for_file_value "$path" "$size"
164}
165
166function write_hugetlbfs_and_get_usage() {
167  local cgroup="$1"
168  local size="$2"
169  local populate="$3"
170  local write="$4"
171  local path="$5"
172  local method="$6"
173  local private="$7"
174  local expect_failure="$8"
175  local reserve="$9"
176
177  # Function return values.
178  reservation_failed=0
179  oom_killed=0
180  hugetlb_difference=0
181  reserved_difference=0
182
183  local hugetlb_usage=$cgroup_path/$cgroup/hugetlb.${MB}MB.$fault_usage_file
184  local reserved_usage=$cgroup_path/$cgroup/hugetlb.${MB}MB.$reservation_usage_file
185
186  local hugetlb_before=$(cat $hugetlb_usage)
187  local reserved_before=$(cat $reserved_usage)
188
189  echo
190  echo Starting:
191  echo hugetlb_usage="$hugetlb_before"
192  echo reserved_usage="$reserved_before"
193  echo expect_failure is "$expect_failure"
194
195  output=$(mktemp)
196  set +e
197  if [[ "$method" == "1" ]] || [[ "$method" == 2 ]] ||
198    [[ "$private" == "-r" ]] && [[ "$expect_failure" != 1 ]]; then
199
200    bash write_hugetlb_memory.sh "$size" "$populate" "$write" \
201      "$cgroup" "$path" "$method" "$private" "-l" "$reserve" 2>&1 | tee $output &
202
203    local write_result=$?
204    local write_pid=$!
205
206    until grep -q -i "DONE" $output; do
207      echo waiting for DONE signal.
208      if ! ps $write_pid > /dev/null
209      then
210        echo "FAIL: The write died"
211        cleanup
212        exit 1
213      fi
214      sleep 0.5
215    done
216
217    echo ================= write_hugetlb_memory.sh output is:
218    cat $output
219    echo ================= end output.
220
221    if [[ "$populate" == "-o" ]] || [[ "$write" == "-w" ]]; then
222      wait_for_hugetlb_memory_to_get_written "$cgroup" "$size"
223    elif [[ "$reserve" != "-n" ]]; then
224      wait_for_hugetlb_memory_to_get_reserved "$cgroup" "$size"
225    else
226      # This case doesn't produce visible effects, but we still have
227      # to wait for the async process to start and execute...
228      sleep 0.5
229    fi
230
231    echo write_result is $write_result
232  else
233    bash write_hugetlb_memory.sh "$size" "$populate" "$write" \
234      "$cgroup" "$path" "$method" "$private" "$reserve"
235    local write_result=$?
236
237    if [[ "$reserve" != "-n" ]]; then
238      wait_for_hugetlb_memory_to_get_reserved "$cgroup" "$size"
239    fi
240  fi
241  set -e
242
243  if [[ "$write_result" == 1 ]]; then
244    reservation_failed=1
245  fi
246
247  # On linus/master, the above process gets SIGBUS'd on oomkill, with
248  # return code 135. On earlier kernels, it gets actual oomkill, with return
249  # code 137, so just check for both conditions in case we're testing
250  # against an earlier kernel.
251  if [[ "$write_result" == 135 ]] || [[ "$write_result" == 137 ]]; then
252    oom_killed=1
253  fi
254
255  local hugetlb_after=$(cat $hugetlb_usage)
256  local reserved_after=$(cat $reserved_usage)
257
258  echo After write:
259  echo hugetlb_usage="$hugetlb_after"
260  echo reserved_usage="$reserved_after"
261
262  hugetlb_difference=$(($hugetlb_after - $hugetlb_before))
263  reserved_difference=$(($reserved_after - $reserved_before))
264}
265
266function cleanup_hugetlb_memory() {
267  set +e
268  local cgroup="$1"
269  if [[ "$(pgrep -f write_to_hugetlbfs)" != "" ]]; then
270    echo killing write_to_hugetlbfs
271    killall -2 --wait write_to_hugetlbfs
272    wait_for_hugetlb_memory_to_get_depleted $cgroup
273  fi
274  set -e
275
276  if [[ -e /mnt/huge ]]; then
277    rm -rf /mnt/huge/*
278    umount /mnt/huge
279    rmdir /mnt/huge
280  fi
281}
282
283function run_test() {
284  local size=$(($1 * ${MB} * 1024 * 1024))
285  local populate="$2"
286  local write="$3"
287  local cgroup_limit=$(($4 * ${MB} * 1024 * 1024))
288  local reservation_limit=$(($5 * ${MB} * 1024 * 1024))
289  local nr_hugepages="$6"
290  local method="$7"
291  local private="$8"
292  local expect_failure="$9"
293  local reserve="${10}"
294
295  # Function return values.
296  hugetlb_difference=0
297  reserved_difference=0
298  reservation_failed=0
299  oom_killed=0
300
301  echo nr hugepages = "$nr_hugepages"
302  echo "$nr_hugepages" >/proc/sys/vm/nr_hugepages
303
304  setup_cgroup "hugetlb_cgroup_test" "$cgroup_limit" "$reservation_limit"
305
306  mkdir -p /mnt/huge
307  mount -t hugetlbfs -o pagesize=${MB}M none /mnt/huge
308
309  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test" "$size" "$populate" \
310    "$write" "/mnt/huge/test" "$method" "$private" "$expect_failure" \
311    "$reserve"
312
313  cleanup_hugetlb_memory "hugetlb_cgroup_test"
314
315  local final_hugetlb=$(cat $cgroup_path/hugetlb_cgroup_test/hugetlb.${MB}MB.$fault_usage_file)
316  local final_reservation=$(cat $cgroup_path/hugetlb_cgroup_test/hugetlb.${MB}MB.$reservation_usage_file)
317
318  echo $hugetlb_difference
319  echo $reserved_difference
320  expect_equal "0" "$final_hugetlb" "final hugetlb is not zero"
321  expect_equal "0" "$final_reservation" "final reservation is not zero"
322}
323
324function run_multiple_cgroup_test() {
325  local size1="$1"
326  local populate1="$2"
327  local write1="$3"
328  local cgroup_limit1="$4"
329  local reservation_limit1="$5"
330
331  local size2="$6"
332  local populate2="$7"
333  local write2="$8"
334  local cgroup_limit2="$9"
335  local reservation_limit2="${10}"
336
337  local nr_hugepages="${11}"
338  local method="${12}"
339  local private="${13}"
340  local expect_failure="${14}"
341  local reserve="${15}"
342
343  # Function return values.
344  hugetlb_difference1=0
345  reserved_difference1=0
346  reservation_failed1=0
347  oom_killed1=0
348
349  hugetlb_difference2=0
350  reserved_difference2=0
351  reservation_failed2=0
352  oom_killed2=0
353
354  echo nr hugepages = "$nr_hugepages"
355  echo "$nr_hugepages" >/proc/sys/vm/nr_hugepages
356
357  setup_cgroup "hugetlb_cgroup_test1" "$cgroup_limit1" "$reservation_limit1"
358  setup_cgroup "hugetlb_cgroup_test2" "$cgroup_limit2" "$reservation_limit2"
359
360  mkdir -p /mnt/huge
361  mount -t hugetlbfs -o pagesize=${MB}M none /mnt/huge
362
363  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test1" "$size1" \
364    "$populate1" "$write1" "/mnt/huge/test1" "$method" "$private" \
365    "$expect_failure" "$reserve"
366
367  hugetlb_difference1=$hugetlb_difference
368  reserved_difference1=$reserved_difference
369  reservation_failed1=$reservation_failed
370  oom_killed1=$oom_killed
371
372  local cgroup1_hugetlb_usage=$cgroup_path/hugetlb_cgroup_test1/hugetlb.${MB}MB.$fault_usage_file
373  local cgroup1_reservation_usage=$cgroup_path/hugetlb_cgroup_test1/hugetlb.${MB}MB.$reservation_usage_file
374  local cgroup2_hugetlb_usage=$cgroup_path/hugetlb_cgroup_test2/hugetlb.${MB}MB.$fault_usage_file
375  local cgroup2_reservation_usage=$cgroup_path/hugetlb_cgroup_test2/hugetlb.${MB}MB.$reservation_usage_file
376
377  local usage_before_second_write=$(cat $cgroup1_hugetlb_usage)
378  local reservation_usage_before_second_write=$(cat $cgroup1_reservation_usage)
379
380  write_hugetlbfs_and_get_usage "hugetlb_cgroup_test2" "$size2" \
381    "$populate2" "$write2" "/mnt/huge/test2" "$method" "$private" \
382    "$expect_failure" "$reserve"
383
384  hugetlb_difference2=$hugetlb_difference
385  reserved_difference2=$reserved_difference
386  reservation_failed2=$reservation_failed
387  oom_killed2=$oom_killed
388
389  expect_equal "$usage_before_second_write" \
390    "$(cat $cgroup1_hugetlb_usage)" "Usage changed."
391  expect_equal "$reservation_usage_before_second_write" \
392    "$(cat $cgroup1_reservation_usage)" "Reservation usage changed."
393
394  cleanup_hugetlb_memory
395
396  local final_hugetlb=$(cat $cgroup1_hugetlb_usage)
397  local final_reservation=$(cat $cgroup1_reservation_usage)
398
399  expect_equal "0" "$final_hugetlb" \
400    "hugetlbt_cgroup_test1 final hugetlb is not zero"
401  expect_equal "0" "$final_reservation" \
402    "hugetlbt_cgroup_test1 final reservation is not zero"
403
404  local final_hugetlb=$(cat $cgroup2_hugetlb_usage)
405  local final_reservation=$(cat $cgroup2_reservation_usage)
406
407  expect_equal "0" "$final_hugetlb" \
408    "hugetlb_cgroup_test2 final hugetlb is not zero"
409  expect_equal "0" "$final_reservation" \
410    "hugetlb_cgroup_test2 final reservation is not zero"
411}
412
413cleanup
414
415for populate in "" "-o"; do
416  for method in 0 1 2; do
417    for private in "" "-r"; do
418      for reserve in "" "-n"; do
419
420        # Skip mmap(MAP_HUGETLB | MAP_SHARED). Doesn't seem to be supported.
421        if [[ "$method" == 1 ]] && [[ "$private" == "" ]]; then
422          continue
423        fi
424
425        # Skip populated shmem tests. Doesn't seem to be supported.
426        if [[ "$method" == 2"" ]] && [[ "$populate" == "-o" ]]; then
427          continue
428        fi
429
430        if [[ "$method" == 2"" ]] && [[ "$reserve" == "-n" ]]; then
431          continue
432        fi
433
434        cleanup
435        echo
436        echo
437        echo
438        echo Test normal case.
439        echo private=$private, populate=$populate, method=$method, reserve=$reserve
440        run_test 5 "$populate" "" 10 10 10 "$method" "$private" "0" "$reserve"
441
442        echo Memory charged to hugtlb=$hugetlb_difference
443        echo Memory charged to reservation=$reserved_difference
444
445        if [[ "$populate" == "-o" ]]; then
446          expect_equal "$((5 * $MB * 1024 * 1024))" "$hugetlb_difference" \
447            "Reserved memory charged to hugetlb cgroup."
448        else
449          expect_equal "0" "$hugetlb_difference" \
450            "Reserved memory charged to hugetlb cgroup."
451        fi
452
453        if [[ "$reserve" != "-n" ]] || [[ "$populate" == "-o" ]]; then
454          expect_equal "$((5 * $MB * 1024 * 1024))" "$reserved_difference" \
455            "Reserved memory not charged to reservation usage."
456        else
457          expect_equal "0" "$reserved_difference" \
458            "Reserved memory not charged to reservation usage."
459        fi
460
461        echo 'PASS'
462
463        cleanup
464        echo
465        echo
466        echo
467        echo Test normal case with write.
468        echo private=$private, populate=$populate, method=$method, reserve=$reserve
469        run_test 5 "$populate" '-w' 5 5 10 "$method" "$private" "0" "$reserve"
470
471        echo Memory charged to hugtlb=$hugetlb_difference
472        echo Memory charged to reservation=$reserved_difference
473
474        expect_equal "$((5 * $MB * 1024 * 1024))" "$hugetlb_difference" \
475          "Reserved memory charged to hugetlb cgroup."
476
477        expect_equal "$((5 * $MB * 1024 * 1024))" "$reserved_difference" \
478          "Reserved memory not charged to reservation usage."
479
480        echo 'PASS'
481
482        cleanup
483        continue
484        echo
485        echo
486        echo
487        echo Test more than reservation case.
488        echo private=$private, populate=$populate, method=$method, reserve=$reserve
489
490        if [ "$reserve" != "-n" ]; then
491          run_test "5" "$populate" '' "10" "2" "10" "$method" "$private" "1" \
492            "$reserve"
493
494          expect_equal "1" "$reservation_failed" "Reservation succeeded."
495        fi
496
497        echo 'PASS'
498
499        cleanup
500
501        echo
502        echo
503        echo
504        echo Test more than cgroup limit case.
505        echo private=$private, populate=$populate, method=$method, reserve=$reserve
506
507        # Not sure if shm memory can be cleaned up when the process gets sigbus'd.
508        if [[ "$method" != 2 ]]; then
509          run_test 5 "$populate" "-w" 2 10 10 "$method" "$private" "1" "$reserve"
510
511          expect_equal "1" "$oom_killed" "Not oom killed."
512        fi
513        echo 'PASS'
514
515        cleanup
516
517        echo
518        echo
519        echo
520        echo Test normal case, multiple cgroups.
521        echo private=$private, populate=$populate, method=$method, reserve=$reserve
522        run_multiple_cgroup_test "3" "$populate" "" "10" "10" "5" \
523          "$populate" "" "10" "10" "10" \
524          "$method" "$private" "0" "$reserve"
525
526        echo Memory charged to hugtlb1=$hugetlb_difference1
527        echo Memory charged to reservation1=$reserved_difference1
528        echo Memory charged to hugtlb2=$hugetlb_difference2
529        echo Memory charged to reservation2=$reserved_difference2
530
531        if [[ "$reserve" != "-n" ]] || [[ "$populate" == "-o" ]]; then
532          expect_equal "3" "$reserved_difference1" \
533            "Incorrect reservations charged to cgroup 1."
534
535          expect_equal "5" "$reserved_difference2" \
536            "Incorrect reservation charged to cgroup 2."
537
538        else
539          expect_equal "0" "$reserved_difference1" \
540            "Incorrect reservations charged to cgroup 1."
541
542          expect_equal "0" "$reserved_difference2" \
543            "Incorrect reservation charged to cgroup 2."
544        fi
545
546        if [[ "$populate" == "-o" ]]; then
547          expect_equal "3" "$hugetlb_difference1" \
548            "Incorrect hugetlb charged to cgroup 1."
549
550          expect_equal "5" "$hugetlb_difference2" \
551            "Incorrect hugetlb charged to cgroup 2."
552
553        else
554          expect_equal "0" "$hugetlb_difference1" \
555            "Incorrect hugetlb charged to cgroup 1."
556
557          expect_equal "0" "$hugetlb_difference2" \
558            "Incorrect hugetlb charged to cgroup 2."
559        fi
560        echo 'PASS'
561
562        cleanup
563        echo
564        echo
565        echo
566        echo Test normal case with write, multiple cgroups.
567        echo private=$private, populate=$populate, method=$method, reserve=$reserve
568        run_multiple_cgroup_test "3" "$populate" "-w" "10" "10" "5" \
569          "$populate" "-w" "10" "10" "10" \
570          "$method" "$private" "0" "$reserve"
571
572        echo Memory charged to hugtlb1=$hugetlb_difference1
573        echo Memory charged to reservation1=$reserved_difference1
574        echo Memory charged to hugtlb2=$hugetlb_difference2
575        echo Memory charged to reservation2=$reserved_difference2
576
577        expect_equal "3" "$hugetlb_difference1" \
578          "Incorrect hugetlb charged to cgroup 1."
579
580        expect_equal "3" "$reserved_difference1" \
581          "Incorrect reservation charged to cgroup 1."
582
583        expect_equal "5" "$hugetlb_difference2" \
584          "Incorrect hugetlb charged to cgroup 2."
585
586        expect_equal "5" "$reserved_difference2" \
587          "Incorrected reservation charged to cgroup 2."
588        echo 'PASS'
589
590        cleanup
591
592      done # reserve
593    done   # private
594  done     # populate
595done       # method
596
597if [[ $do_umount ]]; then
598  umount $cgroup_path
599  rmdir $cgroup_path
600fi
601
602echo "$nr_hugepgs" > /proc/sys/vm/nr_hugepages
603