summaryrefslogtreecommitdiff
path: root/lib/byteback/disk_free_history.rb
blob: 0c67370107843a3333d6f02fa428079b9f224d20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/ruby

require 'sys/filesystem'

module Byteback
  class DiskFreeReading < Struct.new(:fsstat, :time)
    def initialize(fsstat, time = Time.now)
      self.fsstat = fsstat
      self.time = time
    end

    # helper method to return %age of disc space free
    #
    def percent_free
      fsstat.blocks_available * 100 / fsstat.blocks
    end
  end

  # A simple round-robin list  to store a short history of a given mount
  # point's disk space history.
  #
  class DiskFreeHistory
    attr_reader :mountpoint, :history_file

    MINIMUM_INTERVAL = 5 * 60 # don't take readings more than 5 mins apart
    MAXIMUM_AGE = 7 * 24 * 60 * 60 # delete readings after a week

    # Initialize a new list storing the disc space history for the given
    # mount point.
    #
    def initialize(mountpoint, history_file = nil)
      history_file = "#{mountpoint}/.disk_free_history" unless
        history_file
      @history_file = history_file
      @mountpoint = mountpoint
      load!
    end

    # Take a new reading
    #
    def new_reading!
      reading = DiskFreeReading.new(Sys::Filesystem.stat(@mountpoint))

      # Don't record a new reading if it's exactly the same as last time,
      # and less than the minimum interval.
      #
      return nil if @list.last &&
                    @list.last.fsstat.blocks_available == reading.fsstat.blocks_available &&
                    Time.now - @list.last.time < MINIMUM_INTERVAL

      @list << reading

      save!
    end

    def list
      load! unless @list
      @list
    end

    def gradient(last_n_seconds, &value_from_reading)
      value_from_reading ||= proc { |r| r.fsstat.blocks_available }
      earliest = Time.now - last_n_seconds

      total = 0
      readings = 0
      later_reading = nil

      list.reverse.each do |reading|
        if later_reading
          difference =
            value_from_reading.call(reading) -
            value_from_reading.call(later_reading)
          total += difference
        end
        readings += 1
        break if reading.time < earliest
        later_reading = reading
      end

      return 0 if readings == 0

      total / readings
    end

    protected

    def load!
      File.open(@history_file) do |fh|
        @list = Marshal.restore(fh.read(1_000_000))
      end
    rescue Errno::ENOENT, TypeError => err
      @list = []
      new_reading!
    end

    def save!
      list.shift while Time.now - list.first.time > MAXIMUM_AGE

      tmp = "#{@history_file}.#{$PROCESS_ID}.#{rand(9_999_999_999)}"
      begin
        File.open(tmp, 'w') do |fh|
          fh.write(Marshal.dump(list))
          File.rename(tmp, @history_file)
        end
      ensure
        File.unlink(tmp) if File.exist?(tmp)
      end
    end
  end
end