Advertisement
Guest User

btrfs auto rebalancing

a guest
Feb 14th, 2016
1,103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 3.32 KB | None | 0 0
  1. #!/usr/bin/ruby
  2.  
  3. # Maximum free space wasted as percentage
  4. MAX_WASTED_RATIO = 0.3
  5. # Maximum unallocated space above which no check is needed
  6. UNALLOCATED_THRESHOLD_RATIO = 0.3
  7. # Target max waste when rebalancing (not much less than max_wasted)
  8. TARGET_WASTED_RATIO = 0.25
  9. MAX_FAILURES = 20
  10. MAX_REBALANCES = 100
  11.  
  12. require 'open3'
  13.  
  14. def filesystems
  15.   mounts = []
  16.   IO.popen("mount") do |io|
  17.     io.each_line do |line|
  18.       if match = line.match(/^(.*) on (.*) type btrfs/)
  19.         mounts << [ match[1], match[2] ] unless mounts.map(&:first).include?(match[1])
  20.       end
  21.     end
  22.   end
  23.   return mounts.map { |m| m[1] }
  24. end
  25.  
  26. class Btrfs
  27.   def initialize(mountpoint)
  28.     @mountpoint = mountpoint
  29.     refresh_usage
  30.   end
  31.  
  32.   def refresh_usage
  33.     IO.popen("btrfs fi usage --raw '#{@mountpoint}'") do |io|
  34.       io.each_line do |line|
  35.         case line
  36.         when /Device size:\s*(\d*)$/
  37.           @device_size = Regexp.last_match[1].to_i
  38.         when /Device allocated:\s*(\d*)$/
  39.           @allocated = Regexp.last_match[1].to_i
  40.         when /Device unallocated:\s*(\d*)$/
  41.           @unallocated = Regexp.last_match[1].to_i
  42.         when /Free \(estimated\):\s*(\d*)\s*\(min: \d*\)$/
  43.           @free = Regexp.last_match[1].to_i
  44.         when /Data ratio:\s*(\d+\.\d+)$/
  45.           @ratio = Regexp.last_match[1].to_f
  46.         end
  47.       end
  48.     end
  49.   end
  50.  
  51.   def rebalance_if_needed
  52.     if rebalance_needed?
  53.       log("waste %.2f%%" % (free_wasted * 100))
  54.       log "unallocated: #{@unallocated}"
  55.       log "ratio:       #{@ratio}"
  56.       log "free:        #{@free}"
  57.       rebalance
  58.     end
  59.   rescue => ex
  60.     log "can't rebalance, error: #{ex}"
  61.   end
  62.  
  63.   def rebalance
  64.     failures = 0
  65.     count = 0
  66.     log "First quick pass in case removing empty chunks would be enough"
  67.     balance_usage(0)
  68.     # Try to guess best usage_target
  69.     usage_target = ((1 - TARGET_WASTED_RATIO) * 50).to_i
  70.     while (free_wasted > target_wasted) && (count < MAX_REBALANCES)
  71.       log "rebalance with usage: #{usage_target}"
  72.       count += 1
  73.       fail "not enough space, remove snapshots or files" if usage_target == 0
  74.       exit_status = balance_usage(usage_target)
  75.       if exit_status.exitstatus != 0
  76.         failures += 1
  77.         if usage_target == 0
  78.           fail "can't retry rebalancing, usage_target reached 0 already"
  79.         end
  80.         if failures >= MAX_FAILURES
  81.           fail "too many rebalance failures: MAX_FAILURES"
  82.         end
  83.         usage_target -= 1
  84.       else
  85.         failures = 0
  86.         usage_target += 1
  87.       end
  88.       refresh_usage
  89.       log("waste %.2f%%" % (free_wasted * 100))
  90.     end
  91.   end
  92.  
  93.   def balance_usage(target)
  94.     output, status = Open3.capture2e("btrfs fi balance start -dusage=#{target} #{@mountpoint}")
  95.     log output
  96.     return status
  97.   end
  98.  
  99.   def log(msg)
  100.     puts "#{@mountpoint}: #{msg}"
  101.   end
  102.  
  103.   def rebalance_needed?
  104.     (unallocated_ratio < UNALLOCATED_THRESHOLD_RATIO) && (free_wasted > max_wasted)
  105.   end
  106.  
  107.   def free_wasted
  108.     1 - (@unallocated.to_f / (@free * @ratio))
  109.   end
  110.   def unallocated_ratio
  111.     @unallocated.to_f / @device_size
  112.   end
  113.   def max_wasted
  114.     MAX_WASTED_RATIO
  115.   end
  116.  
  117.   def target_wasted
  118.     TARGET_WASTED_RATIO
  119.   end
  120. end
  121.  
  122. def rebalance_fs
  123.   filesystems.each do |fs|
  124.     Btrfs.new(fs).rebalance_if_needed
  125.   end
  126. end
  127.  
  128. rebalance_fs
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement