Skip to content

Instantly share code, notes, and snippets.

@henderea
Last active February 23, 2017 18:22
Show Gist options
  • Save henderea/52f2374d633da2491e2e356f336fc21f to your computer and use it in GitHub Desktop.
Save henderea/52f2374d633da2491e2e356f336fc21f to your computer and use it in GitHub Desktop.
purge-watch.rb: run it as root on mac and it will automatically purge when the free memory goes below the threshold
#!/usr/bin/env ruby
# MUST BE RUN AS ROOT. You can set it up in Launch Daemons (/Library/LaunchDaemons/*.plist) to have it start on login.
# Using the LaunchControl app, add it under "Global Daemons". In LingonX, set it to run as root.
# You also need growlnotify installed at "/usr/local/bin/growlnotify". You can install it with "brew cask install growlnotify"
# To disable growl notifications, set the key :growl_enable to false in purge-watch-settings.yaml (see below)
# You can now set a maximum time to wait before running a purge. If the free memory isn't low enough to trigger the purge,
# settings the max wait will cause it to still purge if it has been at least :max_wait seconds since the last purge.
# It defaults to being off, but any POSITIVE value (doesn't include 0) will cause it to not wait more than that amount of time
# between purges. The max wait setting does not apply when the memory is below the threshold, so if you set max wait to be
# lower than min wait, it will still wait until it reaches the min-wait value if the free memory is below the threshold.
# Create a purge-watch-settings.yaml in the same directory as purge-watch.rb if you want to override the default threshold,
# check interval, and minimum time between purges. Threshold is in GB if <= 128, bytes otherwise. Growl-enable is a boolean.
# Other two settings are in seconds. YAML file contains a map with keys (all optional) :threshold, :interval, :min_wait,
# :max_wait, and :growl_enable.
require 'yaml'
DEFAULT_THRESHOLD = 2.5
DEFAULT_INTERVAL = 5
DEFAULT_MIN_WAIT = 60
DEFAULT_GROWL_ENABLE = true
DEFAULT_MAX_WAIT = -1
SETTINGS_PATH = File.expand_path('purge-watch-settings.yaml', __dir__)
$stdout.sync = true
def get_free_mem
page_size = `vm_stat | grep 'page size' | awk '{ print $8 }'`.chomp!.to_i
pages_free = `vm_stat | grep 'Pages free' | awk '{ print $3 }'`.chomp![0...-1].to_i
page_size * pages_free
end
def format_bytes(bytes, show_raw = false)
abs_b = bytes.abs
return "#{bytes} B" if abs_b <= 1
lg = (Math.log(abs_b)/Math.log(1024)).floor.to_f
unit = %w(B KB MB GB TB)[lg]
"#{'%.2f' % (bytes.to_f / 1024.0**lg)} #{unit}#{show_raw ? " (#{bytes} B)" : ''}"
end
def notify(title, msg = nil)
`/usr/local/bin/growlnotify -m '#{msg || title}' '#{title}'` if $settings[:growl_enable]
end
def my_log(msg)
puts "[#{Time.now.strftime('%F %r')}] #{msg}"
end
def get_settings
if !$last_settings_fetch || (Time.now - $last_settings_fetch) >= $settings[:min_wait]
my_log "Checking settings"
$last_settings_fetch = Time.now
old_settings = $settings || {}
$settings = {}
if File.exist?(SETTINGS_PATH)
$settings = YAML.load_file(SETTINGS_PATH)
new_settings = YAML.load_file(SETTINGS_PATH)
end
$settings[:threshold] ||= DEFAULT_THRESHOLD;
$settings[:threshold] = $settings[:threshold] * (1024**3) if $settings[:threshold] <= 128
$settings[:interval] ||= DEFAULT_INTERVAL
$settings[:min_wait] ||= DEFAULT_MIN_WAIT
$settings[:growl_enable] = DEFAULT_GROWL_ENABLE unless $settings.key?(:growl_enable)
$settings[:max_wait] ||= DEFAULT_MAX_WAIT
my_log "settings = #{new_settings}" if File.exist?(SETTINGS_PATH) && $settings.any? {|k, v| v != old_settings[k] }
end
end
last_purge = nil
def check_wait(wait, tnow, last_purge)
wait > 0 && (last_purge.nil? || (tnow - last_purge) >= wait)
end
def purge(free_mem)
my_log "Free memory at #{format_bytes(free_mem, true)}; running purge"
system('/usr/sbin/purge')
new_free_mem = get_free_mem
my_log "Freed #{format_bytes(new_free_mem - free_mem, true)}. Free memory now at #{format_bytes(new_free_mem, true)}"
notify "Purged #{format_bytes(new_free_mem - free_mem)}", "Free memory now at #{format_bytes(new_free_mem)}"
Time.now
end
loop {
get_settings
free_mem = get_free_mem
tnow = Time.now
if free_mem < $settings[:threshold]
if check_wait($settings[:min_wait], tnow, last_purge)
last_purge = purge(free_mem)
else
my_log "Would purge, but need to wait #{'%.2f' % ($settings[:min_wait].to_f - (tnow - last_purge))} more seconds"
end
elsif check_wait($settings[:max_wait], tnow, last_purge)
last_purge = purge(free_mem)
end
sleep($settings[:interval])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment