|
class RedisTimeseriesMetric |
|
cattr_accessor :redis |
|
self.redis ||= $redis |
|
|
|
# Public: Initialize a new metric |
|
# |
|
# metric_name - The String metric name that is used as part of the |
|
# redis key |
|
# tags - An Array of Strings of required tags to be provided |
|
# with all calls to `incr()` |
|
# options - The Hash options used to provide an interval (default: {}): |
|
# :interval - The Integer time interval to round metrics |
|
# to (default: 1.hour) |
|
# |
|
# Examples |
|
# |
|
# @metric = RedisTimeseriesMetric.new('emails', %w(hostname)) |
|
# |
|
def initialize(metric_name, *tags) |
|
options = tags.pop if tags.last.is_a?(Hash) |
|
|
|
@metric_name = metric_name |
|
@tags = tags.flatten.sort |
|
@interval = options[:interval] || 1.hour.to_i |
|
end |
|
|
|
# Public: Increment the metric |
|
# |
|
# val - The Integer of how much to increment the metric by (default: 1) |
|
# time - The Time the increment happened in (default: Time.now) |
|
# tags - A Hash of the tags and values to attribute the `val` to (optional) |
|
# |
|
# Examples |
|
# |
|
# @metric.incr(1, Time.now, :hostname => Socket.gethostname) |
|
# |
|
# Returns nothing. |
|
def incr(val = 1, time = Time.now, tags = {}) |
|
# Round the time to our current rounding interval |
|
time -= time % @interval |
|
|
|
if !(missing_tags = @tags - tags.keys).empty? |
|
raise "Required tags were not found: #{missing_tags * ', '}" |
|
end |
|
|
|
tags.each do |k, v| |
|
redis.incrby "#{@metric_name}:#{k}=#{v}:#{time.to_i}", val |
|
end |
|
redis.incrby "#{@metric_name}:#{time.to_i}", val |
|
end |
|
|
|
# Public: Retrieve a sum of the counter for a given time range |
|
# |
|
# start_time - The Time to start retrieving metrics |
|
# end_time - The Time to stop retrieving metrics |
|
# tags - A Hash of the tags and values to gather (optional) |
|
# |
|
# Examples |
|
# |
|
# @metric.count(1.day.ago, Time.now) |
|
# # => 29382 |
|
# |
|
# @metric.count(1.day.ago, Time.now, :hostname => 'email1') |
|
# # => 1827 |
|
# |
|
# Returns the Integer sum of the metrics. |
|
def count(start_time, end_time, tags = {}) |
|
raise "Cannot specify more than one tag" if tags.length > 1 |
|
|
|
keys, num_targets = *keylist(start_time, end_time, tags) |
|
|
|
redis.mget(*keys).sum { |i| i.to_i } |
|
end |
|
|
|
# Public: Retrieve a timeseries of the counter for a given time range |
|
# |
|
# start_time - The Time to start retrieving metrics |
|
# end_time - The Time to stop retrieving metrics |
|
# tags - A Hash of the tags and values to gather (optional) |
|
# |
|
# Examples |
|
# |
|
# @metric.timeseries(1.day.ago, Time.now) |
|
# # => [ 2, 7, 0, 82, 7, ... , 3 ] |
|
# |
|
# @metric.timeseries(1.day.ago, Time.now, :hostname => 'email1') |
|
# # => [ 1, 3, 0, 23, 3, ..., 1 ] |
|
# |
|
# Returns the Array of Integers for each time unit for the metric. |
|
def timeseries(start_time, end_time, tags = {}) |
|
raise "Cannot specify more than one tag" if tags.length > 1 |
|
|
|
keys, num_targets = *keylist(start_time, end_time, tags) |
|
|
|
redis.mget(*keys).each_slice(num_targets).collect { |s| s.sum { |i| i.to_i } } |
|
end |
|
|
|
private |
|
def keylist(start_time, end_time, tags = {}) |
|
raise "Cannot specify more than one tag" if tags.length > 1 |
|
|
|
times = timerange(start_time, end_time) |
|
|
|
if tags.empty? |
|
keys = times.collect do |time| |
|
"#{@metric_name}:#{time}" |
|
end |
|
else |
|
key, values = *tags.first |
|
|
|
keys = times.collect do |time| |
|
Array(values).collect do |value| |
|
"#{@metric_name}:#{key}=#{value}:#{time}" |
|
end |
|
end |
|
end |
|
|
|
[ keys, tags.empty? ? 1 : Array(values).length ] |
|
end |
|
|
|
def timerange(start_time, end_time) |
|
start_time, end_time = start_time.to_i, end_time.to_i |
|
|
|
start_time -= start_time % @interval if start_time % @interval != 0 |
|
end_time -= end_time % @interval if end_time % @interval != 0 |
|
|
|
Range.new(start_time, end_time).step(@interval) |
|
end |
|
end |