octocore-mongo 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +12 -0
  3. data/CONTRIBUTING +0 -0
  4. data/Gemfile +11 -0
  5. data/Gemfile.lock +150 -0
  6. data/LICENSE +202 -0
  7. data/MAINTAINERS +0 -0
  8. data/NOTICE +8 -0
  9. data/README.md +69 -0
  10. data/Rakefile +51 -0
  11. data/bin/fakestream-mongo +258 -0
  12. data/bin/octocore-admin-mongo +54 -0
  13. data/lib/octocore-mongo.rb +157 -0
  14. data/lib/octocore-mongo/baseline.rb +131 -0
  15. data/lib/octocore-mongo/callbacks.rb +117 -0
  16. data/lib/octocore-mongo/config.rb +39 -0
  17. data/lib/octocore-mongo/config/config.yml +1 -0
  18. data/lib/octocore-mongo/config/search/index/user.yml +42 -0
  19. data/lib/octocore-mongo/counter.rb +265 -0
  20. data/lib/octocore-mongo/counter/helpers.rb +168 -0
  21. data/lib/octocore-mongo/email.rb +63 -0
  22. data/lib/octocore-mongo/featureflag.rb +79 -0
  23. data/lib/octocore-mongo/helpers.rb +6 -0
  24. data/lib/octocore-mongo/helpers/api_consumer_helper.rb +51 -0
  25. data/lib/octocore-mongo/helpers/api_helper.rb +65 -0
  26. data/lib/octocore-mongo/helpers/api_logger.rb +14 -0
  27. data/lib/octocore-mongo/helpers/client_helper.rb +104 -0
  28. data/lib/octocore-mongo/helpers/kong_helper.rb +164 -0
  29. data/lib/octocore-mongo/helpers/sinatra_helper.rb +22 -0
  30. data/lib/octocore-mongo/kafka_bridge.rb +60 -0
  31. data/lib/octocore-mongo/kldivergence.rb +14 -0
  32. data/lib/octocore-mongo/mailer.rb +1 -0
  33. data/lib/octocore-mongo/mailer/subscriber_mailer.rb +32 -0
  34. data/lib/octocore-mongo/message_parser.rb +114 -0
  35. data/lib/octocore-mongo/models.rb +275 -0
  36. data/lib/octocore-mongo/models/contactus.rb +42 -0
  37. data/lib/octocore-mongo/models/enterprise.rb +75 -0
  38. data/lib/octocore-mongo/models/enterprise/adapter_details.rb +18 -0
  39. data/lib/octocore-mongo/models/enterprise/api_event.rb +14 -0
  40. data/lib/octocore-mongo/models/enterprise/api_hit.rb +20 -0
  41. data/lib/octocore-mongo/models/enterprise/api_key.rb +11 -0
  42. data/lib/octocore-mongo/models/enterprise/api_track.rb +13 -0
  43. data/lib/octocore-mongo/models/enterprise/app_init.rb +13 -0
  44. data/lib/octocore-mongo/models/enterprise/app_login.rb +12 -0
  45. data/lib/octocore-mongo/models/enterprise/app_logout.rb +12 -0
  46. data/lib/octocore-mongo/models/enterprise/authorization.rb +67 -0
  47. data/lib/octocore-mongo/models/enterprise/category.rb +14 -0
  48. data/lib/octocore-mongo/models/enterprise/category_baseline.rb +19 -0
  49. data/lib/octocore-mongo/models/enterprise/category_hit.rb +26 -0
  50. data/lib/octocore-mongo/models/enterprise/category_trend.rb +19 -0
  51. data/lib/octocore-mongo/models/enterprise/conversions.rb +69 -0
  52. data/lib/octocore-mongo/models/enterprise/ctr.rb +54 -0
  53. data/lib/octocore-mongo/models/enterprise/dimension_choice.rb +21 -0
  54. data/lib/octocore-mongo/models/enterprise/engagement_time.rb +43 -0
  55. data/lib/octocore-mongo/models/enterprise/funnel_data.rb +20 -0
  56. data/lib/octocore-mongo/models/enterprise/funnel_tracker.rb +19 -0
  57. data/lib/octocore-mongo/models/enterprise/funnels.rb +129 -0
  58. data/lib/octocore-mongo/models/enterprise/gcm.rb +21 -0
  59. data/lib/octocore-mongo/models/enterprise/newsfeed_hit.rb +52 -0
  60. data/lib/octocore-mongo/models/enterprise/notification_hit.rb +42 -0
  61. data/lib/octocore-mongo/models/enterprise/page.rb +15 -0
  62. data/lib/octocore-mongo/models/enterprise/page_view.rb +14 -0
  63. data/lib/octocore-mongo/models/enterprise/pageload_time.rb +43 -0
  64. data/lib/octocore-mongo/models/enterprise/product.rb +22 -0
  65. data/lib/octocore-mongo/models/enterprise/product_baseline.rb +20 -0
  66. data/lib/octocore-mongo/models/enterprise/product_hit.rb +26 -0
  67. data/lib/octocore-mongo/models/enterprise/product_page_view.rb +13 -0
  68. data/lib/octocore-mongo/models/enterprise/product_trend.rb +18 -0
  69. data/lib/octocore-mongo/models/enterprise/push_key.rb +15 -0
  70. data/lib/octocore-mongo/models/enterprise/rules.rb +45 -0
  71. data/lib/octocore-mongo/models/enterprise/segment.rb +65 -0
  72. data/lib/octocore-mongo/models/enterprise/segment_data.rb +22 -0
  73. data/lib/octocore-mongo/models/enterprise/tag.rb +14 -0
  74. data/lib/octocore-mongo/models/enterprise/tag_baseline.rb +19 -0
  75. data/lib/octocore-mongo/models/enterprise/tag_hit.rb +26 -0
  76. data/lib/octocore-mongo/models/enterprise/tag_trend.rb +19 -0
  77. data/lib/octocore-mongo/models/enterprise/template.rb +18 -0
  78. data/lib/octocore-mongo/models/plans.rb +17 -0
  79. data/lib/octocore-mongo/models/subscribe.rb +13 -0
  80. data/lib/octocore-mongo/models/user.rb +13 -0
  81. data/lib/octocore-mongo/models/user/push_token.rb +15 -0
  82. data/lib/octocore-mongo/models/user/user_browser_details.rb +16 -0
  83. data/lib/octocore-mongo/models/user/user_location_history.rb +15 -0
  84. data/lib/octocore-mongo/models/user/user_persona.rb +101 -0
  85. data/lib/octocore-mongo/models/user/user_phone_details.rb +17 -0
  86. data/lib/octocore-mongo/models/user/user_profile.rb +20 -0
  87. data/lib/octocore-mongo/models/user/user_timeline.rb +111 -0
  88. data/lib/octocore-mongo/record.rb +20 -0
  89. data/lib/octocore-mongo/schedeuleable.rb +20 -0
  90. data/lib/octocore-mongo/scheduler.rb +72 -0
  91. data/lib/octocore-mongo/search.rb +5 -0
  92. data/lib/octocore-mongo/search/client.rb +33 -0
  93. data/lib/octocore-mongo/search/indexer.rb +0 -0
  94. data/lib/octocore-mongo/search/searchable.rb +18 -0
  95. data/lib/octocore-mongo/search/setup.rb +71 -0
  96. data/lib/octocore-mongo/segment.rb +287 -0
  97. data/lib/octocore-mongo/stats.rb +33 -0
  98. data/lib/octocore-mongo/trendable.rb +88 -0
  99. data/lib/octocore-mongo/trends.rb +158 -0
  100. data/lib/octocore-mongo/utils.rb +90 -0
  101. data/lib/octocore-mongo/version.rb +4 -0
  102. data/octocore-mongo.gemspec +50 -0
  103. data/spec/lib/stats_spec.rb +20 -0
  104. data/spec/spec_helper.rb +103 -0
  105. metadata +545 -0
@@ -0,0 +1,131 @@
1
+ require 'octocore-mongo/counter/helpers'
2
+ require 'descriptive_statistics'
3
+
4
+ module Octo
5
+
6
+ # The baseline module. This module has methods that support
7
+ # a baseline structure.
8
+ module Baseline
9
+
10
+ include Octo::Counter::Helper
11
+
12
+ # Define the past duration to look for while calculating
13
+ # baseline. This must be in days
14
+ MAX_DURATION = 7
15
+
16
+ # Defines the column needed for a baseline
17
+ def baselineable
18
+ key :type, Integer
19
+ key :ts, Time
20
+ key :uid, String
21
+
22
+ key :val, Float
23
+
24
+ # Generate the aggregator methods
25
+ generate_aggregators { |ts, method|
26
+ type = method_names_type_counter(method)
27
+ aggregate type, ts
28
+ }
29
+ end
30
+
31
+ # Defines the class for whom the baseline is applicable
32
+ def baseline_for(klass)
33
+ @baseline_for = klass
34
+ end
35
+
36
+ # Finds baseline value of an object
37
+ # @param [Fixnum] baseline_type One of the valid Baseline Types defined
38
+ # @param [Object] obj The object for whom baseline value is to be found
39
+ # @param [Time] ts The timestamp at which baseline is to be found
40
+ def get_baseline_value(baseline_type, obj, ts = Time.now.ceil)
41
+ unless Octo::Counter.constants.include?baseline_type
42
+ raise ArgumentError, 'No such baseline defined'
43
+ end
44
+
45
+ args = {
46
+ ts: ts,
47
+ type: Octo::Counter.const_get(baseline_type),
48
+ uid: obj.unique_id,
49
+ enterprise_id: obj.enterprise.id
50
+ }
51
+ bl = get_cached(args)
52
+ if bl
53
+ bl.val
54
+ else
55
+ 0.01
56
+ end
57
+ end
58
+
59
+ # Does an aggregation of type for a timestamp
60
+ # @param [Fixnum] type The counter type for which aggregation
61
+ # has to be done
62
+ # @param [Time] ts The time at which aggregation should happen
63
+ def aggregate(type, ts)
64
+ Octo::Enterprise.each do |enterprise|
65
+ aggregate_baseline enterprise.id, type, ts
66
+ end
67
+ end
68
+
69
+
70
+ # Aggregates the baseline for a minute
71
+ def aggregate_baseline(enterprise_id, type, ts = Time.now.floor)
72
+ clazz = @baseline_for.constantize
73
+ _ts = ts
74
+ start_calc_time = (_ts.to_datetime - MAX_DURATION.day).to_time
75
+ last_n_days_interval = start_calc_time.ceil.to(_ts, 24.hour)
76
+ last_n_days_interval.each do |hist|
77
+ args = {
78
+ ts: hist,
79
+ type: type,
80
+ enterprise_id: enterprise_id
81
+ }
82
+ counters = @baseline_for.constantize.send(:where, args)
83
+ baseline = baseline_from_counters(counters)
84
+ store_baseline enterprise_id, type, hist, baseline
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Stores the baseline for an enterprise, and type
91
+ # @param [String] enterprise_id The enterprise ID of enterprise
92
+ # @param [Fixnum] type The Counter type as baseline type
93
+ # @param [Time] ts The time stamp of storage
94
+ # @param [Hash{String => Float}] baseline A hash representing baseline
95
+ def store_baseline(enterprise_id, type, ts, baseline)
96
+ return if baseline.nil? or baseline.empty?
97
+ baseline.each do |uid, val|
98
+ self.new({
99
+ enterprise_id: enterprise_id,
100
+ type: type,
101
+ ts: ts,
102
+ uid: uid,
103
+ val: val
104
+ }).save!
105
+ end
106
+ end
107
+
108
+ # Calculates the baseline from counters
109
+ def baseline_from_counters(counters)
110
+ baseline = {}
111
+ uid_groups = counters.group_by { |x| x.uid }
112
+ uid_groups.each do |uid, counts|
113
+ baseline[uid] = score_counts(counts)
114
+ end
115
+ baseline
116
+ end
117
+
118
+ # Calculates the baseline score from an array of scores
119
+ # @param [Array<Float>] counts The counts array
120
+ # @return [Float] The baseline score for counters
121
+ def score_counts(counts)
122
+ if counts.count > 0
123
+ _num = counts.map { |x| x.obp }
124
+ _num.percentile(90)
125
+ else
126
+ 0.01
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,117 @@
1
+ require 'hooks'
2
+ require 'active_support/concern'
3
+
4
+ module Octo
5
+
6
+ # Central Hooks Module
7
+ module OctoHooks
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+
13
+ define_hooks :after_app_init, :after_app_login, :after_app_logout,
14
+ :after_page_view, :after_productpage_view, :after_connect,
15
+ :after_update_profile, :after_update_push_token, :after_funnel_update
16
+
17
+ # # Define the after_app_init hook
18
+ # after_app_init do |args|
19
+ # update_counters args
20
+ # end
21
+
22
+ # # Define the after_app_login hook
23
+ # after_app_login do |args|
24
+ # update_counters args
25
+ # end
26
+
27
+ # # Define the after_app_logout hook
28
+ # after_app_logout do |args|
29
+ # update_counters args
30
+ # end
31
+
32
+ # # Define the after_page_view hook
33
+ # after_page_view do |args|
34
+ # add_session args
35
+ # update_counters args
36
+ # end
37
+
38
+ # # Define the after_productpage_view hook
39
+ # after_productpage_view do |args|
40
+ # add_session args
41
+ # update_counters args
42
+ # end
43
+
44
+
45
+ end
46
+
47
+ # Add all the post-hook-call methods here. Also, extend the module
48
+ # from here.
49
+ module ClassMethods
50
+
51
+ # Updates the counters of various types depending
52
+ # on the event.
53
+ # @param [Hash] opts The options hash
54
+ def update_counters(opts)
55
+ if opts.has_key?(:product)
56
+ Octo::ProductHit.increment_for(opts[:product])
57
+ end
58
+ if opts.has_key?(:categories)
59
+ opts[:categories].each do |cat|
60
+ Octo::CategoryHit.increment_for(cat)
61
+ end
62
+ end
63
+ if opts.has_key?(:tags)
64
+ opts[:tags].each do |tag|
65
+ Octo::TagHit.increment_for(tag)
66
+ end
67
+ end
68
+ if opts.has_key?(:event)
69
+ Octo::ApiHit.increment_for(opts[:event])
70
+ end
71
+ end
72
+ # Adds user session for page_view and
73
+ # product_page_view to redis.
74
+ # It self expires in n seconds from last hit
75
+ def add_session(opts)
76
+ if Octo.is_not_flagged?(Octo::Funnel)
77
+ if opts.has_key?(:type)
78
+ createRedisShadowKey(opts[:enterprise].id.to_s + '_' + opts[:user].id.to_s,
79
+ opts[:type].to_s,
80
+ Octo.get_config(:session_length))
81
+ end
82
+ end
83
+ end
84
+
85
+ # Method was created because when a redis key
86
+ # expires you can just catch the key name,
87
+ # and not its value. So what we do is
88
+ # create a shadow key for the given key name
89
+ # and make it expire in the given amt of
90
+ # time (seconds).
91
+ # This helps us in catching the event
92
+ # when the (shadow) key expires, after which we
93
+ # read the value of the main key and later
94
+ # delete the main key
95
+ # You can change it from rpush to lpush, or set
96
+ # whichever you want to use.
97
+ def createRedisShadowKey(keyname, value, duration)
98
+ MongoMapper::Document.redis.setex("shadow:" + keyname,duration,"")
99
+ MongoMapper::Document.redis.rpush(keyname, value)
100
+ end
101
+ end
102
+ end
103
+
104
+ # The class responsible for handling callbacks.
105
+ # You must never need to make changes here
106
+ class Callbacks
107
+ include Hooks
108
+ include Octo::OctoHooks
109
+
110
+ class << self
111
+ def callback_update(msg)
112
+ update_counters msg
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,39 @@
1
+ module Octo
2
+ module Config
3
+
4
+ def self.included(base)
5
+ base.include InstanceMethods
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def load_config(opts)
16
+ curr_config.merge!opts
17
+ end
18
+
19
+ def set_config(key, val)
20
+ curr_config[key] = val
21
+ end
22
+
23
+ def get_config(key, default = nil)
24
+ curr_config.fetch(key, default)
25
+ end
26
+
27
+ def curr_config
28
+ @config = Hash.new({}) unless @config
29
+ @config
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ module Octo
36
+ include Octo::Config
37
+ end
38
+
39
+ require 'octocore-mongo/featureflag'
@@ -0,0 +1 @@
1
+ name: octo_development
@@ -0,0 +1,42 @@
1
+ settings:
2
+ number_of_shards: 1
3
+ number_of_replicas: 0
4
+ mappings:
5
+ user:
6
+ properties:
7
+ enterpriseid:
8
+ type: string
9
+ userid:
10
+ type: integer
11
+ created:
12
+ type: date
13
+ updated:
14
+ type: date
15
+ last_login:
16
+ type: date
17
+ device_id:
18
+ type: string
19
+ manufacturer:
20
+ type: string
21
+ model:
22
+ type: string
23
+ city:
24
+ type: integer
25
+ country:
26
+ type: integer
27
+ state:
28
+ type: integer
29
+ os:
30
+ type: integer
31
+ browser:
32
+ type: integer
33
+ engagement:
34
+ type: integer
35
+ home_location:
36
+ type: geo_point
37
+ work_location:
38
+ type: geo_point
39
+ persona:
40
+ type: nested
41
+ time_slots:
42
+ type: nested
@@ -0,0 +1,265 @@
1
+ require 'octocore-mongo/counter/helpers'
2
+
3
+ module Octo
4
+ module Counter
5
+ include Octo::Counter::Helper
6
+
7
+ INDEX_KEY_PREFIX = :CounterIndex
8
+ COUNTER_KEY_PREFIX = :Counter
9
+
10
+ SEPARATOR = '_'
11
+
12
+ # Define the different types of counters here. As a design decision
13
+ # you MUST ALWAYS keep the counters that can be created from
14
+ # subcounters in multiples. So for instance, you can not create
15
+ # a counter of type TYPE_MINUTE_36 from a counter of type TYPE_MINUTE_15.
16
+ # If you have to create a counter of type TYPE_MINUTE_36, consider
17
+ # creating subcounters like TYPE_MINUTE_9 and TYPE_MINUTE_4. Derive
18
+ # TYPE_MINUTE_9 from TYPE_MINUTE_4 and TYPE_MINUTE_4 from TYPE_MINUTE_1
19
+ # NOTE:
20
+ #
21
+ # IT IS VERY VERY IMPORTANT TO KEEP THE VALUES OF THESE COUNTERS IN
22
+ # ASCENDING ORDER. because it helps to define a concept of "max_type"
23
+ TYPE_MINUTE = 0
24
+ TYPE_MINUTE_30 = 1
25
+ TYPE_HOUR = 2
26
+ TYPE_HOUR_3 = 3
27
+ TYPE_HOUR_6 = 4
28
+ TYPE_HOUR_12 = 5
29
+ TYPE_DAY = 6
30
+ TYPE_DAY_3 = 7
31
+ TYPE_DAY_6 = 8
32
+ TYPE_WEEK = 9
33
+
34
+
35
+ # Define the columns necessary for counter model
36
+ def countables
37
+ key :type, Integer
38
+ key :ts, Time
39
+ key :uid, String
40
+
41
+ key :count, Integer
42
+
43
+ generate_aggregators { |ts, method|
44
+ totype = method_names_type_counter(method)
45
+ fromtype = get_fromtype_for_totype(totype)
46
+ aggregate_and_create(fromtype, totype, ts)
47
+ }
48
+ end
49
+
50
+ # Increments the counter for a model.
51
+ # @param [Object] obj The model instance for whom counter would be
52
+ # incremented
53
+ def increment_for(obj)
54
+ # decide the time of event asap
55
+ ts = Time.now.ceil.to_i
56
+
57
+ if obj.class.ancestors.include?MongoMapper::Document
58
+ args = obj.key_attributes.collect { |k,v| v.to_s }
59
+ cache_key = generate_key(ts, obj.class.name, *args)
60
+
61
+ val = Cequel::Record.redis.get(cache_key)
62
+ if val.nil?
63
+ val = 1
64
+ else
65
+ val = val.to_i + 1
66
+ end
67
+
68
+ ttl = (time_window + 1) * 60
69
+
70
+ # Update a sharded counter
71
+ MongoMapper::Document.redis.setex(cache_key, ttl, val)
72
+
73
+ # Optionally, update the index
74
+ index_key = generate_index_key(ts, obj.class.name, *args)
75
+ index_present = MongoMapper::Document.redis.get(index_key).try(:to_i)
76
+ if index_present != 1
77
+ MongoMapper::Document.redis.setex(index_key, ttl, 1)
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ def aggregate_and_create(from_type, to_type, ts = Time.now.ceil)
84
+ duration = get_duration_for_counter_type(to_type, ts)
85
+ aggr = local_count(duration, from_type)
86
+ sum = local_counter_sum(aggr)
87
+ update_counters(aggr, sum, to_type, ts)
88
+ # Post Update Hooks should go here
89
+ call_completion_hook(to_type, ts)
90
+ end
91
+
92
+ def call_completion_hook(type, ts)
93
+ # If this counter type has a corresponding trends_klass
94
+ # it means that the trend for it must be calculated.
95
+ # So, schedule the trend calculation right after this
96
+ # is finished
97
+ if self.instance_variables.include?(:@trends_klass)
98
+ klass = self.instance_variable_get(:@trends_klass).constantize
99
+ # make sure it responds to the aggregation method
100
+ if klass.respond_to?(:aggregate_and_create)
101
+ klass.send(:aggregate_and_create, type, ts)
102
+ end
103
+ end
104
+ end
105
+
106
+ def update_counters(aggr, sum, type, ts)
107
+ aggr.each do |enterprise_id, uidCounters|
108
+ uidCounters.each do |uid, count|
109
+ counter = self.new({
110
+ enterprise_id: enterprise_id,
111
+ uid: uid,
112
+ count: count,
113
+ type: type,
114
+ ts: ts
115
+ })
116
+ if counter.respond_to?(:obp)
117
+ counter.obp = count.to_f/sum[enterprise_id]
118
+ end
119
+ counter.save!
120
+ end
121
+ end
122
+ end
123
+
124
+ # Does the counting from DB. Unlike the other counter that uses Redis. Hence
125
+ # the name local_count
126
+ # @param [Time] duration A time/time range object
127
+ # @param [Fixnum] type The type of counter to look for
128
+ def local_count(duration, type)
129
+ aggr = {}
130
+ Octo::Enterprise.each do |enterprise|
131
+ args = {
132
+ enterprise_id: enterprise.id,
133
+ ts: duration,
134
+ type: type
135
+ }
136
+ aggr[enterprise.id.to_s] = {} unless aggr.has_key?(enterprise.id.to_s)
137
+ results = where(args)
138
+ results_group = results.group_by { |x| x.uid }
139
+ results_group.each do |uid, counters|
140
+ _sum = counters.inject(0) do |sum, counter|
141
+ sum + counter.count
142
+ end
143
+ aggr[enterprise.id.to_s][uid] = _sum
144
+ end
145
+ end
146
+ aggr
147
+ end
148
+
149
+ def local_counter_sum(aggr)
150
+ sum = {}
151
+ aggr.each do |enterprise_id, uidCounters|
152
+ _sum = uidCounters.values.inject(0) do |s, count|
153
+ s + count
154
+ end
155
+ sum[enterprise_id] = _sum
156
+ end
157
+ sum
158
+ end
159
+
160
+ # Aggregates all the counters available. Aggregation of only time specific
161
+ # events can be done by passing the `ts` parameter.
162
+ # @param [Time] ts The time at which aggregation has to be done.
163
+ # @return [Hash{Fixnum => Hash{ Obj => Fixnum }}] The counts of each object
164
+ def aggregate(ts = Time.now.floor)
165
+ ts = ts.to_i
166
+ aggr = {}
167
+ # Find all counters from the index
168
+ index_key = generate_index_key(ts, '*')
169
+ counters = MongoMapper::Document.redis.keys(index_key)
170
+ counters.each do |cnt|
171
+ _tmp = cnt.split(SEPARATOR)
172
+ _ts = _tmp[2].to_i
173
+ aggr[_ts] = {} unless aggr.has_key?(_ts)
174
+
175
+ clazz = _tmp[3]
176
+ _clazz = clazz.constantize
177
+
178
+ _attrs = _tmp[4.._tmp.length]
179
+
180
+ args = {}
181
+ _clazz.key_column_names.each_with_index do |k, i|
182
+ args[k] = _attrs[i]
183
+ end
184
+
185
+ obj = _clazz.public_send(:get_cached, args)
186
+
187
+ # construct the keys for all counters matching this patter
188
+ _attrs << '*'
189
+ counters_search_key = generate_key_prefix(_ts, clazz, _attrs)
190
+ counter_keys = MongoMapper::Document.redis.keys(counters_search_key)
191
+ counter_keys.each do |c_key|
192
+ val = MongoMapper::Document.redis.get(c_key)
193
+ if val
194
+ aggr[_ts][obj] = aggr[_ts].fetch(obj, 0) + val.to_i
195
+ else
196
+ aggr[_ts][obj] = aggr[_ts].fetch(obj, 0) + 1
197
+ end
198
+ end
199
+ end
200
+ aggr
201
+
202
+ end
203
+
204
+ # Aggregates and attempts to store it into the database. This would only
205
+ # work if the class that extends Octo::Counter includes from
206
+ # Cequel::Record
207
+ def aggregate!(ts = Time.now.floor)
208
+ unless self.ancestors.include?MongoMapper::Document
209
+ raise NoMethodError, "aggregate! not defined for this counter"
210
+ end
211
+
212
+ aggr = aggregate(ts)
213
+ aggr.each do |_ts, counterVals|
214
+ counterVals.each do |obj, count|
215
+ args = gen_args_for_instance obj, count, _ts, TYPE_MINUTE
216
+ counter = self.new args
217
+ counter.save!
218
+ end
219
+ end
220
+ call_completion_hook(TYPE_MINUTE, ts)
221
+ end
222
+
223
+ private
224
+
225
+ def gen_args_for_instance(obj, count, ts, type)
226
+ {
227
+ enterprise: obj.enterprise,
228
+ uid: obj.unique_id,
229
+ count: count,
230
+ type: type,
231
+ ts: ts
232
+ }
233
+ end
234
+
235
+ def generate_key(ts, *args)
236
+ args << rand(counters)
237
+ [COUNTER_KEY_PREFIX, ts, *args].join(SEPARATOR)
238
+ end
239
+
240
+ def generate_key_prefix(ts, *args)
241
+ [COUNTER_KEY_PREFIX, ts, *args].join(SEPARATOR)
242
+ end
243
+
244
+ def generate_index_key(ts, *args)
245
+ [INDEX_KEY_PREFIX, self.name, ts, *args].join(SEPARATOR)
246
+ end
247
+
248
+ def counters
249
+ if self.constants.include?(:COUNTERS)
250
+ self.const_get(:COUNTERS)
251
+ else
252
+ 30
253
+ end
254
+ end
255
+
256
+ def time_window
257
+ if self.constants.include?(:TIME_WINDOW)
258
+ self.const_get(:TIME_WINDOW)
259
+ else
260
+ 1
261
+ end
262
+ end
263
+
264
+ end
265
+ end