Last active
February 23, 2017 18:22
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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