splitclient-rb 3.1.0.pre.rc7 → 3.1.0.pre.rc8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 466e08e89ced1060fc54b61943291949d592de14
4
- data.tar.gz: d3dc48c5aadb358a573cec5d7774d173edcf0673
3
+ metadata.gz: b1309484de3281c553d149dedc90df953df886f6
4
+ data.tar.gz: 3f4cc3a188da4d9c3167465b4d3e9886bc92320f
5
5
  SHA512:
6
- metadata.gz: 929793f1ddf099968d42c1ff6397b127cf5e11791e66f702b44304374baed4b16600e5a2185941b71e8e4416999175f6530cfdd7d85dc33523dc17331c4ddd55
7
- data.tar.gz: e992dcbac1d607b045da85132bdb913e72cd8b4d4cf98e12a002f97d3a2275488a96cb544b2497451515ffe1d63414b229b4810e1c38ad09ed2370fe92ce448a
6
+ metadata.gz: 9d28027d4c955132b5064c331fb2d7a50104a8e04b5d7abc86bd102731324050572b021b02a4d5e384d2101e6c37351f158c50660706d26125d8c8619b45cb5c
7
+ data.tar.gz: fe822d1c1f3395e0f8c48fed3dbee881dd8d7a9b15964bc5340d300c0cf2494fdba7406b6fe79c8f409955cc8fc02793374e26026cdd3ee34795c8aecb3a4f42
data/README.md CHANGED
@@ -104,7 +104,7 @@ The following values can be customized:
104
104
 
105
105
  **impressions_refresh_rate** : The SDK sends information on who got what treatment at what time back to Split servers to power analytics. This parameter controls how often this data is sent to Split servers in seconds
106
106
 
107
- **impressions_queue_size** : The size of the impressions queue. -1 to disable impressions.
107
+ **impressions_queue_size** : The size of the impressions queue in case of `cache_adapter == :memory` and the size impressions batch to be fetched from Redis in case of `cache_adapter == :redis`. Use `-1` to disable impressions.
108
108
 
109
109
  *default value* = `60`
110
110
 
@@ -130,7 +130,7 @@ The following values can be customized:
130
130
 
131
131
  #### Cache adapter
132
132
 
133
- The SDK needs some container to store fetched data, i.e. splits/segments. By default it will store everything in the application's memory, but you can also use Redis.
133
+ The SDK needs some container to store data, i.e. splits/segments/impressions. By default it will store everything in the application's memory, but you can also use Redis.
134
134
 
135
135
  To use Redis, you have to include `redis-rb` in your app's Gemfile.
136
136
 
@@ -163,7 +163,7 @@ options = {
163
163
  logger: Logger.new('logfile.log'),
164
164
  block_until_ready: 5,
165
165
  cache_adapter: :redis,
166
- mode: :consumer,
166
+ mode: :standalone,
167
167
  redis_url: 'redis://127.0.0.1:6379/0'
168
168
  }
169
169
  begin
@@ -172,6 +172,10 @@ rescue SplitIoClient::SDKBlockerTimeoutExpiredException
172
172
  # Some arbitrary actions
173
173
  end
174
174
  ```
175
+
176
+ #### IMPORTANT
177
+ For now, SDK does not support both `producer` mode and `block_until_ready`. You must either run SDK in `standalone` mode, or do not use `block_until_ready` option.
178
+
175
179
  This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic.
176
180
 
177
181
  ### Execution
@@ -230,28 +234,28 @@ And you should get something like this:
230
234
 
231
235
  ```ruby
232
236
  [
233
- {
237
+ {
234
238
  name: 'some_feature',
235
239
  traffic_type_name: nil,
236
240
  killed: false,
237
241
  treatments: nil,
238
242
  change_number: 1469134003507
239
243
  },
240
- {
244
+ {
241
245
  name: 'another_feature',
242
246
  traffic_type_name: nil,
243
247
  killed: false,
244
248
  treatments: nil,
245
249
  change_number: 1469134003414
246
250
  },
247
- {
251
+ {
248
252
  name: 'even_more_features',
249
253
  traffic_type_name: nil,
250
254
  killed: false,
251
255
  treatments: nil,
252
256
  change_number: 1469133991063
253
257
  },
254
- {
258
+ {
255
259
  name: 'yet_another_feature',
256
260
  traffic_type_name: nil,
257
261
  killed: false,
@@ -297,7 +301,7 @@ SDK can be ran in `producer` mode both in the scope of the application (e.g. as
297
301
  bundle binstubs splitclient-rb
298
302
  ```
299
303
 
300
- - Run the executable provided by the SDK:
304
+ - Run the executable provided by the SDK:
301
305
  ```ruby
302
306
  bundle exec bin/splitio -c ~/path/to/config/file.yml
303
307
  ```
@@ -3,98 +3,9 @@ require 'concurrent'
3
3
  module SplitIoClient
4
4
  module Cache
5
5
  module Adapters
6
- class MemoryAdapter
7
- def initialize
8
- @map = Concurrent::Map.new
9
- end
10
-
11
- # Map
12
- def initialize_map(key)
13
- @map[key] = Concurrent::Map.new
14
- end
15
-
16
- def add_to_map(key, field, value)
17
- @map[key].put(field, value)
18
- end
19
-
20
- def find_in_map(key, field)
21
- return nil if @map[key].nil?
22
-
23
- @map[key].get(field)
24
- end
25
-
26
- def delete_from_map(key, fields)
27
- if fields.is_a? Array
28
- fields.each { |field| @map[key].delete(field) }
29
- else
30
- @map[key].delete(field)
31
- end
32
- end
33
-
34
- def in_map?(key, field)
35
- return false if @map[key].nil?
36
-
37
- @map[key].key?(field)
38
- end
39
-
40
- def map_keys(key)
41
- @map[key].keys
42
- end
43
-
44
- def get_map(key)
45
- @map[key]
46
- end
47
-
48
- # String
49
- def string(key)
50
- @map[key]
51
- end
52
-
53
- def set_string(key, str)
54
- @map[key] = str
55
- end
56
-
57
- def find_strings_by_prefix(prefix)
58
- @map.keys.select { |str| str.start_with? prefix }
59
- end
60
-
61
- def multiple_strings(keys)
62
- keys.each_with_object({}) do |key, memo|
63
- memo[key] = string(key)
64
- end
65
- end
66
-
67
- # Bool
68
- def set_bool(key, val)
69
- @map[key] = val
70
- end
71
-
72
- def bool(key)
73
- @map[key]
74
- end
75
-
76
- # Set
77
- alias_method :initialize_set, :initialize_map
78
- alias_method :get_set, :map_keys
79
- alias_method :delete_from_set, :delete_from_map
80
- alias_method :in_set?, :in_map?
81
-
82
- def add_to_set(key, values)
83
- if values.is_a? Array
84
- values.each { |value| add_to_map(key, value, 1) }
85
- else
86
- add_to_map(key, values, 1)
87
- end
88
- end
89
-
90
- # General
91
- def exists?(key)
92
- !@map[key].nil?
93
- end
94
-
95
- def delete(key)
96
- @map.delete(key)
97
- end
6
+ # Memory adapter can have different implementations, this class is used as a delegator to
7
+ # this implementations
8
+ class MemoryAdapter < SimpleDelegator
98
9
  end
99
10
  end
100
11
  end
@@ -0,0 +1,121 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Adapters
6
+ module MemoryAdapters
7
+ # Memory adapter implementation, which stores everything inside thread-safe Map
8
+ class MapAdapter
9
+ def initialize(_ = nil)
10
+ @map = Concurrent::Map.new
11
+ end
12
+
13
+ # Map
14
+ def initialize_map(key)
15
+ @map[key] = Concurrent::Map.new
16
+ end
17
+
18
+ def add_to_map(key, field, value)
19
+ initialize_map(key) unless @map[key]
20
+
21
+ @map[key].put(field, value)
22
+ end
23
+
24
+ def find_in_map(key, field)
25
+ return nil if @map[key].nil?
26
+
27
+ @map[key].get(field)
28
+ end
29
+
30
+ def delete_from_map(key, fields)
31
+ if fields.is_a? Array
32
+ fields.each { |field| @map[key].delete(field) }
33
+ else
34
+ @map[key].delete(field)
35
+ end
36
+ end
37
+
38
+ def in_map?(key, field)
39
+ return false if @map[key].nil?
40
+
41
+ @map[key].key?(field)
42
+ end
43
+
44
+ def map_keys(key)
45
+ @map[key].keys
46
+ end
47
+
48
+ def get_map(key)
49
+ @map[key]
50
+ end
51
+
52
+ # String
53
+ def string(key)
54
+ @map[key]
55
+ end
56
+
57
+ def set_string(key, str)
58
+ @map[key] = str
59
+ end
60
+
61
+ def find_strings_by_prefix(prefix)
62
+ @map.keys.select { |str| str.start_with? prefix }
63
+ end
64
+
65
+ def multiple_strings(keys)
66
+ keys.each_with_object({}) do |key, memo|
67
+ memo[key] = string(key)
68
+ end
69
+ end
70
+
71
+ # Bool
72
+ def set_bool(key, val)
73
+ @map[key] = val
74
+ end
75
+
76
+ def bool(key)
77
+ @map[key]
78
+ end
79
+
80
+ # Set
81
+ alias_method :initialize_set, :initialize_map
82
+ alias_method :get_set, :map_keys
83
+ alias_method :delete_from_set, :delete_from_map
84
+ alias_method :in_set?, :in_map?
85
+ alias_method :find_sets_by_prefix, :find_strings_by_prefix
86
+
87
+ def add_to_set(key, values)
88
+ if values.is_a? Array
89
+ values.each { |value| add_to_map(key, value, 1) }
90
+ else
91
+ add_to_map(key, values, 1)
92
+ end
93
+ end
94
+
95
+ def get_all_from_set(key)
96
+ @map[key].keys
97
+ end
98
+
99
+ def union_sets(set_keys)
100
+ set_keys.each_with_object([]) do |key, memo|
101
+ memo << get_set(key)
102
+ end.flatten
103
+ end
104
+
105
+ # General
106
+ def exists?(key)
107
+ !@map[key].nil?
108
+ end
109
+
110
+ def delete(key)
111
+ if key.is_a? Array
112
+ key.each { |k| @map.delete(k) }
113
+ else
114
+ @map.delete(key)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,38 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Adapters
4
+ module MemoryAdapters
5
+ # Memory adapter implementation, which stores everything inside sized queue
6
+ class SizedQueueAdapter
7
+ def initialize(size)
8
+ @size = size
9
+ @queue = SizedQueue.new(queue_size)
10
+ end
11
+
12
+ # Adds data to queue in non-blocking mode
13
+ def add_to_queue(data)
14
+ # IMPORTANT: this requires Ruby >= 2.2, consider changing implementation
15
+ @queue.push(data, true)
16
+ end
17
+
18
+ # Get all items from the queue
19
+ def clear
20
+ items = []
21
+
22
+ loop { items << @queue.pop(true) }
23
+ rescue ThreadError
24
+ # Last queue item reached
25
+ items
26
+ end
27
+
28
+ private
29
+
30
+ # Return 1 to prevent an exception
31
+ def queue_size
32
+ @size <= 0 ? 1 : @size
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -4,6 +4,7 @@ require 'json'
4
4
  module SplitIoClient
5
5
  module Cache
6
6
  module Adapters
7
+ # Redis adapter used to provide interface to Redis
7
8
  class RedisAdapter
8
9
  def initialize(redis_url)
9
10
  connection = redis_url.is_a?(Hash) ? redis_url : { url: redis_url }
@@ -68,6 +69,7 @@ module SplitIoClient
68
69
 
69
70
  # Set
70
71
  alias_method :initialize_set, :initialize_map
72
+ alias_method :find_sets_by_prefix, :find_strings_by_prefix
71
73
 
72
74
  def add_to_set(key, val)
73
75
  @redis.sadd(key, val)
@@ -85,12 +87,28 @@ module SplitIoClient
85
87
  @redis.sismember(key, val)
86
88
  end
87
89
 
90
+ def get_all_from_set(key)
91
+ @redis.smembers(key)
92
+ end
93
+
94
+ def union_sets(set_keys)
95
+ return [] if set_keys == []
96
+
97
+ @redis.sunion(set_keys)
98
+ end
99
+
100
+ def random_set_elements(key, count)
101
+ @redis.srandmember(key, count)
102
+ end
103
+
88
104
  # General
89
105
  def exists?(key)
90
106
  @redis.exists(key)
91
107
  end
92
108
 
93
109
  def delete(key)
110
+ return nil if key == []
111
+
94
112
  @redis.del(key)
95
113
  end
96
114
  end
@@ -0,0 +1,35 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Impressions
5
+ class MemoryRepository
6
+ def initialize(adapter, config)
7
+ @adapter = adapter
8
+ @config = config
9
+ end
10
+
11
+ # Store impression data in the selected adapter
12
+ def add(split_name, data)
13
+ @adapter.add_to_queue(feature: split_name, impressions: data)
14
+ rescue ThreadError # queue is full
15
+ if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
16
+ @config.logger.warn("Dropping impressions. Current size is #{@config.impressions_queue_size}. " \
17
+ "Consider increasing impressions_queue_size")
18
+ end
19
+ end
20
+
21
+ # Get everything from the queue and leave it empty
22
+ def clear
23
+ @adapter.clear
24
+ end
25
+
26
+ private
27
+
28
+ def random_sampler
29
+ @random_sampler ||= Random.new
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Impressions
5
+ class RedisRepository < Repository
6
+ def initialize(adapter, config)
7
+ @adapter = adapter
8
+ @config = config
9
+ end
10
+
11
+ # Store impression data in Redis
12
+ def add(split_name, data)
13
+ @adapter.add_to_set(
14
+ namespace_key("impressions.#{split_name}"), data.merge(split_name: split_name).to_json
15
+ )
16
+ end
17
+
18
+ # Get random impressions from redis in batches of size @config.impressions_queue_size,
19
+ # delete fetched impressions afterwards
20
+ def clear
21
+ impressions = impression_keys.each_with_object([]) do |key, memo|
22
+ @adapter.random_set_elements(key, @config.impressions_queue_size).each do |impression|
23
+ parsed_impression = JSON.parse(impression)
24
+
25
+ memo << {
26
+ feature: parsed_impression['split_name'],
27
+ impressions: parsed_impression.reject { |k, _| k == 'split_name' }
28
+ }
29
+
30
+ @adapter.delete_from_set(key, impression)
31
+ end
32
+ end
33
+
34
+ impressions
35
+ end
36
+
37
+ private
38
+
39
+ # Get all sets by prefix
40
+ def impression_keys
41
+ @adapter.find_sets_by_prefix(namespace_key('impressions.'))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ # Repository which forwards impressions interface to the selected adapter
5
+ class ImpressionsRepository < Repository
6
+ extend Forwardable
7
+ def_delegators :@adapter, :add, :clear, :empty?
8
+
9
+ def initialize(adapter, config)
10
+ @adapter = case adapter.class.to_s
11
+ when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
12
+ Repositories::Impressions::MemoryRepository.new(adapter, config)
13
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
14
+ Repositories::Impressions::RedisRepository.new(adapter, config)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -10,6 +10,7 @@ module SplitIoClient
10
10
  @adapter.set_bool(namespace_key('ready'), false)
11
11
  end
12
12
 
13
+ # Receives segment data, adds and removes segements from the store
13
14
  def add_to_segment(segment)
14
15
  name = segment[:name]
15
16
 
@@ -13,10 +13,6 @@ module SplitIoClient
13
13
  # also, uses safe threads to execute fetches and post give the time execution values from the config
14
14
  #
15
15
  class SplitAdapter < NoMethodError
16
- #
17
- # handler for impressions
18
- attr_reader :impressions
19
-
20
16
  #
21
17
  # handler for metrics
22
18
  attr_reader :metrics
@@ -31,7 +27,7 @@ module SplitIoClient
31
27
 
32
28
  attr_reader :impressions_producer
33
29
 
34
- attr_reader :splits_repository, :segments_repository
30
+ attr_reader :splits_repository, :segments_repository, :impressions_repository
35
31
 
36
32
  #
37
33
  # Creates a new split api adapter instance that consumes split api endpoints
@@ -39,14 +35,14 @@ module SplitIoClient
39
35
  # @param api_key [String] the API key for your split account
40
36
  #
41
37
  # @return [SplitIoClient] split.io client instance
42
- def initialize(api_key, config, splits_repository, segments_repository, sdk_blocker)
38
+ def initialize(api_key, config, splits_repository, segments_repository, impressions_repository, sdk_blocker)
43
39
  @api_key = api_key
44
40
  @config = config
45
- @impressions = Impressions.new(@config)
46
41
  @metrics = Metrics.new(100)
47
42
 
48
43
  @splits_repository = splits_repository
49
44
  @segments_repository = segments_repository
45
+ @impressions_repository = impressions_repository
50
46
 
51
47
  @sdk_blocker = sdk_blocker
52
48
 
@@ -67,10 +63,10 @@ module SplitIoClient
67
63
  impressions_sender
68
64
  when :consumer
69
65
  metrics_sender
70
- impressions_sender
71
66
  when :producer
72
67
  split_store
73
68
  segment_store
69
+ impressions_sender
74
70
 
75
71
  sleep unless ENV['SPLITCLIENT_ENV'] == 'test'
76
72
  end
@@ -107,7 +103,13 @@ module SplitIoClient
107
103
  req.body = param.to_json
108
104
  req.options.timeout = @config.read_timeout
109
105
  req.options.open_timeout = @config.connection_timeout
110
- @config.logger.debug("POST #{@config.events_uri + path} #{req.body}") if @config.debug_enabled
106
+
107
+ if @config.transport_debug_enabled
108
+ @config.logger.debug("POST #{@config.events_uri + path} #{req.body}")
109
+ elsif @config.debug_enabled
110
+ @config.logger.debug("POST #{@config.events_uri + path}")
111
+ end
112
+
111
113
  end
112
114
  end
113
115
 
@@ -145,6 +147,14 @@ module SplitIoClient
145
147
  end
146
148
 
147
149
  def impressions_sender
150
+ # Disable impressions if @config.impressions_queue_size == -1
151
+ if @config.impressions_queue_size < 0
152
+ @config.logger.info("Disabling impressions service by config.")
153
+ return
154
+ end
155
+
156
+ @config.logger.info("Starting impressions service...")
157
+
148
158
  Thread.new do
149
159
  loop do
150
160
  begin
@@ -157,6 +167,7 @@ module SplitIoClient
157
167
  end
158
168
  end
159
169
  end
170
+ @config.logger.info("Started impressions service")
160
171
  end
161
172
 
162
173
  #
@@ -165,46 +176,69 @@ module SplitIoClient
165
176
  #
166
177
  # @return [void]
167
178
  def post_impressions
168
- if @impressions.queue.empty?
169
- @config.logger.debug('No impressions to report.') if @config.debug_enabled
179
+ impressions = impressions_array
180
+
181
+ if impressions.empty?
182
+ @config.logger.debug('No impressions to report') if @config.debug_enabled
183
+ return
184
+ end
185
+
186
+ res = post_api('/testImpressions/bulk', impressions)
187
+ if res.status / 100 != 2
188
+ @config.logger.error("Unexpected status code while posting impressions: #{res.status}")
170
189
  else
171
- popped_impressions = @impressions.clear
172
- test_impression_array = []
173
- popped_impressions.each do |i|
174
- filtered = []
175
- keys_treatments_seen = []
176
-
177
- impressions = i[:impressions]
178
- impressions.each do |imp|
179
- if keys_treatments_seen.include?("#{imp.key}:#{imp.treatment}")
180
- next
181
- end
182
- keys_treatments_seen << "#{imp.key}:#{imp.treatment}"
183
- filtered << imp
184
- end
190
+ @config.logger.debug("Impressions reported: #{calculate_tot_impressions(impressions)}") if @config.debug_enabled
191
+ end
192
+ end
185
193
 
186
- if filtered.empty?
187
- @config.logger.debug('No impressions to report post filtering.') if @config.debug_enabled
188
- else
189
- test_impression = {}
190
- key_impressions = []
194
+ def calculate_tot_impressions(impressions = nil)
195
+ tot = 0
196
+ return tot if impressions.nil?
197
+ impressions.each do |test_impression|
198
+ tot += test_impression[:keyImpressions].length
199
+ end
200
+ tot
201
+ end
191
202
 
192
- filtered.each do |f|
193
- key_impressions << {keyName: f.key, treatment: f.treatment, time: f.time.to_i}
194
- end
203
+ # REFACTOR
204
+ def impressions_array(impressions = nil)
205
+ impressions_data = impressions || @impressions_repository
206
+ popped_impressions = impressions_data.clear
207
+ test_impression_array = []
208
+ filtered_impressions = []
209
+ keys_treatments_seen = []
195
210
 
196
- test_impression = {testName: i[:feature], keyImpressions: key_impressions}
197
- test_impression_array << test_impression
198
- end
211
+ if !popped_impressions.empty?
212
+ popped_impressions.each do |item|
213
+ item_hash = "#{item[:impressions]['key_name']}:#{item[:impressions]['treatment']}"
214
+
215
+ next if keys_treatments_seen.include?(item_hash)
216
+
217
+ keys_treatments_seen << item_hash
218
+ filtered_impressions << item
199
219
  end
200
220
 
201
- res = post_api('/testImpressions/bulk', test_impression_array)
202
- if res.status / 100 != 2
203
- @config.logger.error("Unexpected status code while posting impressions: #{res.status}")
204
- else
205
- @config.logger.debug("Impressions reported: #{test_impression_array}") if @config.debug_enabled
221
+ return [] unless filtered_impressions
222
+
223
+ features = filtered_impressions.map { |i| i[:feature] }.uniq
224
+ test_impression_array = features.each_with_object([]) do |feature, memo|
225
+ current_impressions = filtered_impressions.select { |i| i[:feature] == feature }
226
+ current_impressions.map! do |i|
227
+ {
228
+ keyName: i[:impressions]['key_name'],
229
+ treatment: i[:impressions]['treatment'],
230
+ time: i[:impressions]['time']
231
+ }
232
+ end
233
+
234
+ memo << {
235
+ testName: feature,
236
+ keyImpressions: current_impressions
237
+ }
206
238
  end
207
239
  end
240
+
241
+ test_impression_array
208
242
  end
209
243
 
210
244
  #
@@ -224,7 +258,7 @@ module SplitIoClient
224
258
  if res.status / 100 != 2
225
259
  @config.logger.error("Unexpected status code while posting time metrics: #{res.status}")
226
260
  else
227
- @config.logger.debug("Metric time reported: #{metrics_time}") if @config.debug_enabled
261
+ @config.logger.debug("Metric time reported: #{metrics_time.size()}") if @config.debug_enabled
228
262
  end
229
263
  end
230
264
  end
@@ -240,7 +274,7 @@ module SplitIoClient
240
274
  if res.status / 100 != 2
241
275
  @config.logger.error("Unexpected status code while posting count metrics: #{res.status}")
242
276
  else
243
- @config.logger.debug("Metric counts reported: #{metrics_count}") if @config.debug_enabled
277
+ @config.logger.debug("Metric counts reported: #{metrics_count.size()}") if @config.debug_enabled
244
278
  end
245
279
  end
246
280
  end
@@ -256,7 +290,7 @@ module SplitIoClient
256
290
  if res.status / 100 != 2
257
291
  @config.logger.error("Unexpected status code while posting gauge metrics: #{res.status}")
258
292
  else
259
- @config.logger.debug("Metric gauge reported: #{metrics_gauge}") if @config.debug_enabled
293
+ @config.logger.debug("Metric gauge reported: #{metrics_gauge.size()}") if @config.debug_enabled
260
294
  end
261
295
  end
262
296
  end
@@ -5,11 +5,16 @@ require 'splitclient-rb/localhost_split_factory_builder'
5
5
  require 'splitclient-rb/localhost_split_factory'
6
6
  require 'splitclient-rb/split_config'
7
7
  require 'exceptions/sdk_blocker_timeout_expired_exception'
8
+ require 'cache/adapters/memory_adapters/map_adapter'
9
+ require 'cache/adapters/memory_adapters/sized_queue_adapter'
8
10
  require 'cache/adapters/memory_adapter'
9
11
  require 'cache/adapters/redis_adapter'
10
12
  require 'cache/repositories/repository'
11
13
  require 'cache/repositories/segments_repository'
12
14
  require 'cache/repositories/splits_repository'
15
+ require 'cache/repositories/impressions_repository'
16
+ require 'cache/repositories/impressions/memory_repository'
17
+ require 'cache/repositories/impressions/redis_repository'
13
18
  require 'cache/stores/sdk_blocker'
14
19
  require 'cache/stores/segment_store'
15
20
  require 'cache/stores/split_store'
@@ -32,7 +37,6 @@ require 'engine/matchers/greater_than_or_equal_to_matcher'
32
37
  require 'engine/matchers/less_than_or_equal_to_matcher'
33
38
  require 'engine/matchers/between_matcher'
34
39
  require 'engine/evaluator/splitter'
35
- require 'engine/impressions/impressions'
36
40
  require 'engine/metrics/metrics'
37
41
  require 'engine/metrics/binary_search_latency_tracker'
38
42
  require 'engine/models/split'
@@ -29,14 +29,21 @@ module SplitIoClient
29
29
  @events_uri = (opts[:events_uri] || SplitConfig.default_events_uri).chomp('/')
30
30
  @mode = opts[:mode] || SplitConfig.default_mode
31
31
  @redis_url = opts[:redis_url] || SplitConfig.default_redis_url
32
- @cache_adapter = SplitConfig.init_cache_adapter(opts[:cache_adapter] || SplitConfig.default_cache_adapter, @redis_url)
32
+ @cache_adapter = SplitConfig.init_cache_adapter(
33
+ opts[:cache_adapter] || SplitConfig.default_cache_adapter, :map_adapter, @redis_url, false
34
+ )
33
35
  @connection_timeout = opts[:connection_timeout] || SplitConfig.default_connection_timeout
34
36
  @read_timeout = opts[:read_timeout] || SplitConfig.default_read_timeout
35
37
  @features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
36
38
  @segments_refresh_rate = opts[:segments_refresh_rate] || SplitConfig.default_segments_refresh_rate
37
39
  @metrics_refresh_rate = opts[:metrics_refresh_rate] || SplitConfig.default_metrics_refresh_rate
40
+
38
41
  @impressions_refresh_rate = opts[:impressions_refresh_rate] || SplitConfig.default_impressions_refresh_rate
39
42
  @impressions_queue_size = opts[:impressions_queue_size] || SplitConfig.default_impressions_queue_size
43
+ @impressions_adapter = SplitConfig.init_cache_adapter(
44
+ opts[:cache_adapter] || SplitConfig.default_cache_adapter, :sized_queue_adapter, @redis_url, @impressions_queue_size
45
+ )
46
+
40
47
  @logger = opts[:logger] || SplitConfig.default_logger
41
48
  @debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
42
49
  @transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
@@ -76,6 +83,12 @@ module SplitIoClient
76
83
  # @return [Object] Cache adapter instance
77
84
  attr_reader :cache_adapter
78
85
 
86
+ #
87
+ # The cache adapter to store impressions in
88
+ #
89
+ # @return [Object] Impressions adapter instance
90
+ attr_reader :impressions_adapter
91
+
79
92
  #
80
93
  # The connection timeout for network connections in seconds.
81
94
  #
@@ -143,10 +156,15 @@ module SplitIoClient
143
156
  'https://events.split.io/api/'
144
157
  end
145
158
 
146
- def self.init_cache_adapter(adapter, redis_url)
159
+ def self.init_cache_adapter(adapter, data_structure, redis_url = nil, impressions_queue_size = nil)
147
160
  case adapter
148
161
  when :memory
149
- SplitIoClient::Cache::Adapters::MemoryAdapter.new
162
+ # takes :memory_adapter (symbol) and returns MemoryAdapter (string)
163
+ adapter = SplitIoClient::Cache::Adapters::MemoryAdapters.const_get(
164
+ data_structure.to_s.split('_').collect(&:capitalize).join
165
+ ).new(impressions_queue_size)
166
+
167
+ SplitIoClient::Cache::Adapters::MemoryAdapter.new(adapter)
150
168
  when :redis
151
169
  SplitIoClient::Cache::Adapters::RedisAdapter.new(redis_url)
152
170
  end
@@ -131,7 +131,7 @@ module SplitIoClient
131
131
  # @param api_key [String] the API key for your split account
132
132
  #
133
133
  # @return [SplitIoClient] split.io client instance
134
- def initialize(api_key, config = {}, adapter = nil, localhost_mode = false, splits_repository, segments_repository)
134
+ def initialize(api_key, config = {}, adapter = nil, localhost_mode = false, splits_repository, segments_repository, impressions_repository)
135
135
  @localhost_mode = localhost_mode
136
136
  @localhost_mode_features = []
137
137
 
@@ -139,6 +139,7 @@ module SplitIoClient
139
139
 
140
140
  @splits_repository = splits_repository
141
141
  @segments_repository = segments_repository
142
+ @impressions_repository = impressions_repository
142
143
 
143
144
  if api_key == LOCALHOST_MODE
144
145
  @localhost_mode = true
@@ -198,8 +199,11 @@ module SplitIoClient
198
199
  result = result.nil? ? Treatments::CONTROL : result
199
200
 
200
201
  begin
201
- @adapter.impressions.log(matching_key, split_name, result, (Time.now.to_f * 1000.0))
202
202
  latency = (Time.now - start) * 1000.0
203
+ if @config.impressions_queue_size > 0
204
+ # Disable impressions if @config.impressions_queue_size == -1
205
+ @impressions_repository.add(split_name, 'key_name' => matching_key, 'treatment' => result, 'time' => (Time.now.to_f * 1000.0).to_i)
206
+ end
203
207
 
204
208
  # Measure
205
209
  @adapter.metrics.time("sdk.get_treatment", latency)
@@ -226,7 +230,7 @@ module SplitIoClient
226
230
  #
227
231
  # @return [string] version value for this sdk
228
232
  def self.sdk_version
229
- 'RubyClientSDK-'+SplitIoClient::VERSION
233
+ 'ruby-'+SplitIoClient::VERSION
230
234
  end
231
235
 
232
236
  private
@@ -275,9 +279,10 @@ module SplitIoClient
275
279
  @cache_adapter = @config.cache_adapter
276
280
  @splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(@cache_adapter)
277
281
  @segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(@cache_adapter)
282
+ @impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(@config.impressions_adapter, @config)
278
283
  @sdk_blocker = SplitIoClient::Cache::Stores::SDKBlocker.new(@config)
279
284
  @adapter = api_key != 'localhost' \
280
- ? SplitAdapter.new(api_key, @config, @splits_repository, @segments_repository, @sdk_blocker)
285
+ ? SplitAdapter.new(api_key, @config, @splits_repository, @segments_repository, @impressions_repository, @sdk_blocker)
281
286
  : nil
282
287
  @localhost_mode = api_key == 'localhost'
283
288
 
@@ -304,7 +309,7 @@ module SplitIoClient
304
309
  attr_reader :adapter
305
310
 
306
311
  def init_client
307
- SplitClient.new(@api_key, @config, @adapter, @localhost_mode, @splits_repository, @segments_repository)
312
+ SplitClient.new(@api_key, @config, @adapter, @localhost_mode, @splits_repository, @segments_repository, @impressions_repository)
308
313
  end
309
314
 
310
315
  def init_manager
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '3.1.0-rc7'
2
+ VERSION = '3.1.0-rc8'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: splitclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0.pre.rc7
4
+ version: 3.1.0.pre.rc8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-11-02 00:00:00.000000000 Z
11
+ date: 2016-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -251,7 +251,12 @@ files:
251
251
  - Rakefile
252
252
  - exe/splitio
253
253
  - lib/cache/adapters/memory_adapter.rb
254
+ - lib/cache/adapters/memory_adapters/map_adapter.rb
255
+ - lib/cache/adapters/memory_adapters/sized_queue_adapter.rb
254
256
  - lib/cache/adapters/redis_adapter.rb
257
+ - lib/cache/repositories/impressions/memory_repository.rb
258
+ - lib/cache/repositories/impressions/redis_repository.rb
259
+ - lib/cache/repositories/impressions_repository.rb
255
260
  - lib/cache/repositories/repository.rb
256
261
  - lib/cache/repositories/segments_repository.rb
257
262
  - lib/cache/repositories/splits_repository.rb
@@ -262,7 +267,6 @@ files:
262
267
  - lib/engine/api/segments.rb
263
268
  - lib/engine/api/splits.rb
264
269
  - lib/engine/evaluator/splitter.rb
265
- - lib/engine/impressions/impressions.rb
266
270
  - lib/engine/matchers/all_keys_matcher.rb
267
271
  - lib/engine/matchers/between_matcher.rb
268
272
  - lib/engine/matchers/combiners.rb
@@ -1,88 +0,0 @@
1
- module SplitIoClient
2
-
3
- #
4
- # class to manage cached impressions
5
- #
6
- class Impressions < NoMethodError
7
-
8
- # the queue of cached impression values
9
- #
10
- # @return [object] array of impressions
11
- attr_accessor :queue
12
-
13
- # max number of cached entries for impressions
14
- #
15
- # @return [int] max numbre of entries
16
- attr_accessor :max_number_of_keys
17
-
18
- #
19
- # initializes the class
20
- #
21
- # @param config [SplitConfig] the config object
22
- def initialize(config)
23
- @config = config
24
- @queue = SizedQueue.new(config.impressions_queue_size <= 0? 1 : config.impressions_queue_size)
25
- @max_number_of_keys = config.impressions_queue_size
26
- end
27
-
28
- #
29
- # generates a new entry for impressions list
30
- #
31
- # @param id [string] user key
32
- # @param feature [string] feature name
33
- # @param treatment [string] treatment value
34
- # @param time [time] time value in milisenconds
35
- #
36
- # @return void
37
- def log(id, feature, treatment, time)
38
- return if @max_number_of_keys <= 0 # shortcut to desable impressions
39
- impressions = KeyImpressions.new(id, treatment, time)
40
- begin
41
- @queue.push( {feature: feature, impressions: impressions} , true ) # don't wait if queue is full
42
- rescue ThreadError
43
- @random_sampler ||= Random.new
44
- if @random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time.
45
- @config.logger.warn("Dropping impressions. Current size is #{@max_number_of_keys}. Consider increasing impressions_queue_size")
46
- end
47
- end
48
- end
49
-
50
- #
51
- # clears the impressions queue
52
- #
53
- # @returns void
54
- def clear
55
- popped_impressions = []
56
- begin
57
- loop do
58
- impression_element = @queue.pop(true)
59
- feature_hash = popped_impressions.find { |i| i[:feature] == impression_element[:feature] }
60
- if feature_hash.nil?
61
- popped_impressions << {feature: impression_element[:feature], impressions: [] << impression_element[:impressions]}
62
- else
63
- feature_hash[:impressions] << impression_element[:impressions]
64
- end
65
- end
66
- rescue ThreadError
67
- end
68
- popped_impressions
69
- end
70
-
71
- end
72
-
73
- #
74
- # small class to use as DTO for impressions
75
- #
76
- class KeyImpressions
77
- attr_accessor :key
78
- attr_accessor :treatment
79
- attr_accessor :time
80
-
81
- def initialize(key, treatment, time)
82
- @key = key
83
- @treatment = treatment
84
- @time = time
85
- end
86
- end
87
-
88
- end