reclaimer.sh 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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]
  11. Reclaim memory at 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. reclaim() {
  104. cgpath="/sys/fs/cgroup"
  105. echo "$1" > "$cgpath/memory.reclaim"
  106. }
  107. dump_reclaim_args() {
  108. log_debug "last_psi=$last_psi psi=$psi delta=$delta integral=$integral"
  109. }
  110. dump_memusage() {
  111. NL=$'\n'
  112. _usage="$NL$(free -h)$NL"
  113. log_debug "memusage: $_usage"
  114. }
  115. wait_for_next_run() {
  116. sleep "$interval"
  117. }
  118. reclaim_run() {
  119. update_states
  120. [ -n "$debug" ] && dump_reclaim_args
  121. if ! should_reclaim; then
  122. return
  123. fi
  124. reclaim_size_kb="$(calc_size_to_reclaim_kb)"
  125. if ! reclaim "${reclaim_size_kb}K"; then
  126. log_warn "unable to reclaim, status=$?"
  127. return
  128. fi
  129. log_info "reclaimed memory ${reclaim_size_kb}K"
  130. [ -n "$debug" ] && dump_memusage
  131. return
  132. }
  133. while getopts "s:t:i:l:dh" arg; do
  134. case "$arg" in
  135. s) size="$OPTARG" ;;
  136. t) threshold="$OPTARG" ;;
  137. i) interval="$OPTARG" ;;
  138. l) loss="$OPTARG" ;;
  139. d) debug=y ;;
  140. h) usage 0 ;;
  141. ?) usage 1 ;;
  142. esac
  143. done
  144. shift $((OPTIND - 1))
  145. init_reclaim_states
  146. while true; do
  147. reclaim_run
  148. wait_for_next_run
  149. done