Class: Retained::Tracker

Inherits:
Object
  • Object
show all
Defined in:
lib/retained/tracker.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = Configuration.new) ⇒ Tracker

Returns a new instance of Tracker.



10
11
12
# File 'lib/retained/tracker.rb', line 10

def initialize(config = Configuration.new)
  @config = config
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



8
9
10
# File 'lib/retained/tracker.rb', line 8

def config
  @config
end

Instance Method Details

#active?(entity, group: nil, period: Time.now) ⇒ Boolean

Returns true if the entity was active in the given period, or now if no period is provided. If a group or an array of groups is provided activity will only be considered based on those groups.

Returns:

  • (Boolean)


52
53
54
55
56
57
58
59
60
# File 'lib/retained/tracker.rb', line 52

def active?(entity, group: nil, period: Time.now)
  group = [group] if group.is_a?(String)
  group = groups if group == [] || !group

  group.to_a.each do |g|
    return true if config.redis_connection.getbit(key_period(g, period), entity_index(entity, g)) == 1
  end
  false
end

#configure {|config| ... } ⇒ Object

Yields:



14
15
16
# File 'lib/retained/tracker.rb', line 14

def configure
  yield(config)
end

#entity_index(entity, group) ⇒ Object

Returns the index (offset) of the entity within the group.

Thanks to crashlytics for the monotonic_zadd approach taken here www.slideshare.net/crashlytics/crashlytics-on-redis-analytics



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/retained/tracker.rb', line 71

def entity_index(entity, group)
  monotonic_zadd = <<LUA
    local sequential_id = redis.call('zscore', KEYS[1], ARGV[1])
    if not sequential_id then
      sequential_id = redis.call('zcard', KEYS[1])
      redis.call('zadd', KEYS[1], sequential_id, ARGV[1])
    end
    return sequential_id
LUA

  key = "#{config.prefix}:entity_ids:#{group}"
  config.redis_connection.eval(monotonic_zadd, [key], [entity.to_s]).to_i
end

#groupsObject

Returns an array of all groups



63
64
65
# File 'lib/retained/tracker.rb', line 63

def groups
  config.redis_connection.smembers "#{config.prefix}:groups"
end

#key_period(group, period) ⇒ Object

Returns the key for the group at the period. All periods are internally stored relative to UTC.



87
88
89
# File 'lib/retained/tracker.rb', line 87

def key_period(group, period)
  "#{config.prefix}:#{group}:#{period_start(group, period).to_i}"
end

#retain(entity, group: 'default', period: Time.now) ⇒ Object

Tracks the entity as active at the period, or now if no period is provided.



20
21
22
23
# File 'lib/retained/tracker.rb', line 20

def retain(entity, group: 'default', period: Time.now)
  index = entity_index(entity, group)
  config.redis_connection.setbit key_period(group, period), index, 1
end

#total_active(group: 'default', period: Time.now) ⇒ Object

Total active entities in the period, or now if no period, is provided.



27
28
29
# File 'lib/retained/tracker.rb', line 27

def total_active(group: 'default', period: Time.now)
  config.redis_connection.bitcount key_period(group, period)
end

#unique_active(group: 'default', start:, stop: Time.now) ⇒ Object

Returns the total number of unique active entities between the start and end periods (inclusive), or now if no stop period is provided.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/retained/tracker.rb', line 34

def unique_active(group: 'default', start:, stop: Time.now)
  keys = []
  start = period_start(group, start)
  while (start <= stop)
    keys << key_period(group, start)
    start += seconds_in_reporting_interval(config.group(group).reporting_interval)
  end
  return 0  if keys.length == 0

  temp_bitmap do |key|
    config.redis_connection.bitop 'OR', key, *keys
    config.redis_connection.bitcount key
  end
end