/lib/batsd/redis.rb

http://github.com/noahhl/batsd · Ruby · 153 lines · 95 code · 14 blank · 44 comment · 8 complexity · 7beee084053fc3ac607458c1c1419361 MD5 · raw file

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