Class: Retained::Tracker
- Inherits:
-
Object
- Object
- Retained::Tracker
- Defined in:
- lib/retained/tracker.rb
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
Instance Method Summary collapse
-
#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.
- #configure {|config| ... } ⇒ Object
-
#entity_index(entity, group) ⇒ Object
Returns the index (offset) of the entity within the group.
-
#groups ⇒ Object
Returns an array of all groups.
-
#initialize(config = Configuration.new) ⇒ Tracker
constructor
A new instance of Tracker.
-
#key_period(group, period) ⇒ Object
Returns the key for the group at the period.
-
#retain(entity, group: 'default', period: Time.now) ⇒ Object
Tracks the entity as active at the period, or now if no period is provided.
-
#retention(group: 'default', initial_start:, initial_stop:, final_start:, final_stop:) ⇒ Object
Returns the percent retained (as a float) retained between an initial and a final period range.
-
#total_active(group: 'default', period: Time.now) ⇒ Object
Total active entities in the period, or now if no period, is provided.
-
#total_retained(group: 'default', initial_start:, initial_stop:, final_start:, final_stop:) ⇒ Object
Returns the total number of unique active entities retained between an initial and a final period range.
-
#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.
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
#config ⇒ Object
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.
87 88 89 90 91 92 93 94 95 |
# File 'lib/retained/tracker.rb', line 87 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
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
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/retained/tracker.rb', line 106 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 |
#groups ⇒ Object
Returns an array of all groups
98 99 100 |
# File 'lib/retained/tracker.rb', line 98 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.
122 123 124 |
# File 'lib/retained/tracker.rb', line 122 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 |
#retention(group: 'default', initial_start:, initial_stop:, final_start:, final_stop:) ⇒ Object
Returns the percent retained (as a float) retained between an initial and a final period range. Each period range consists of a start period and an end period (inclusive). The final period range’s starting period must be after the inital period range’s ending period. If there are no entities in the initial period, Float::NAN is returned.
74 75 76 77 78 79 80 81 82 |
# File 'lib/retained/tracker.rb', line 74 def retention(group: 'default', initial_start:, initial_stop:, final_start: , final_stop:) initial_count = unique_active(group: group, start: initial_start, stop: initial_stop) retained = total_retained(group: group, initial_start: initial_start, initial_stop: initial_stop, final_start: final_start, final_stop: final_stop) return retained / initial_count.to_f 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 |
#total_retained(group: 'default', initial_start:, initial_stop:, final_start:, final_stop:) ⇒ Object
Returns the total number of unique active entities retained between an initial and a final period range. Each period range consists of a start period and an end period (inclusive). The final period range’s starting period must be after the inital period range’s ending period.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/retained/tracker.rb', line 49 def total_retained(group: 'default', initial_start:, initial_stop:, final_start: , final_stop:) #raise ArgumentError, "final_start must be after initial_stop" if final_start <= initial_stop initial_keys = period_range_keys(group, initial_start, initial_stop) final_keys = period_range_keys(group, final_start, final_stop) return 0 if initial_keys == 0 || final_keys == 0 temp_bitmap do |key| temp_bitmap do |initial_key| config.redis_connection.bitop 'OR', initial_key, *initial_keys temp_bitmap do |final_key| config.redis_connection.bitop 'OR', final_key, *final_keys config.redis_connection.bitop 'AND', key, initial_key, final_key config.redis_connection.bitcount key end end end 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 |
# File 'lib/retained/tracker.rb', line 34 def unique_active(group: 'default', start:, stop: Time.now) keys = period_range_keys(group, start, stop) return 0 if keys.length == 0 temp_bitmap do |key| config.redis_connection.bitop 'OR', key, *keys config.redis_connection.bitcount key end end |