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