reclaimer.sh 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/bin/bash
  2. set -euo pipefail
  3. size=1M
  4. threshold=100
  5. interval=1
  6. debug=
  7. usage() {
  8. cat <<EOF
  9. Usage: $0 [OPTIONS]
  10. Reclaim memory at fixed interval
  11. Options:
  12. -s <size> Memory to reclaim in one run. Available units: K, M
  13. [default: $size]
  14. -t <threshold> PSI threshold to continue reclaiming.
  15. [default: $threshold]
  16. -i <interval> Interval between reclaims in seconds.
  17. [default: $interval]
  18. -d Enable debug output.
  19. [default: no]
  20. -h Show this message.
  21. EOF
  22. exit "$1"
  23. }
  24. log() {
  25. printf "[%s] %5s: %s\n" "$(date +%F\ %T)" "$1" "$2" >&2
  26. }
  27. log_debug() {
  28. log "DEBUG" "$1"
  29. }
  30. log_info() {
  31. log "INFO" "$1"
  32. }
  33. log_warn() {
  34. log "WARN" "$1"
  35. }
  36. log_fatal() {
  37. log "FATAL" "$1"
  38. }
  39. die() {
  40. log_fatal "$1"
  41. exit 1
  42. }
  43. readpsi() {
  44. psi_path="/proc/pressure/memory"
  45. head -n 1 "$psi_path" | awk -F= '{ print $5 }'
  46. }
  47. init_reclaim_states() {
  48. last_psi="$(readpsi)"
  49. psi="$last_psi"
  50. size_kb=
  51. # Convert size into KBs
  52. case "$size" in
  53. [0-9]*M|[0-9]*m)
  54. size_kb="$(echo "$size" | tr -d mM)"
  55. size_kb="$((size_kb * 1024))"
  56. ;;
  57. [0-9]*K|[0-9]*k)
  58. size_kb="$(echo "$size" | tr -d kK)"
  59. ;;
  60. *)
  61. die "invalid size format: $size"
  62. ;;
  63. esac
  64. # Scale PSI threshold by interval
  65. threshold="$((threshold * interval))"
  66. log_info "reclaim params:"
  67. log_info "size=$size threshold=$threshold interval=$interval"
  68. }
  69. update_states() {
  70. last_psi="$psi"
  71. psi="$(readpsi)"
  72. delta="$((psi - last_psi))"
  73. }
  74. skip_reclaim() {
  75. log_info "reclaim skipped for $1"
  76. }
  77. should_reclaim() {
  78. if [[ "$delta" -gt "$threshold" ]]; then
  79. skip_reclaim "high pressure: $delta > $threshold over the last ${interval}s"
  80. return 1
  81. fi
  82. return 0
  83. }
  84. reclaim() {
  85. cgpath="/sys/fs/cgroup"
  86. echo "${size_kb}K" > "$cgpath/memory.reclaim"
  87. }
  88. dump_reclaim_args() {
  89. log_debug "last_psi=$last_psi psi=$psi delta=$delta"
  90. }
  91. dump_memusage() {
  92. NL=$'\n'
  93. _usage="$NL$(free -h)$NL"
  94. log_debug "memusage: $_usage"
  95. }
  96. wait_for_next_run() {
  97. sleep "$interval"
  98. }
  99. reclaim_run() {
  100. update_states
  101. [ -n "$debug" ] && dump_reclaim_args
  102. if ! should_reclaim; then
  103. return
  104. fi
  105. if ! reclaim; then
  106. log_warn "unable to reclaim, status=$?"
  107. return
  108. fi
  109. log_info "reclaimed memory ${size_kb}K"
  110. [ -n "$debug" ] && dump_memusage
  111. return
  112. }
  113. while getopts "s:t:i:dh" arg; do
  114. case "$arg" in
  115. s) size="$OPTARG" ;;
  116. t) threshold="$OPTARG" ;;
  117. i) interval="$OPTARG" ;;
  118. d) debug=y ;;
  119. h) usage 0 ;;
  120. ?) usage 1 ;;
  121. esac
  122. done
  123. shift $((OPTIND - 1))
  124. init_reclaim_states
  125. while true; do
  126. reclaim_run
  127. wait_for_next_run
  128. done