splitclient-rb 7.1.4.pre.rc7-java → 7.1.4.pre.rc8-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d0c945c95a835a0f60f13a614bdae0a20978bf2d
4
- data.tar.gz: 7dbe050fd70c0f5ff6805e38b715f7a571666fba
3
+ metadata.gz: 587fa9e98277f3a3f2551240e421d5a734ed821e
4
+ data.tar.gz: 9ba4dd4290b4b61e18e29c3390a901bdd9e9aebd
5
5
  SHA512:
6
- metadata.gz: 5517bb32198da4696b4bee5a983d7cd2baa3af2caf2de65f2848c58817ad6664865a4437a53e87be9ef54914870a6ab31999304ce2ddc103e9442999a21bb927
7
- data.tar.gz: 55983d8a45d23b1862e5e2809f468779e2f9a7c2edbe059e9beb3615885c0c1211d94554e275f530e1f871489c1952dc410189fe300cb192764308507e0c86da
6
+ metadata.gz: 737188f30254868feebdf799ddc9e4a54ef840db885aac1c1155b1e4bc988e112e15ec7879d89bc31911abb334b08379211b7cf93d5735124b330d2f93bf7704
7
+ data.tar.gz: e96eca60d3dc2c53c2667f0a4eee50ed960865af99d6108788598589f8d16cc5b2d33d61a9ae9616f2446888c9a527b8019eecd459ade92ecce66aedac9c2224
@@ -1,7 +1,7 @@
1
1
  CHANGES
2
2
 
3
3
  7.1.3 (Jul 31, 2020)
4
- - Updated rake development dependency to ~> 12.3.3
4
+ - Updated rake development dependency to ~> 12.3.3.
5
5
 
6
6
  7.1.2 (Jun 15, 2020)
7
7
  - Fixed uninitialized constant LocalhostSplitStore::YAML for console apps.
data/Rakefile CHANGED
@@ -11,14 +11,19 @@ RSpec::Core::RakeTask.new(:spec)
11
11
  RuboCop::RakeTask.new(:rubocop)
12
12
 
13
13
  task spec: :compile
14
-
15
- if RUBY_PLATFORM == 'java'
14
+ case RUBY_PLATFORM
15
+ when 'java'
16
16
  require 'rake/javaextensiontask'
17
17
  Rake::JavaExtensionTask.new 'murmurhash' do |ext|
18
18
  ext.lib_dir = 'lib/murmurhash'
19
19
  ext.target_version = '1.7'
20
20
  ext.source_version = '1.7'
21
21
  end
22
+ else
23
+ require 'rake/extensiontask'
24
+ Rake::ExtensionTask.new 'murmurhash' do |ext|
25
+ ext.lib_dir = 'lib/murmurhash'
26
+ end
22
27
  end
23
28
 
24
29
  if !ENV['APPRAISAL_INITIALIZED']
@@ -159,4 +159,143 @@ public final class MurmurHash3 {
159
159
 
160
160
  return h1 & 0xFFFFFFFFL;
161
161
  }
162
+
163
+ // The following set of methods and constants are borrowed from:
164
+ // `This method is borrowed from `org.apache.commons.codec.digest.MurmurHash3`
165
+
166
+ // Constants for 128-bit variant
167
+ private static final long C1 = 0x87c37b91114253d5L;
168
+ private static final long C2 = 0x4cf5ad432745937fL;
169
+ private static final int R1 = 31;
170
+ private static final int R2 = 27;
171
+ private static final int R3 = 33;
172
+ private static final int M = 5;
173
+ private static final int N1 = 0x52dce729;
174
+ private static final int N2 = 0x38495ab5;
175
+
176
+ /**
177
+ * Gets the little-endian long from 8 bytes starting at the specified index.
178
+ *
179
+ * @param data The data
180
+ * @param index The index
181
+ * @return The little-endian long
182
+ */
183
+ private static long getLittleEndianLong(final byte[] data, final int index) {
184
+ return (((long) data[index ] & 0xff) ) |
185
+ (((long) data[index + 1] & 0xff) << 8) |
186
+ (((long) data[index + 2] & 0xff) << 16) |
187
+ (((long) data[index + 3] & 0xff) << 24) |
188
+ (((long) data[index + 4] & 0xff) << 32) |
189
+ (((long) data[index + 5] & 0xff) << 40) |
190
+ (((long) data[index + 6] & 0xff) << 48) |
191
+ (((long) data[index + 7] & 0xff) << 56);
192
+ }
193
+
194
+ public static long[] hash128x64(final byte[] data) {
195
+ return hash128x64(data, 0, data.length, 0);
196
+ }
197
+
198
+ /**
199
+ * Generates 128-bit hash from the byte array with the given offset, length and seed.
200
+ *
201
+ * <p>This is an implementation of the 128-bit hash function {@code MurmurHash3_x64_128}
202
+ * from from Austin Applyby's original MurmurHash3 {@code c++} code in SMHasher.</p>
203
+ *
204
+ * @param data The input byte array
205
+ * @param offset The first element of array
206
+ * @param length The length of array
207
+ * @param seed The initial seed value
208
+ * @return The 128-bit hash (2 longs)
209
+ */
210
+ public static long[] hash128x64(final byte[] data, final int offset, final int length, final long seed) {
211
+ long h1 = seed;
212
+ long h2 = seed;
213
+ final int nblocks = length >> 4;
214
+
215
+ // body
216
+ for (int i = 0; i < nblocks; i++) {
217
+ final int index = offset + (i << 4);
218
+ long k1 = getLittleEndianLong(data, index);
219
+ long k2 = getLittleEndianLong(data, index + 8);
220
+
221
+ // mix functions for k1
222
+ k1 *= C1;
223
+ k1 = Long.rotateLeft(k1, R1);
224
+ k1 *= C2;
225
+ h1 ^= k1;
226
+ h1 = Long.rotateLeft(h1, R2);
227
+ h1 += h2;
228
+ h1 = h1 * M + N1;
229
+
230
+ // mix functions for k2
231
+ k2 *= C2;
232
+ k2 = Long.rotateLeft(k2, R3);
233
+ k2 *= C1;
234
+ h2 ^= k2;
235
+ h2 = Long.rotateLeft(h2, R1);
236
+ h2 += h1;
237
+ h2 = h2 * M + N2;
238
+ }
239
+
240
+ // tail
241
+ long k1 = 0;
242
+ long k2 = 0;
243
+ final int index = offset + (nblocks << 4);
244
+ switch (offset + length - index) {
245
+ case 15:
246
+ k2 ^= ((long) data[index + 14] & 0xff) << 48;
247
+ case 14:
248
+ k2 ^= ((long) data[index + 13] & 0xff) << 40;
249
+ case 13:
250
+ k2 ^= ((long) data[index + 12] & 0xff) << 32;
251
+ case 12:
252
+ k2 ^= ((long) data[index + 11] & 0xff) << 24;
253
+ case 11:
254
+ k2 ^= ((long) data[index + 10] & 0xff) << 16;
255
+ case 10:
256
+ k2 ^= ((long) data[index + 9] & 0xff) << 8;
257
+ case 9:
258
+ k2 ^= data[index + 8] & 0xff;
259
+ k2 *= C2;
260
+ k2 = Long.rotateLeft(k2, R3);
261
+ k2 *= C1;
262
+ h2 ^= k2;
263
+
264
+ case 8:
265
+ k1 ^= ((long) data[index + 7] & 0xff) << 56;
266
+ case 7:
267
+ k1 ^= ((long) data[index + 6] & 0xff) << 48;
268
+ case 6:
269
+ k1 ^= ((long) data[index + 5] & 0xff) << 40;
270
+ case 5:
271
+ k1 ^= ((long) data[index + 4] & 0xff) << 32;
272
+ case 4:
273
+ k1 ^= ((long) data[index + 3] & 0xff) << 24;
274
+ case 3:
275
+ k1 ^= ((long) data[index + 2] & 0xff) << 16;
276
+ case 2:
277
+ k1 ^= ((long) data[index + 1] & 0xff) << 8;
278
+ case 1:
279
+ k1 ^= data[index] & 0xff;
280
+ k1 *= C1;
281
+ k1 = Long.rotateLeft(k1, R1);
282
+ k1 *= C2;
283
+ h1 ^= k1;
284
+ }
285
+
286
+ // finalization
287
+ h1 ^= length;
288
+ h2 ^= length;
289
+
290
+ h1 += h2;
291
+ h2 += h1;
292
+
293
+ h1 = fmix64(h1);
294
+ h2 = fmix64(h2);
295
+
296
+ h1 += h2;
297
+ h2 += h1;
298
+
299
+ return new long[] { h1, h2 };
300
+ }
162
301
  }
@@ -12,6 +12,8 @@ require 'splitclient-rb/cache/adapters/memory_adapter'
12
12
  require 'splitclient-rb/cache/adapters/redis_adapter'
13
13
  require 'splitclient-rb/cache/fetchers/segment_fetcher'
14
14
  require 'splitclient-rb/cache/fetchers/split_fetcher'
15
+ require 'splitclient-rb/cache/hashers/impression_hasher'
16
+ require 'splitclient-rb/cache/observers/impression_observer'
15
17
  require 'splitclient-rb/cache/repositories/repository'
16
18
  require 'splitclient-rb/cache/repositories/segments_repository'
17
19
  require 'splitclient-rb/cache/repositories/splits_repository'
@@ -28,6 +30,7 @@ require 'splitclient-rb/cache/senders/impressions_formatter'
28
30
  require 'splitclient-rb/cache/senders/impressions_sender'
29
31
  require 'splitclient-rb/cache/senders/metrics_sender'
30
32
  require 'splitclient-rb/cache/senders/events_sender'
33
+ require 'splitclient-rb/cache/senders/impressions_count_sender'
31
34
  require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
32
35
  require 'splitclient-rb/cache/stores/store_utils'
33
36
  require 'splitclient-rb/cache/stores/localhost_split_builder'
@@ -52,6 +55,8 @@ require 'splitclient-rb/engine/api/metrics'
52
55
  require 'splitclient-rb/engine/api/segments'
53
56
  require 'splitclient-rb/engine/api/splits'
54
57
  require 'splitclient-rb/engine/api/events'
58
+ require 'splitclient-rb/engine/common/impressions_counter'
59
+ require 'splitclient-rb/engine/common/impressions_manager'
55
60
  require 'splitclient-rb/engine/parser/condition'
56
61
  require 'splitclient-rb/engine/parser/partition'
57
62
  require 'splitclient-rb/engine/parser/evaluator'
@@ -105,7 +110,7 @@ require 'splitclient-rb/sse/notification_processor'
105
110
  require 'splitclient-rb/sse/sse_handler'
106
111
 
107
112
  # C extension
108
- #require 'murmurhash/murmurhash_mri'
113
+ require 'murmurhash/murmurhash_mri'
109
114
 
110
115
  module SplitIoClient
111
116
  def self.root
@@ -0,0 +1,34 @@
1
+ module SplitIoClient
2
+ module Hashers
3
+ class ImpressionHasher
4
+ def initialize
5
+ @murmur_hash_128_64 = case RUBY_PLATFORM
6
+ when 'java'
7
+ Proc.new { |key, seed| Java::MurmurHash3.hash128x64(key, seed) }
8
+ else
9
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x64_128.rawdigest(key, [seed].pack('L')) }
10
+ end
11
+ end
12
+
13
+ def process(impression)
14
+ impression_data = "#{unknown_if_null(impression[:k])}"
15
+ impression_data << ":#{unknown_if_null(impression[:f])}"
16
+ impression_data << ":#{unknown_if_null(impression[:t])}"
17
+ impression_data << ":#{unknown_if_null(impression[:r])}"
18
+ impression_data << ":#{zero_if_null(impression[:c])}"
19
+
20
+ @murmur_hash_128_64.call(impression_data, 0)[0];
21
+ end
22
+
23
+ private
24
+
25
+ def unknown_if_null(value)
26
+ value == nil ? "UNKNOWN" : value
27
+ end
28
+
29
+ def zero_if_null(value)
30
+ value == nil ? 0 : value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module SplitIoClient
2
+ module Observers
3
+ class ImpressionObserver
4
+ LAST_SEEN_CACHE_SIZE = 500000
5
+
6
+ def initialize
7
+ @cache = LruRedux::TTL::ThreadSafeCache.new(LAST_SEEN_CACHE_SIZE)
8
+ @impression_hasher = Hashers::ImpressionHasher.new
9
+ end
10
+
11
+ def test_and_set(impression)
12
+ return if impression.nil?
13
+
14
+ hash = @impression_hasher.process(impression)
15
+ previous = @cache[hash]
16
+ @cache[hash] = impression[:m]
17
+
18
+ previous.nil? ? nil : [previous, impression[:m]].min
19
+ end
20
+ end
21
+ end
22
+ end
@@ -10,18 +10,10 @@ module SplitIoClient
10
10
  @adapter = @config.impressions_adapter
11
11
  end
12
12
 
13
- # Store impression data in the selected adapter
14
- def add(matching_key, bucketing_key, split_name, treatment, time)
15
- @adapter.add_to_queue(
16
- m: metadata,
17
- i: impression_data(
18
- matching_key,
19
- bucketing_key,
20
- split_name,
21
- treatment,
22
- time
23
- )
24
- )
13
+ def add_bulk(impressions)
14
+ impressions.each do |impression|
15
+ @adapter.add_to_queue(impression)
16
+ end
25
17
  rescue ThreadError # queue is full
26
18
  if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
27
19
  @config.logger.warn("Dropping impressions. Current size is \
@@ -30,12 +22,6 @@ module SplitIoClient
30
22
  end
31
23
  end
32
24
 
33
- def add_bulk(key, bucketing_key, treatments, time)
34
- treatments.each do |split_name, treatment|
35
- add(key, bucketing_key, split_name, treatment, time)
36
- end
37
- end
38
-
39
25
  def batch
40
26
  return [] if @config.impressions_bulk_size.zero?
41
27
 
@@ -12,28 +12,17 @@ module SplitIoClient
12
12
  @adapter = @config.impressions_adapter
13
13
  end
14
14
 
15
- def add(matching_key, bucketing_key, split_name, treatment, time)
16
- add_bulk(matching_key, bucketing_key, { split_name => treatment }, time)
17
- end
18
-
19
- def add_bulk(matching_key, bucketing_key, treatments, time)
20
- impressions = treatments.map do |split_name, treatment|
21
- {
22
- m: metadata,
23
- i: impression_data(
24
- matching_key,
25
- bucketing_key,
26
- split_name,
27
- treatment,
28
- time
29
- )
30
- }.to_json
15
+ def add_bulk(impressions)
16
+ impressions_json = impressions.map do |impression|
17
+ impression.to_json
31
18
  end
32
19
 
33
- impressions_list_size = @adapter.add_to_queue(key, impressions)
20
+ impressions_list_size = @adapter.add_to_queue(key, impressions_json)
34
21
 
35
22
  # Synchronizer might not be running
36
- @adapter.expire(key, EXPIRE_SECONDS) if impressions.size == impressions_list_size
23
+ @adapter.expire(key, EXPIRE_SECONDS) if impressions_json.size == impressions_list_size
24
+ rescue StandardError => e
25
+ @config.logger.error("Exception while add_bulk_v2: #{e}")
37
26
  end
38
27
 
39
28
  def get_impressions(number_of_impressions = 0)
@@ -6,7 +6,7 @@ module SplitIoClient
6
6
  # Repository which forwards impressions interface to the selected adapter
7
7
  class ImpressionsRepository < Repository
8
8
  extend Forwardable
9
- def_delegators :@repository, :add, :add_bulk, :batch, :clear, :empty?
9
+ def_delegators :@repository, :add_bulk, :batch, :clear, :empty?
10
10
 
11
11
  def initialize(config)
12
12
  super(config)
@@ -17,32 +17,6 @@ module SplitIoClient
17
17
  Repositories::Impressions::RedisRepository.new(@config)
18
18
  end
19
19
  end
20
-
21
- protected
22
-
23
- def impression_data(matching_key, bucketing_key, split_name, treatment, timestamp)
24
- {
25
- k: matching_key,
26
- b: bucketing_key,
27
- f: split_name,
28
- t: treatment[:treatment],
29
- r: applied_rule(treatment[:label]),
30
- c: treatment[:change_number],
31
- m: timestamp
32
- }
33
- end
34
-
35
- def metadata
36
- {
37
- s: "#{@config.language}-#{@config.version}",
38
- i: @config.machine_ip,
39
- n: @config.machine_name
40
- }
41
- end
42
-
43
- def applied_rule(label)
44
- @config.labels_enabled ? label : nil
45
- end
46
20
  end
47
21
  end
48
22
  end
@@ -18,24 +18,22 @@ module SplitIoClient
18
18
  end
19
19
  end
20
20
 
21
- def add(impression)
22
- enqueue(impression)
23
- end
24
-
25
21
  def add_bulk(impressions)
26
- impressions[:split_names].each do |split_name|
22
+ return unless @listener
23
+ impressions.each do |impression|
27
24
  enqueue(
28
- split_name: split_name.to_s,
29
- matching_key: impressions[:matching_key],
30
- bucketing_key: impressions[:bucketing_key],
31
- time: impressions[:time],
25
+ split_name: impression[:i][:f],
26
+ matching_key: impression[:i][:k],
27
+ bucketing_key: impression[:i][:b],
28
+ time: impression[:i][:m],
32
29
  treatment: {
33
- label: impressions[:treatments_labels_change_numbers][split_name.to_sym][:label],
34
- treatment: impressions[:treatments_labels_change_numbers][split_name.to_sym][:treatment],
35
- change_number: impressions[:treatments_labels_change_numbers][split_name.to_sym][:change_number]
30
+ label: impression[:i][:r],
31
+ treatment: impression[:i][:t],
32
+ change_number: impression[:i][:c]
36
33
  },
37
- attributes: impressions[:attributes]
38
- ) unless impressions[:treatments_labels_change_numbers][split_name.to_sym].nil?
34
+ previous_time: impression[:i][:pt],
35
+ attributes: impression[:attributes]
36
+ ) unless impression.nil?
39
37
  end
40
38
  end
41
39
 
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsCountSender
7
+ COUNTER_REFRESH_RATE_SECONDS = 1800
8
+
9
+ def initialize(config, impression_counter, impressions_api)
10
+ @config = config
11
+ @impression_counter = impression_counter
12
+ @impressions_api = impressions_api
13
+ end
14
+
15
+ def call
16
+ impressions_count_thread
17
+
18
+ if defined?(PhusionPassenger)
19
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
20
+ impressions_count_thread if forked
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def impressions_count_thread
28
+ @config.threads[:impressions_count_sender] = Thread.new do
29
+ begin
30
+ @config.logger.info('Starting impressions count service')
31
+
32
+ loop do
33
+ post_impressions_count
34
+
35
+ sleep(COUNTER_REFRESH_RATE_SECONDS)
36
+ end
37
+ rescue SplitIoClient::SDKShutdownException
38
+ post_impressions_count
39
+
40
+ @config.logger.info('Posting impressions count due to shutdown')
41
+ end
42
+ end
43
+
44
+ def post_impressions_count
45
+ @impressions_api.post_count(formatter(@impression_counter.pop_all))
46
+ rescue StandardError => error
47
+ @config.log_found_exception(__method__.to_s, error)
48
+ end
49
+
50
+ def formatter(counts)
51
+ return if counts.empty?
52
+
53
+ formated_counts = {pf: []}
54
+
55
+ counts.each do |key, value|
56
+ key_splited = key.split('::')
57
+
58
+ formated_counts[:pf] << {
59
+ f: key_splited[0].to_s, # feature name
60
+ m: key_splited[1].to_i, # time frame
61
+ rc: value # count
62
+ }
63
+ end
64
+
65
+ formated_counts
66
+ rescue StandardError => error
67
+ @config.log_found_exception(__method__.to_s, error)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -17,12 +17,10 @@ module SplitIoClient
17
17
 
18
18
  formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
19
19
  feature_impressions = feature_impressions(filtered_impressions, feature)
20
- ip = feature_impressions.first[:m][:i]
21
20
  current_impressions = current_impressions(feature_impressions)
22
21
  memo << {
23
- testName: feature.to_sym,
24
- keyImpressions: current_impressions,
25
- ip: ip
22
+ f: feature.to_sym,
23
+ i: current_impressions
26
24
  }
27
25
  end
28
26
 
@@ -40,12 +38,13 @@ module SplitIoClient
40
38
  def current_impressions(feature_impressions)
41
39
  feature_impressions.map do |impression|
42
40
  {
43
- keyName: impression[:i][:k],
44
- treatment: impression[:i][:t],
45
- time: impression[:i][:m],
46
- bucketingKey: impression[:i][:b],
47
- label: impression[:i][:r],
48
- changeNumber: impression[:i][:c]
41
+ k: impression[:i][:k],
42
+ t: impression[:i][:t],
43
+ m: impression[:i][:m],
44
+ b: impression[:i][:b],
45
+ r: impression[:i][:r],
46
+ c: impression[:i][:c],
47
+ pt: impression[:i][:pt]
49
48
  }
50
49
  end
51
50
  end
@@ -73,7 +72,8 @@ module SplitIoClient
73
72
  "#{impression[:i][:k]}:" \
74
73
  "#{impression[:i][:b]}:" \
75
74
  "#{impression[:i][:c]}:" \
76
- "#{impression[:i][:t]}"
75
+ "#{impression[:i][:t]}:" \
76
+ "#{impression[:i][:pt]}"
77
77
  end
78
78
  end
79
79
  end
@@ -4,10 +4,10 @@ module SplitIoClient
4
4
  module Cache
5
5
  module Senders
6
6
  class ImpressionsSender
7
- def initialize(impressions_repository, api_key, config)
7
+ def initialize(impressions_repository, config, impressions_api)
8
8
  @impressions_repository = impressions_repository
9
- @api_key = api_key
10
9
  @config = config
10
+ @impressions_api = impressions_api
11
11
  end
12
12
 
13
13
  def call
@@ -50,7 +50,7 @@ module SplitIoClient
50
50
  end
51
51
 
52
52
  def impressions_api
53
- @impressions_api ||= SplitIoClient::Api::Impressions.new(@api_key, @config)
53
+ @impressions_api
54
54
  end
55
55
  end
56
56
  end
@@ -9,7 +9,7 @@ module SplitIoClient
9
9
  # @param api_key [String] the API key for your split account
10
10
  #
11
11
  # @return [SplitIoClient] split.io client instance
12
- def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config)
12
+ def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config, impressions_manager)
13
13
  @api_key = api_key
14
14
  @metrics = metrics
15
15
  @splits_repository = splits_repository
@@ -20,17 +20,21 @@ module SplitIoClient
20
20
  @sdk_blocker = sdk_blocker
21
21
  @destroyed = false
22
22
  @config = config
23
+ @impressions_manager = impressions_manager
23
24
  end
24
25
 
25
26
  def get_treatment(
26
27
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
27
28
  multiple = false, evaluator = nil
28
29
  )
29
- treatment = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator)
30
+ impressions = []
31
+ result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment', impressions)
32
+ @impressions_manager.track(impressions)
33
+
30
34
  if multiple
31
- treatment.tap { |t| t.delete(:config) }
35
+ result.tap { |t| t.delete(:config) }
32
36
  else
33
- treatment[:treatment]
37
+ result[:treatment]
34
38
  end
35
39
  end
36
40
 
@@ -38,7 +42,11 @@ module SplitIoClient
38
42
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
39
43
  multiple = false, evaluator = nil
40
44
  )
41
- treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config')
45
+ impressions = []
46
+ result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config', impressions)
47
+ @impressions_manager.track(impressions)
48
+
49
+ result
42
50
  end
43
51
 
44
52
  def get_treatments(key, split_names, attributes = {})
@@ -74,53 +82,6 @@ module SplitIoClient
74
82
  @destroyed = true
75
83
  end
76
84
 
77
- def store_impression(split_name, matching_key, bucketing_key, treatment, attributes)
78
- time = (Time.now.to_f * 1000.0).to_i
79
-
80
- @impressions_repository.add(
81
- matching_key,
82
- bucketing_key,
83
- split_name,
84
- treatment,
85
- time
86
- )
87
-
88
- route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
89
-
90
- rescue StandardError => error
91
- @config.log_found_exception(__method__.to_s, error)
92
- end
93
-
94
- def route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
95
- impression_router.add(
96
- split_name: split_name,
97
- matching_key: matching_key,
98
- bucketing_key: bucketing_key,
99
- time: time,
100
- treatment: {
101
- label: treatment[:label],
102
- treatment: treatment[:treatment],
103
- change_number: treatment[:change_number]
104
- },
105
- attributes: attributes
106
- )
107
- end
108
-
109
- def route_impressions(split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
110
- impression_router.add_bulk(
111
- split_names: split_names,
112
- matching_key: matching_key,
113
- bucketing_key: bucketing_key,
114
- time: time,
115
- treatments_labels_change_numbers: treatments_labels_change_numbers,
116
- attributes: attributes
117
- )
118
- end
119
-
120
- def impression_router
121
- @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
122
- end
123
-
124
85
  def track(key, traffic_type_name, event_type, value = nil, properties = nil)
125
86
  return false unless valid_client && @config.split_validator.valid_track_parameters(key, traffic_type_name, event_type, value, properties)
126
87
 
@@ -239,26 +200,20 @@ module SplitIoClient
239
200
 
240
201
  bucketing_key, matching_key = keys_from_key(key)
241
202
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
242
- matching_key = matching_key ? matching_key.to_s : nil
203
+ matching_key = matching_key ? matching_key.to_s : nil
243
204
 
244
205
  evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true)
245
206
  start = Time.now
207
+ impressions = []
246
208
  treatments_labels_change_numbers =
247
209
  @splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
248
- memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator))
210
+ memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions))
249
211
  end
250
212
  latency = (Time.now - start) * 1000.0
251
213
  # Measure
252
214
  @metrics.time('sdk.' + calling_method, latency)
253
215
 
254
- treatments_for_impressions = get_treatment_for_impressions(treatments_labels_change_numbers)
255
-
256
- time = (Time.now.to_f * 1000.0).to_i
257
- @impressions_repository.add_bulk(
258
- matching_key, bucketing_key, treatments_for_impressions, time
259
- ) unless treatments_for_impressions == {}
260
-
261
- route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_for_impressions, attributes)
216
+ @impressions_manager.track(impressions)
262
217
 
263
218
  split_names_keys = treatments_labels_change_numbers.keys
264
219
  treatments = treatments_labels_change_numbers.values.map do |v|
@@ -284,7 +239,7 @@ module SplitIoClient
284
239
  # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
285
240
  def treatment(
286
241
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
287
- multiple = false, evaluator = nil, calling_method = 'get_treatment'
242
+ multiple = false, evaluator = nil, calling_method = 'get_treatment', impressions = []
288
243
  )
289
244
  control_treatment = { treatment: Engine::Models::Treatment::CONTROL }
290
245
 
@@ -329,15 +284,18 @@ module SplitIoClient
329
284
  end
330
285
 
331
286
  latency = (Time.now - start) * 1000.0
332
-
333
- store_impression(split_name, matching_key, bucketing_key, treatment_data, attributes) if store_impressions
287
+
288
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil })
289
+ impressions << impression unless impression.nil?
334
290
 
335
291
  # Measure
336
292
  @metrics.time('sdk.' + calling_method, latency) unless multiple
337
293
  rescue StandardError => error
294
+ p error
338
295
  @config.log_found_exception(__method__.to_s, error)
339
296
 
340
- store_impression(split_name, matching_key, bucketing_key, control_treatment, attributes) if store_impressions
297
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, control_treatment, { attributes: attributes, time: nil })
298
+ impressions << impression unless impression.nil?
341
299
 
342
300
  return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }))
343
301
  end
@@ -357,12 +315,5 @@ module SplitIoClient
357
315
  def parsed_attributes(attributes)
358
316
  return attributes || attributes.to_h
359
317
  end
360
-
361
- def get_treatment_for_impressions(treatments_labels_change_numbers)
362
- return treatments_labels_change_numbers.select{|imp|
363
- treatments_labels_change_numbers[imp][:label] != Engine::Models::Label::NOT_FOUND &&
364
- !treatments_labels_change_numbers[imp][:label].nil?
365
- }
366
- end
367
318
  end
368
319
  end
@@ -14,16 +14,31 @@ module SplitIoClient
14
14
  return
15
15
  end
16
16
 
17
- impressions_by_ip(impressions).each do |ip, impressions_ip|
18
- response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions_ip)
19
-
20
- if response.success?
21
- @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
22
- else
23
- @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
24
- ' - Check your API key and base URI')
25
- raise 'Split SDK failed to connect to backend to post impressions'
26
- end
17
+ response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
18
+
19
+ if response.success?
20
+ @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
21
+ else
22
+ @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
23
+ ' - Check your API key and base URI')
24
+ raise 'Split SDK failed to connect to backend to post impressions'
25
+ end
26
+ end
27
+
28
+ def post_count(impressions_count)
29
+ if impressions_count.nil? || impressions_count[:pf].empty?
30
+ @config.split_logger.log_if_debug('No impressions count to send')
31
+ return
32
+ end
33
+
34
+ response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
35
+
36
+ if response.success?
37
+ @config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
38
+ else
39
+ @config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
40
+ ' - Check your API key and base URI')
41
+ raise 'Split SDK failed to connect to backend to post impressions'
27
42
  end
28
43
  end
29
44
 
@@ -31,14 +46,16 @@ module SplitIoClient
31
46
  return 0 if impressions.nil?
32
47
 
33
48
  impressions.reduce(0) do |impressions_count, impression|
34
- impressions_count += impression[:keyImpressions].length
49
+ impressions_count += impression[:i].length
35
50
  end
36
51
  end
37
52
 
38
53
  private
39
54
 
40
- def impressions_by_ip(impressions)
41
- impressions.group_by { |impression| impression[:ip] }
55
+ def impressions_headers
56
+ {
57
+ 'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
58
+ }
42
59
  end
43
60
  end
44
61
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ TIME_INTERVAL_MS = 3600 * 1000
9
+
10
+ class ImpressionCounter
11
+ DEFAULT_AMOUNT = 1
12
+
13
+ def initialize
14
+ @cache = Concurrent::Hash.new
15
+ end
16
+
17
+ def inc(split_name, time_frame)
18
+ key = make_key(split_name, time_frame)
19
+
20
+ current_amount = @cache[key]
21
+ @cache[key] = current_amount.nil? ? DEFAULT_AMOUNT : (current_amount + DEFAULT_AMOUNT)
22
+ end
23
+
24
+ def pop_all
25
+ to_return = Concurrent::Hash.new
26
+
27
+ @cache.each do |key, value|
28
+ to_return[key] = value
29
+ end
30
+ @cache.clear
31
+
32
+ to_return
33
+ end
34
+
35
+ def truncate_time_frame(timestamp_ms)
36
+ timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
37
+ end
38
+
39
+ def make_key(split_name, time_frame)
40
+ "#{split_name}::#{truncate_time_frame(time_frame)}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Common
6
+ class ImpressionManager
7
+ def initialize(config, impressions_repository, impression_counter)
8
+ @config = config
9
+ @impressions_repository = impressions_repository
10
+ @impression_counter = impression_counter
11
+ @impression_router = SplitIoClient::ImpressionRouter.new(@config)
12
+ @impression_observer = SplitIoClient::Observers::ImpressionObserver.new
13
+ end
14
+
15
+ # added param time for test
16
+ def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
17
+ impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
18
+
19
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data) unless redis?
20
+
21
+ return impression_optimized(split_name, impression_data, params[:attributes]) if optimized? && !redis?
22
+
23
+ impression(impression_data, params[:attributes])
24
+ rescue StandardError => error
25
+ @config.log_found_exception(__method__.to_s, error)
26
+ end
27
+
28
+ def track(impressions)
29
+ @impressions_repository.add_bulk(impressions)
30
+ @impression_router.add_bulk(impressions)
31
+ rescue StandardError => error
32
+ @config.log_found_exception(__method__.to_s, error)
33
+ end
34
+
35
+ private
36
+
37
+ # added param time for test
38
+ def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil)
39
+ {
40
+ k: matching_key,
41
+ b: bucketing_key,
42
+ f: split_name,
43
+ t: treatment[:treatment],
44
+ r: applied_rule(treatment[:label]),
45
+ c: treatment[:change_number],
46
+ m: time || (Time.now.to_f * 1000.0).to_i,
47
+ pt: nil
48
+ }
49
+ end
50
+
51
+ def metadata
52
+ {
53
+ s: "#{@config.language}-#{@config.version}",
54
+ i: @config.machine_ip,
55
+ n: @config.machine_name
56
+ }
57
+ end
58
+
59
+ def applied_rule(label)
60
+ @config.labels_enabled ? label : nil
61
+ end
62
+
63
+ def optimized?
64
+ @config.impressions_mode == :optimized
65
+ end
66
+
67
+ def impression_optimized(split_name, impression_data, attributes)
68
+ @impression_counter.inc(split_name, impression_data[:m])
69
+
70
+ impression(impression_data, attributes) if should_queue_impression?(impression_data)
71
+ end
72
+
73
+ def should_queue_impression?(impression)
74
+ impression[:pt].nil? || (impression[:pt] < ((Time.now.to_f * 1000.0).to_i - Common::TIME_INTERVAL_MS))
75
+ end
76
+
77
+ def impression(impression_data, attributes)
78
+ { m: metadata, i: impression_data, attributes: attributes }
79
+ end
80
+
81
+ def redis?
82
+ @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,7 +1,3 @@
1
- unless defined?(JRUBY_VERSION)
2
- require 'digest/murmurhash'
3
- end
4
-
5
1
  module SplitIoClient
6
2
  # Misc class in charge of providing hash functions and
7
3
  # determination of treatment based on concept of buckets
@@ -13,7 +9,7 @@ module SplitIoClient
13
9
  when 'java'
14
10
  Proc.new { |key, seed| Java::MurmurHash3.murmurhash3_x86_32(key, seed) }
15
11
  else
16
- Proc.new { |key, seed| Digest::MurmurHash3_x86_32.rawdigest(key, [seed].pack('L')) }
12
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x86_32.rawdigest(key, [seed].pack('L')) }
17
13
  end
18
14
  end
19
15
 
@@ -1,7 +1,3 @@
1
- unless defined?(JRUBY_VERSION)
2
- require 'digest/murmurhash'
3
- end
4
-
5
1
  module SplitIoClient
6
2
  module Engine
7
3
  module Parser
@@ -9,14 +9,13 @@ module SplitIoClient
9
9
  repositories,
10
10
  api_key,
11
11
  config,
12
- sdk_blocker,
13
- metrics
12
+ params
14
13
  )
15
- split_fetcher = SplitFetcher.new(repositories[:splits], api_key, metrics, config, sdk_blocker)
16
- segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, metrics, config, sdk_blocker)
17
- sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher }
14
+ split_fetcher = SplitFetcher.new(repositories[:splits], api_key, params[:metrics], config, params[:sdk_blocker])
15
+ segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, params[:metrics], config, params[:sdk_blocker])
16
+ sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher, imp_counter: params[:impression_counter] }
18
17
 
19
- @synchronizer = Synchronizer.new(repositories, api_key, config, sdk_blocker, sync_params)
18
+ @synchronizer = Synchronizer.new(repositories, api_key, config, params[:sdk_blocker], sync_params)
20
19
  notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
21
20
  manager.on_occupancy { |publisher_available| process_occupancy(publisher_available) }
22
21
  manager.on_push_shutdown { process_push_shutdown }
@@ -23,6 +23,8 @@ module SplitIoClient
23
23
  @sdk_blocker = sdk_blocker
24
24
  @split_fetcher = params[:split_fetcher]
25
25
  @segment_fetcher = params[:segment_fetcher]
26
+ @impressions_api = SplitIoClient::Api::Impressions.new(@api_key, @config)
27
+ @impression_counter = params[:imp_counter]
26
28
  end
27
29
 
28
30
  def sync_all
@@ -35,6 +37,7 @@ module SplitIoClient
35
37
  impressions_sender
36
38
  metrics_sender
37
39
  events_sender
40
+ impressions_count_sender
38
41
  end
39
42
 
40
43
  def start_periodic_fetch
@@ -73,7 +76,7 @@ module SplitIoClient
73
76
 
74
77
  # Starts thread which loops constantly and sends impressions to the Split API
75
78
  def impressions_sender
76
- ImpressionsSender.new(@impressions_repository, @api_key, @config).call
79
+ ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
77
80
  end
78
81
 
79
82
  # Starts thread which loops constantly and sends metrics to the Split API
@@ -85,6 +88,11 @@ module SplitIoClient
85
88
  def events_sender
86
89
  EventsSender.new(@events_repository, @config).call
87
90
  end
91
+
92
+ # Starts thread which loops constantly and sends impressions count to the Split API
93
+ def impressions_count_sender
94
+ ImpressionsCountSender.new(@config, @impression_counter, @impressions_api).call
95
+ end
88
96
  end
89
97
  end
90
98
  end
@@ -53,7 +53,9 @@ module SplitIoClient
53
53
  @segments_refresh_rate = opts[:segments_refresh_rate] || SplitConfig.default_segments_refresh_rate
54
54
  @metrics_refresh_rate = opts[:metrics_refresh_rate] || SplitConfig.default_metrics_refresh_rate
55
55
 
56
- @impressions_refresh_rate = opts[:impressions_refresh_rate] || SplitConfig.default_impressions_refresh_rate
56
+ @impressions_mode = init_impressions_mode(opts[:impressions_mode])
57
+
58
+ @impressions_refresh_rate = SplitConfig.init_impressions_refresh_rate(@impressions_mode, opts[:impressions_refresh_rate], SplitConfig.default_impressions_refresh_rate)
57
59
  @impressions_queue_size = opts[:impressions_queue_size] || SplitConfig.default_impressions_queue_size
58
60
  @impressions_adapter = SplitConfig.init_cache_adapter(
59
61
  opts[:cache_adapter] || SplitConfig.default_cache_adapter, :queue_adapter, @impressions_queue_size, @redis_url
@@ -270,6 +272,30 @@ module SplitIoClient
270
272
 
271
273
  attr_accessor :streaming_enabled
272
274
 
275
+ attr_accessor :impressions_mode
276
+
277
+ def self.default_impressions_mode
278
+ :optimized
279
+ end
280
+
281
+ def init_impressions_mode(impressions_mode)
282
+ impressions_mode ||= SplitConfig.default_impressions_mode
283
+
284
+ case impressions_mode
285
+ when :debug
286
+ return :debug
287
+ else
288
+ @logger.error('You passed an invalid impressions_mode, impressions_mode should be one of the following values: :debug or :optimized. Defaulting to :optimized mode') unless impressions_mode == :optimized
289
+ return :optimized
290
+ end
291
+ end
292
+
293
+ def self.init_impressions_refresh_rate(impressions_mode, refresh_rate, default_rate)
294
+ return (refresh_rate.nil? || refresh_rate <= 0 ? default_rate : refresh_rate) if impressions_mode == :debug
295
+
296
+ return refresh_rate.nil? || refresh_rate <= 0 ? SplitConfig.default_impressions_refresh_rate_optimized : [default_rate, refresh_rate].max
297
+ end
298
+
273
299
  def self.default_streaming_enabled
274
300
  true
275
301
  end
@@ -387,6 +413,10 @@ module SplitIoClient
387
413
  60
388
414
  end
389
415
 
416
+ def self.default_impressions_refresh_rate_optimized
417
+ 300
418
+ end
419
+
390
420
  def self.default_impression_listener_refresh_rate
391
421
  0
392
422
  end
@@ -34,10 +34,12 @@ module SplitIoClient
34
34
  @metrics_repository = MetricsRepository.new(@config)
35
35
  @sdk_blocker = SDKBlocker.new(@splits_repository, @segments_repository, @config)
36
36
  @metrics = Metrics.new(100, @metrics_repository)
37
+ @impression_counter = SplitIoClient::Engine::Common::ImpressionCounter.new
38
+ @impressions_manager = SplitIoClient::Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter)
37
39
 
38
40
  start!
39
41
 
40
- @client = SplitClient.new(@api_key, @metrics, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config)
42
+ @client = SplitClient.new(@api_key, @metrics, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config, @impressions_manager)
41
43
  @manager = SplitManager.new(@splits_repository, @sdk_blocker, @config)
42
44
 
43
45
  validate_api_key
@@ -51,7 +53,8 @@ module SplitIoClient
51
53
  if @config.localhost_mode
52
54
  start_localhost_components
53
55
  else
54
- SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, @sdk_blocker, @metrics).start
56
+ params = { sdk_blocker: @sdk_blocker, metrics: @metrics, impression_counter: @impression_counter }
57
+ SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, params).start
55
58
  end
56
59
  end
57
60
 
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '7.1.4.pre.rc7'
2
+ VERSION = '7.1.4.pre.rc8'
3
3
  end
@@ -26,7 +26,14 @@ Gem::Specification.new do |spec|
26
26
  lib/murmurhash/murmurhash.jar]
27
27
  )
28
28
  else
29
- spec.add_runtime_dependency 'digest-murmurhash', '>= 1.1.1'
29
+ spec.files.concat(
30
+ %w[ext/murmurhash/3_x86_32.c
31
+ ext/murmurhash/3_x64_128.c
32
+ ext/murmurhash/extconf.rb
33
+ ext/murmurhash/murmurhash.c
34
+ ext/murmurhash/murmurhash.h]
35
+ )
36
+ spec.extensions = ['ext/murmurhash/extconf.rb']
30
37
  end
31
38
 
32
39
  spec.add_development_dependency 'allocation_stats'
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: 7.1.4.pre.rc7
4
+ version: 7.1.4.pre.rc8
5
5
  platform: java
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2020-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -337,6 +337,8 @@ files:
337
337
  - lib/splitclient-rb/cache/adapters/redis_adapter.rb
338
338
  - lib/splitclient-rb/cache/fetchers/segment_fetcher.rb
339
339
  - lib/splitclient-rb/cache/fetchers/split_fetcher.rb
340
+ - lib/splitclient-rb/cache/hashers/impression_hasher.rb
341
+ - lib/splitclient-rb/cache/observers/impression_observer.rb
340
342
  - lib/splitclient-rb/cache/repositories/events/memory_repository.rb
341
343
  - lib/splitclient-rb/cache/repositories/events/redis_repository.rb
342
344
  - lib/splitclient-rb/cache/repositories/events_repository.rb
@@ -351,6 +353,7 @@ files:
351
353
  - lib/splitclient-rb/cache/repositories/splits_repository.rb
352
354
  - lib/splitclient-rb/cache/routers/impression_router.rb
353
355
  - lib/splitclient-rb/cache/senders/events_sender.rb
356
+ - lib/splitclient-rb/cache/senders/impressions_count_sender.rb
354
357
  - lib/splitclient-rb/cache/senders/impressions_formatter.rb
355
358
  - lib/splitclient-rb/cache/senders/impressions_sender.rb
356
359
  - lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb
@@ -370,6 +373,8 @@ files:
370
373
  - lib/splitclient-rb/engine/api/segments.rb
371
374
  - lib/splitclient-rb/engine/api/splits.rb
372
375
  - lib/splitclient-rb/engine/auth_api_client.rb
376
+ - lib/splitclient-rb/engine/common/impressions_counter.rb
377
+ - lib/splitclient-rb/engine/common/impressions_manager.rb
373
378
  - lib/splitclient-rb/engine/evaluator/splitter.rb
374
379
  - lib/splitclient-rb/engine/matchers/all_keys_matcher.rb
375
380
  - lib/splitclient-rb/engine/matchers/between_matcher.rb