reclaimer.sh 3.1 KB

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