PageRenderTime 39ms CodeModel.GetById 23ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/batsd/redis.rb

http://github.com/noahhl/batsd
Ruby | 153 lines | 95 code | 14 blank | 44 comment | 8 complexity | 7beee084053fc3ac607458c1c1419361 MD5 | raw file
  1module Batsd
  2  # 
  3  # This is a thin wrapper around the redis client to
  4  # handle multistep procedures that could be executed using
  5  # Redis scripting
  6  #
  7  class Redis
  8
  9    # Opens a new connection to the redis instance specified 
 10    # in the configuration or localhost:6379
 11    #
 12    def initialize(options)
 13      @redis = ::Redis.new(options[:redis] || {host: "127.0.0.1", port: 6379} )
 14      @redis.ping
 15      @lua_support = @redis.info['redis_version'].to_f >= 2.5
 16      @retentions = options[:retentions].keys
 17    end
 18    
 19    # Expose the redis client directly
 20    def client
 21      @redis
 22    end
 23
 24    # Store a counter measurement for each of the specified retentions
 25    #
 26    # * For shortest retention (where timestep == flush interval), add the
 27    #   value and timestamp to the appropriate zset
 28    #
 29    # * For longer retention intervals, increment the appropriate counter
 30    #   by the value specified.
 31    #
 32    # TODO: This can be done in a single network request by rewriting
 33    # it as a redis script in Lua
 34    #
 35    def store_and_update_all_counters(timestamp, key, value)
 36      @retentions.each_with_index do |t, index|
 37        if index.zero?
 38          @redis.zadd key, timestamp, "#{timestamp}<X>#{value}"
 39        else
 40          @redis.incrby "#{key}:#{t}", value
 41          @redis.expire "#{key}:#{t}", t.to_i * 2
 42        end
 43      end
 44    end
 45
 46    # Store a timer to a zset
 47    #
 48    def store_timer(timestamp, key, value)
 49      @redis.zadd key, timestamp, "#{timestamp}<X>#{value}"
 50    end
 51
 52    # Store unaggregated, raw timer values in bucketed keys
 53    # so that they can actually be aggregated "raw"
 54    #
 55    # The set of tiemrs are stored as a single string key delimited by 
 56    # \x0. In benchmarks, this is more efficient in memory by 2-3x, and
 57    # less efficient in time by ~10%
 58    #
 59    # TODO: can this be done more efficiently with redis scripting?
 60    def store_raw_timers_for_aggregations(key, values)
 61      @retentions.each_with_index do |t, index|
 62        next if index.zero?
 63        @redis.append "#{key}:#{t}", "<X>#{values.join("<X>")}"
 64        @redis.expire "#{key}:#{t}", t.to_i * 2
 65      end
 66    end
 67    
 68    # Returns the value of a key and then deletes it.
 69    def get_and_clear_key(key)
 70      if @lua_support
 71        cmd = <<-EOF
 72          local str = redis.call('get', KEYS[1])
 73          redis.call('del', KEYS[1])
 74          return str
 75        EOF
 76        @redis.eval(cmd, [key.to_sym])
 77      else
 78        @redis.multi do |multi|
 79          multi.get(key)
 80          multi.del(key)
 81        end.first
 82      end
 83    end
 84
 85    # Deletes the given key
 86    def clear_key(key)
 87      @redis.del(key)
 88    end
 89    
 90    # Create an array out of a string of values delimited by <X>
 91    def extract_values_from_string(key)
 92      if @lua_support
 93        cmd = <<-EOF
 94          local t={} ; local i=1
 95          local str = redis.call('get', KEYS[1])
 96          if (str) then
 97            for s in string.gmatch(str, "([^".."<X>".."]+)") do
 98              t[i] = s
 99              i = i + 1
100            end
101            redis.call('del', KEYS[1])
102          end
103          return t
104        EOF
105        @redis.eval(cmd, [key.to_sym])
106      else
107        values = get_and_clear_key(key)
108        values.split('<X>') if values
109      end
110    end
111
112    # Truncate a zset since a treshold time
113    #
114    def truncate_zset(key, since)
115      @redis.zremrangebyscore key, 0, since
116    end
117
118    # Return properly formatted values from the zset
119    def values_from_zset(metric, begin_ts, end_ts)
120      begin
121        values = @redis.zrangebyscore(metric, begin_ts, end_ts)
122        values.collect{|val| ts, val = val.split("<X>"); {timestamp: ts.to_i, value: val.to_f } }
123      rescue
124        []
125      end
126    end
127
128    # Convenience accessor to members of datapoints set
129    #
130    def datapoints(with_gauges=true)
131      datapoints = @redis.smembers "datapoints"
132      unless with_gauges
133        datapoints.reject!{|d| (d.match(/^gauge/) rescue false) }
134      end
135      datapoints
136    end
137
138    # Stores a reference to the datapoint in 
139    # the 'datapoints' set
140    #
141    def add_datapoint(key)
142      @redis.sadd "datapoints", key
143    end
144
145    # Stores a reference to the datapoint in 
146    # the 'datapoints' set
147    #
148    def remove_datapoint(key)
149      @redis.srem "datapoints", key
150    end
151
152  end
153end