splitclient-rb 7.3.4.pre.rc2-java → 7.3.5.pre.rc3-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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/update-license-year.yml +45 -0
  3. data/.rubocop.yml +2 -1
  4. data/CHANGES.txt +5 -1
  5. data/LICENSE +1 -1
  6. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +63 -0
  7. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  8. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  9. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  10. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +77 -0
  11. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +6 -30
  12. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  13. data/lib/splitclient-rb/engine/api/telemetry_api.rb +8 -0
  14. data/lib/splitclient-rb/engine/common/impressions_manager.rb +55 -40
  15. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  16. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  17. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +97 -0
  18. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +2 -3
  19. data/lib/splitclient-rb/engine/sync_manager.rb +13 -0
  20. data/lib/splitclient-rb/engine/synchronizer.rb +16 -8
  21. data/lib/splitclient-rb/split_config.rb +36 -0
  22. data/lib/splitclient-rb/split_factory.rb +59 -13
  23. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +3 -1
  24. data/lib/splitclient-rb/utilitites.rb +12 -0
  25. data/lib/splitclient-rb/version.rb +1 -1
  26. data/lib/splitclient-rb.rb +9 -0
  27. data/splitclient-rb.gemspec +1 -0
  28. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ea2b0463e8a3c2f13075209d9e0cb8b189696b3
4
- data.tar.gz: 601571ffb3a015fbf8dcb468577b05caced467c7
3
+ metadata.gz: 186060ea5e282d91426d8c220d060fe34cf8a316
4
+ data.tar.gz: 2217aae3d87f3dd8ba0a09128ba5d7a064fb1fae
5
5
  SHA512:
6
- metadata.gz: c1eaa3106070a63477fa3951ff55a0c24c4287d379384da95852fb34ddc2a20d61557d3282e40f4c56d14f915f6bd615223b8830a8bd0d2b9afd36d32331f83e
7
- data.tar.gz: bde0cdac66e0bdeb0e5e02fc8e77ef8c834c56a767fdf197c398add674e1dbf1e19c683feda9384ab5ce2f80571d0c633e50e8417ab2cd3c0dfab8b7005b1cb3
6
+ metadata.gz: e3e69c550554845b3b27ae2132a86c25e2d2f0b49c001baacdea33feec117f8ce5778ff004ccfe44388841b384d9a086db43eeb89997e2159503361adda3525b
7
+ data.tar.gz: fdc0ec71a389b8f48f9d941cca7bf2571d3e91cd85ee0f1ac03413e53ac8ea4367eb5d8d00b414c5649262f88cba4be8e70726d38d67cbf2f0a8c813ee830fa6
@@ -0,0 +1,45 @@
1
+ name: Update License Year
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 3 1 1 *" # 03:00 AM on January 1
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v2
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Set Current year
21
+ run: "echo CURRENT=$(date +%Y) >> $GITHUB_ENV"
22
+
23
+ - name: Set Previous Year
24
+ run: "echo PREVIOUS=$(($CURRENT-1)) >> $GITHUB_ENV"
25
+
26
+ - name: Update LICENSE
27
+ uses: jacobtomlinson/gha-find-replace@v2
28
+ with:
29
+ find: ${{ env.PREVIOUS }}
30
+ replace: ${{ env.CURRENT }}
31
+ include: "LICENSE"
32
+ regex: false
33
+
34
+ - name: Commit files
35
+ run: |
36
+ git config user.name 'github-actions[bot]'
37
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
38
+ git commit -m "Updated License Year" -a
39
+
40
+ - name: Create Pull Request
41
+ uses: peter-evans/create-pull-request@v3
42
+ with:
43
+ token: ${{ secrets.GITHUB_TOKEN }}
44
+ title: Update License Year
45
+ branch: update-license
data/.rubocop.yml CHANGED
@@ -26,7 +26,7 @@ Metrics/ParameterLists:
26
26
  - lib/splitclient-rb/engine/sync_manager.rb
27
27
 
28
28
  Metrics/LineLength:
29
- Max: 130
29
+ Max: 135
30
30
  Exclude:
31
31
  - spec/sse/**/*
32
32
  - spec/integrations/**/*
@@ -35,6 +35,7 @@ Metrics/LineLength:
35
35
  - spec/telemetry/synchronizer_spec.rb
36
36
  - spec/splitclient/split_config_spec.rb
37
37
  - spec/engine/push_manager_spec.rb
38
+ - spec/cache/senders/impressions_sender_adapter_spec.rb
38
39
 
39
40
  Style/BracesAroundHashParameters:
40
41
  Exclude:
data/CHANGES.txt CHANGED
@@ -1,6 +1,10 @@
1
1
  CHANGES
2
2
 
3
- 7.3.3 (Jan 28, 2021)
3
+ 7.3.4 (Feb 21, 2022)
4
+ - Updated streaming events architecture with a new queue logic.
5
+ - Fixed redis integration Pipelining command deprecation warning.
6
+
7
+ 7.3.3 (Jan 28, 2022)
4
8
  - Fixed edge cases where the sdk lost streaming connection.
5
9
  - Updated default auth service url to https://auth.split.io/api/v2/auth
6
10
  - Updated dependencies:
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2021 Split Software, Inc.
1
+ Copyright © 2022 Split Software, Inc.
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bitarray'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Filter
8
+ class BloomFilter
9
+ def initialize(capacity, false_positive_probability = 0.001)
10
+ @capacity = capacity.round
11
+ m = best_m(capacity, false_positive_probability)
12
+ @ba = BitArray.new(m.round)
13
+ @k = best_k(capacity)
14
+ end
15
+
16
+ def add(string)
17
+ return false if contains?(string)
18
+
19
+ positions = hashes(string)
20
+
21
+ positions.each { |position| @ba[position] = 1 }
22
+
23
+ true
24
+ end
25
+
26
+ def contains?(string)
27
+ !hashes(string).any? { |ea| @ba[ea] == 0 }
28
+ end
29
+
30
+ def clear
31
+ @ba.size.times { |i| @ba[i] = 0 }
32
+ end
33
+
34
+ private
35
+
36
+ # m is the required number of bits in the array
37
+ def best_m(capacity, false_positive_probability)
38
+ -(capacity * Math.log(false_positive_probability)) / (Math.log(2) ** 2)
39
+ end
40
+
41
+ # k is the number of hash functions that minimizes the probability of false positives
42
+ def best_k(capacity)
43
+ (Math.log(2) * (@ba.size / capacity)).round
44
+ end
45
+
46
+ def hashes(data)
47
+ m = @ba.size
48
+ h = Digest::MD5.hexdigest(data.to_s).to_i(16)
49
+ x = h % m
50
+ h /= m
51
+ y = h % m
52
+ h /= m
53
+ z = h % m
54
+ [x] + 1.upto(@k - 1).collect do |i|
55
+ x = (x + y) % m
56
+ y = (y + z) % m
57
+ x
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Filter
6
+ class FilterAdapter
7
+ def initialize(config, filter)
8
+ @config = config
9
+ @filter = filter
10
+ end
11
+
12
+ def add(feature_name, key)
13
+ @filter.add("#{feature_name}#{key}")
14
+ rescue StandardError => e
15
+ @config.log_found_exception(__method__.to_s, e)
16
+ end
17
+
18
+ def contains?(feature_name, key)
19
+ @filter.contains?("#{feature_name}#{key}")
20
+ rescue StandardError => e
21
+ @config.log_found_exception(__method__.to_s, e)
22
+ end
23
+
24
+ def clear
25
+ @filter.clear
26
+ rescue StandardError => e
27
+ @config.log_found_exception(__method__.to_s, e)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module SplitIoClient
2
+ module Observers
3
+ class NoopImpressionObserver
4
+ def test_and_set(impression)
5
+ # no-op
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class MemoryImpressionsSender < ImpressionsSenderAdapter
7
+ def initialize(config, telemetry_api, impressions_api)
8
+ @config = config
9
+ @telemetry_api = telemetry_api
10
+ @impressions_api = impressions_api
11
+ end
12
+
13
+ def record_uniques_key(uniques)
14
+ uniques_keys = uniques_formatter(uniques)
15
+
16
+ @telemetry_api.record_unique_keys(uniques_keys) unless uniques_keys.nil?
17
+ rescue StandardError => e
18
+ @config.log_found_exception(__method__.to_s, e)
19
+ end
20
+
21
+ def record_impressions_count(impressions_count)
22
+ counts = impressions_count_formatter(impressions_count)
23
+
24
+ @impressions_api.post_count(counts) unless counts.nil?
25
+ rescue StandardError => e
26
+ @config.log_found_exception(__method__.to_s, e)
27
+ end
28
+
29
+ private
30
+
31
+ def uniques_formatter(uniques)
32
+ return if uniques.nil? || uniques.empty?
33
+
34
+ to_return = { mtks: [] }
35
+ uniques.each do |key, value|
36
+ to_return[:mtks] << {
37
+ f: key,
38
+ ks: value.to_a
39
+ }
40
+ end
41
+
42
+ to_return
43
+ rescue StandardError => error
44
+ @config.log_found_exception(__method__.to_s, error)
45
+ nil
46
+ end
47
+
48
+ def impressions_count_formatter(counts)
49
+ return if counts.nil? || counts.empty?
50
+
51
+ formated_counts = {pf: []}
52
+
53
+ counts.each do |key, value|
54
+ key_splited = key.split('::')
55
+
56
+ formated_counts[:pf] << {
57
+ f: key_splited[0].to_s, # feature name
58
+ m: key_splited[1].to_i, # time frame
59
+ rc: value # count
60
+ }
61
+ end
62
+
63
+ formated_counts
64
+ rescue StandardError => error
65
+ @config.log_found_exception(__method__.to_s, error)
66
+ nil
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class RedisImpressionsSender < ImpressionsSenderAdapter
7
+ EXPIRE_SECONDS = 3600
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ @adapter = @config.impressions_adapter
12
+ end
13
+
14
+ def record_uniques_key(uniques)
15
+ formatted = uniques_formatter(uniques)
16
+
17
+ unless formatted.nil?
18
+ size = @adapter.add_to_queue(unique_keys_key, formatted)
19
+ @adapter.expire(unique_keys_key, EXPIRE_SECONDS) if formatted.size == size
20
+ end
21
+ rescue StandardError => e
22
+ @config.log_found_exception(__method__.to_s, e)
23
+ end
24
+
25
+ def record_impressions_count(impressions_count)
26
+ return if impressions_count.nil? || impressions_count.empty?
27
+
28
+ result = @adapter.redis.pipelined do |pipeline|
29
+ impressions_count.each do |key, value|
30
+ pipeline.hincrby(impressions_count_key, key, value)
31
+ end
32
+
33
+ @future = pipeline.hlen(impressions_count_key)
34
+ end
35
+
36
+ expire_impressions_count_key(impressions_count, result)
37
+ rescue StandardError => e
38
+ @config.log_found_exception(__method__.to_s, e)
39
+ end
40
+
41
+ private
42
+
43
+ def expire_impressions_count_key(impressions_count, pipeline_result)
44
+ total_count = impressions_count.sum { |_, value| value }
45
+ hlen = pipeline_result.last
46
+
47
+ @adapter.expire(impressions_count_key, EXPIRE_SECONDS) if impressions_count.size == hlen && (pipeline_result.sum - hlen) == total_count
48
+ end
49
+
50
+ def impressions_count_key
51
+ "#{@config.redis_namespace}.impressions.count"
52
+ end
53
+
54
+ def unique_keys_key
55
+ "#{@config.redis_namespace}.uniquekeys"
56
+ end
57
+
58
+ def uniques_formatter(uniques)
59
+ return if uniques.nil? || uniques.empty?
60
+
61
+ to_return = []
62
+ uniques.each do |key, value|
63
+ to_return << {
64
+ f: key,
65
+ k: value.to_a
66
+ }
67
+ end
68
+
69
+ to_return
70
+ rescue StandardError => error
71
+ @config.log_found_exception(__method__.to_s, error)
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -4,12 +4,10 @@ module SplitIoClient
4
4
  module Cache
5
5
  module Senders
6
6
  class ImpressionsCountSender
7
- COUNTER_REFRESH_RATE_SECONDS = 1800
8
-
9
- def initialize(config, impression_counter, impressions_api)
7
+ def initialize(config, impression_counter, impressions_sender_adapter)
10
8
  @config = config
11
9
  @impression_counter = impression_counter
12
- @impressions_api = impressions_api
10
+ @impressions_sender_adapter = impressions_sender_adapter
13
11
  end
14
12
 
15
13
  def call
@@ -22,13 +20,11 @@ module SplitIoClient
22
20
  @config.threads[:impressions_count_sender] = Thread.new do
23
21
  begin
24
22
  @config.logger.info('Starting impressions count service')
25
-
26
23
  loop do
27
- post_impressions_count
28
-
29
- sleep(COUNTER_REFRESH_RATE_SECONDS)
24
+ sleep(@config.counter_refresh_rate)
25
+ post_impressions_count
30
26
  end
31
- rescue SplitIoClient::SDKShutdownException
27
+ rescue SplitIoClient::SDKShutdownException
32
28
  post_impressions_count
33
29
 
34
30
  @config.logger.info('Posting impressions count due to shutdown')
@@ -37,27 +33,7 @@ module SplitIoClient
37
33
  end
38
34
 
39
35
  def post_impressions_count
40
- @impressions_api.post_count(formatter(@impression_counter.pop_all))
41
- rescue StandardError => error
42
- @config.log_found_exception(__method__.to_s, error)
43
- end
44
-
45
- def formatter(counts)
46
- return if counts.empty?
47
-
48
- formated_counts = {pf: []}
49
-
50
- counts.each do |key, value|
51
- key_splited = key.split('::')
52
-
53
- formated_counts[:pf] << {
54
- f: key_splited[0].to_s, # feature name
55
- m: key_splited[1].to_i, # time frame
56
- rc: value # count
57
- }
58
- end
59
-
60
- formated_counts
36
+ @impressions_sender_adapter.record_impressions_count(@impression_counter.pop_all)
61
37
  rescue StandardError => error
62
38
  @config.log_found_exception(__method__.to_s, error)
63
39
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsSenderAdapter
7
+ extend Forwardable
8
+ def_delegators :@sender, :record_uniques_key, :record_impressions_count
9
+
10
+ def initialize(config, telemetry_api, impressions_api)
11
+ @sender = case config.telemetry_adapter.class.to_s
12
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
13
+ Cache::Senders::RedisImpressionsSender.new(config)
14
+ else
15
+ Cache::Senders::MemoryImpressionsSender.new(config, telemetry_api, impressions_api)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -17,6 +17,14 @@ module SplitIoClient
17
17
  post_telemetry("#{@config.telemetry_service_url}/metrics/usage", stats, 'stats')
18
18
  end
19
19
 
20
+ def record_unique_keys(uniques)
21
+ return if uniques[:mtks].empty?
22
+
23
+ post_telemetry("#{@config.telemetry_service_url}/mtks/ss", uniques, 'mtks')
24
+ rescue StandardError => e
25
+ @config.log_found_exception(__method__.to_s, e)
26
+ end
27
+
20
28
  private
21
29
 
22
30
  def post_telemetry(url, obj, method)
@@ -4,65 +4,75 @@ module SplitIoClient
4
4
  module Engine
5
5
  module Common
6
6
  class ImpressionManager
7
- def initialize(config, impressions_repository, impression_counter, telemetry_runtime_producer)
7
+ def initialize(config,
8
+ impressions_repository,
9
+ impression_counter,
10
+ telemetry_runtime_producer,
11
+ impression_observer,
12
+ unique_keys_tracker,
13
+ impression_router)
8
14
  @config = config
9
15
  @impressions_repository = impressions_repository
10
16
  @impression_counter = impression_counter
11
- @impression_observer = SplitIoClient::Observers::ImpressionObserver.new
17
+ @impression_observer = impression_observer
12
18
  @telemetry_runtime_producer = telemetry_runtime_producer
19
+ @unique_keys_tracker = unique_keys_tracker
20
+ @impression_router = impression_router
13
21
  end
14
22
 
15
- # added param time for test
16
23
  def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
17
24
  impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
18
25
 
19
- impression_data[:pt] = @impression_observer.test_and_set(impression_data) unless redis?
20
-
21
- @impression_counter.inc(split_name, impression_data[:m]) if optimized? && !redis?
26
+ begin
27
+ case @config.impressions_mode
28
+ when :debug # In DEBUG mode we should calculate the pt only.
29
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data)
30
+ when :none # In NONE mode we should track the total amount of evaluations and the unique keys.
31
+ @impression_counter.inc(split_name, impression_data[:m])
32
+ @unique_keys_tracker.track(split_name, matching_key)
33
+ else # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
34
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data)
35
+ @impression_counter.inc(split_name, impression_data[:m])
36
+ end
37
+ rescue StandardError => e
38
+ @config.log_found_exception(__method__.to_s, e)
39
+ end
22
40
 
23
41
  impression(impression_data, params[:attributes])
24
- rescue StandardError => e
25
- @config.log_found_exception(__method__.to_s, e)
26
42
  end
27
43
 
28
44
  def track(impressions)
29
45
  return if impressions.empty?
30
46
 
31
- impression_router.add_bulk(impressions)
32
-
33
- dropped = 0
34
- queued = 0
35
- dedupe = 0
36
-
37
- if optimized? && !redis?
38
- optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
39
-
40
- unless optimized_impressions.empty?
41
- dropped = @impressions_repository.add_bulk(optimized_impressions)
42
- dedupe = impressions.length - optimized_impressions.length
43
- queued = optimized_impressions.length - dropped
47
+ stats = { dropped: 0, queued: 0, dedupe: 0 }
48
+ begin
49
+ case @config.impressions_mode
50
+ when :none
51
+ return
52
+ when :debug
53
+ track_debug_mode(impressions, stats)
54
+ when :optimized
55
+ track_optimized_mode(impressions, stats)
44
56
  end
45
- else
46
- dropped = @impressions_repository.add_bulk(impressions)
47
- queued = impressions.length - dropped
57
+ rescue StandardError => e
58
+ @config.log_found_exception(__method__.to_s, e)
59
+ ensure
60
+ record_stats(stats)
61
+ @impression_router.add_bulk(impressions)
48
62
  end
49
-
50
- record_stats(queued, dropped, dedupe)
51
- rescue StandardError => e
52
- @config.log_found_exception(__method__.to_s, e)
53
63
  end
54
64
 
55
65
  private
56
66
 
57
- def record_stats(queued, dropped, dedupe)
67
+ def record_stats(stats)
58
68
  return if redis?
59
69
 
60
70
  imp_queued = Telemetry::Domain::Constants::IMPRESSIONS_QUEUED
61
71
  imp_dropped = Telemetry::Domain::Constants::IMPRESSIONS_DROPPED
62
72
  imp_dedupe = Telemetry::Domain::Constants::IMPRESSIONS_DEDUPE
63
- @telemetry_runtime_producer.record_impressions_stats(imp_queued, queued) unless queued.zero?
64
- @telemetry_runtime_producer.record_impressions_stats(imp_dropped, dropped) unless dropped.zero?
65
- @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, dedupe) unless dedupe.zero?
73
+ @telemetry_runtime_producer.record_impressions_stats(imp_queued, stats[:queued]) unless stats[:queued].zero?
74
+ @telemetry_runtime_producer.record_impressions_stats(imp_dropped, stats[:dropped]) unless stats[:dropped].zero?
75
+ @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, stats[:dedupe]) unless stats[:dedupe].zero?
66
76
  end
67
77
 
68
78
  # added param time for test
@@ -91,10 +101,6 @@ module SplitIoClient
91
101
  @config.labels_enabled ? label : nil
92
102
  end
93
103
 
94
- def optimized?
95
- @config.impressions_mode == :optimized
96
- end
97
-
98
104
  def should_queue_impression?(impression)
99
105
  impression[:pt].nil? ||
100
106
  (ImpressionCounter.truncate_time_frame(impression[:pt]) != ImpressionCounter.truncate_time_frame(impression[:m]))
@@ -108,10 +114,19 @@ module SplitIoClient
108
114
  @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
109
115
  end
110
116
 
111
- def impression_router
112
- @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
113
- rescue StandardError => error
114
- @config.log_found_exception(__method__.to_s, error)
117
+ def track_debug_mode(impressions, stats)
118
+ stats[:dropped] = @impressions_repository.add_bulk(impressions)
119
+ stats[:queued] = impressions.length - stats[:dropped]
120
+ end
121
+
122
+ def track_optimized_mode(impressions, stats)
123
+ optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
124
+
125
+ return if optimized_impressions.empty?
126
+
127
+ stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
128
+ stats[:dedupe] = impressions.length - optimized_impressions.length
129
+ stats[:queued] = optimized_impressions.length - stats[:dropped]
115
130
  end
116
131
  end
117
132
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ class NoopImpressionCounter
9
+ def inc(split_name, time_frame)
10
+ # no-op
11
+ end
12
+
13
+ def pop_all
14
+ # no-op
15
+ end
16
+
17
+ def make_key(split_name, time_frame)
18
+ # no-op
19
+ end
20
+
21
+ def self.truncate_time_frame(timestamp_ms)
22
+ # no-op
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Impressions
6
+ class NoopUniqueKeysTracker
7
+ def call
8
+ # no-op
9
+ end
10
+
11
+ def track(feature_name, key)
12
+ # no-op
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Impressions
6
+ class UniqueKeysTracker
7
+ INTERVAL_TO_CLEAR_LONG_TERM_CACHE = 86_400 # 24 hours
8
+
9
+ def initialize(config,
10
+ filter_adapter,
11
+ sender_adapter,
12
+ cache)
13
+ @config = config
14
+ @filter_adapter = filter_adapter
15
+ @sender_adapter = sender_adapter
16
+ @cache = cache
17
+ @cache_max_size = config.unique_keys_cache_max_size
18
+ @max_bulk_size = config.unique_keys_bulk_size
19
+ @semaphore = Mutex.new
20
+ end
21
+
22
+ def call
23
+ @config.threads[:unique_keys_sender] = Thread.new { send_bulk_data_thread }
24
+ @config.threads[:clear_filter] = Thread.new { clear_filter_thread }
25
+ end
26
+
27
+ def track(feature_name, key)
28
+ return false if @filter_adapter.contains?(feature_name, key)
29
+
30
+ @filter_adapter.add(feature_name, key)
31
+
32
+ add_or_update(feature_name, key)
33
+
34
+ send_bulk_data if @cache.size >= @cache_max_size
35
+
36
+ true
37
+ rescue StandardError => e
38
+ @config.log_found_exception(__method__.to_s, e)
39
+ false
40
+ end
41
+
42
+ private
43
+
44
+ def send_bulk_data_thread
45
+ @config.logger.info('Starting Unique Keys Tracker.') if @config.debug_enabled
46
+ loop do
47
+ sleep(@config.unique_keys_refresh_rate)
48
+ send_bulk_data
49
+ end
50
+ rescue SplitIoClient::SDKShutdownException
51
+ send_bulk_data
52
+ @config.logger.info('Posting unique keys due to shutdown')
53
+ end
54
+
55
+ def clear_filter_thread
56
+ loop do
57
+ sleep(INTERVAL_TO_CLEAR_LONG_TERM_CACHE)
58
+ @config.logger.debug('Starting task to clean the filter cache.') if @config.debug_enabled
59
+ @filter_adapter.clear
60
+ end
61
+ rescue SplitIoClient::SDKShutdownException
62
+ @filter_adapter.clear
63
+ end
64
+
65
+ def add_or_update(feature_name, key)
66
+ if @cache[feature_name].nil?
67
+ @cache[feature_name] = Set.new([key])
68
+ else
69
+ @cache[feature_name].add(key)
70
+ end
71
+ end
72
+
73
+ def send_bulk_data
74
+ @semaphore.synchronize do
75
+ return if @cache.empty?
76
+
77
+ uniques = @cache.clone
78
+ @cache.clear
79
+
80
+ if uniques.size <= @max_bulk_size
81
+ @sender_adapter.record_uniques_key(uniques)
82
+ return
83
+ end
84
+
85
+ bulks = SplitIoClient::Utilities.split_bulk_to_send(uniques, uniques.size / @max_bulk_size)
86
+
87
+ bulks.each do |b|
88
+ @sender_adapter.record_uniques_key(b)
89
+ end
90
+ end
91
+ rescue StandardError => e
92
+ @config.log_found_exception(__method__.to_s, e)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -52,9 +52,8 @@ module SplitIoClient
52
52
  # @return [boolean] match value for combiner delegates
53
53
  def eval_and(args)
54
54
  # Convert all keys to symbols
55
- if args && args[:attributes]
56
- args[:attributes] = args[:attributes].each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v }
57
- end
55
+ args[:attributes] = args[:attributes].each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v } if args && args[:attributes]
56
+
58
57
  @matchers.all? do |matcher|
59
58
  if match_with_key?(matcher)
60
59
  matcher.match?(value: args[:matching_key])
@@ -30,6 +30,11 @@ module SplitIoClient
30
30
  PhusionPassenger.on_event(:starting_worker_process) { |forked| start_thread if forked } if defined?(PhusionPassenger)
31
31
  end
32
32
 
33
+ def start_consumer
34
+ start_consumer_thread
35
+ PhusionPassenger.on_event(:starting_worker_process) { |forked| start_consumer_thread if forked } if defined?(PhusionPassenger)
36
+ end
37
+
33
38
  private
34
39
 
35
40
  def start_thread
@@ -55,6 +60,14 @@ module SplitIoClient
55
60
  end
56
61
  end
57
62
 
63
+ def start_consumer_thread
64
+ @config.threads[:start_sdk_consumer] = Thread.new do
65
+ @status_manager.ready!
66
+ @telemetry_synchronizer.synchronize_config
67
+ @synchronizer.start_periodic_data_recording
68
+ end
69
+ end
70
+
58
71
  def process_subsystem_ready
59
72
  @synchronizer.stop_periodic_fetch
60
73
  @synchronizer.sync_all
@@ -12,7 +12,6 @@ module SplitIoClient
12
12
 
13
13
  def initialize(
14
14
  repositories,
15
- api_key,
16
15
  config,
17
16
  params
18
17
  )
@@ -20,13 +19,14 @@ module SplitIoClient
20
19
  @segments_repository = repositories[:segments]
21
20
  @impressions_repository = repositories[:impressions]
22
21
  @events_repository = repositories[:events]
23
- @api_key = api_key
24
22
  @config = config
25
23
  @split_fetcher = params[:split_fetcher]
26
24
  @segment_fetcher = params[:segment_fetcher]
27
- @impressions_api = SplitIoClient::Api::Impressions.new(@api_key, @config, params[:telemetry_runtime_producer])
25
+ @impressions_api = params[:impressions_api]
28
26
  @impression_counter = params[:imp_counter]
29
27
  @telemetry_synchronizer = params[:telemetry_synchronizer]
28
+ @impressions_sender_adapter = params[:impressions_sender_adapter]
29
+ @unique_keys_tracker = params[:unique_keys_tracker]
30
30
  end
31
31
 
32
32
  def sync_all(asynchronous = true)
@@ -42,10 +42,14 @@ module SplitIoClient
42
42
  end
43
43
 
44
44
  def start_periodic_data_recording
45
- impressions_sender
46
- events_sender
45
+ unless @config.consumer?
46
+ impressions_sender
47
+ events_sender
48
+ start_telemetry_sync_task
49
+ end
50
+
47
51
  impressions_count_sender
48
- start_telemetry_sync_task
52
+ start_unique_keys_tracker_task
49
53
  end
50
54
 
51
55
  def start_periodic_fetch
@@ -170,7 +174,7 @@ module SplitIoClient
170
174
 
171
175
  # Starts thread which loops constantly and sends impressions to the Split API
172
176
  def impressions_sender
173
- ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
177
+ ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call unless @config.impressions_mode == :none
174
178
  end
175
179
 
176
180
  # Starts thread which loops constantly and sends events to the Split API
@@ -180,13 +184,17 @@ module SplitIoClient
180
184
 
181
185
  # Starts thread which loops constantly and sends impressions count to the Split API
182
186
  def impressions_count_sender
183
- ImpressionsCountSender.new(@config, @impression_counter, @impressions_api).call
187
+ ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call unless @config.impressions_mode == :debug
184
188
  end
185
189
 
186
190
  def start_telemetry_sync_task
187
191
  Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call
188
192
  end
189
193
 
194
+ def start_unique_keys_tracker_task
195
+ @unique_keys_tracker.call
196
+ end
197
+
190
198
  def sync_result(success, remaining_attempts, segment_names = nil)
191
199
  { success: success, remaining_attempts: remaining_attempts, segment_names: segment_names }
192
200
  end
@@ -111,6 +111,12 @@ module SplitIoClient
111
111
  @telemetry_refresh_rate = SplitConfig.init_telemetry_refresh_rate(opts[:telemetry_refresh_rate])
112
112
  @telemetry_service_url = opts[:telemetry_service_url] || SplitConfig.default_telemetry_service_url
113
113
 
114
+ @unique_keys_refresh_rate = SplitConfig.default_unique_keys_refresh_rate(@cache_adapter)
115
+ @unique_keys_cache_max_size = SplitConfig.default_unique_keys_cache_max_size
116
+ @unique_keys_bulk_size = SplitConfig.default_unique_keys_bulk_size(@cache_adapter)
117
+
118
+ @counter_refresh_rate = SplitConfig.default_counter_refresh_rate(@cache_adapter)
119
+
114
120
  @sdk_start_time = Time.now
115
121
 
116
122
  @on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds
@@ -284,6 +290,18 @@ module SplitIoClient
284
290
  attr_accessor :on_demand_fetch_retry_delay_seconds
285
291
  attr_accessor :on_demand_fetch_max_retries
286
292
 
293
+ attr_accessor :unique_keys_refresh_rate
294
+ attr_accessor :unique_keys_cache_max_size
295
+ attr_accessor :unique_keys_bulk_size
296
+
297
+ attr_accessor :counter_refresh_rate
298
+
299
+ def self.default_counter_refresh_rate(adapter)
300
+ return 300 if adapter == :redis # Send bulk impressions count - Refresh rate: 5 min.
301
+
302
+ 1800 # Send bulk impressions count - Refresh rate: 30 min.
303
+ end
304
+
287
305
  def self.default_on_demand_fetch_retry_delay_seconds
288
306
  0.05
289
307
  end
@@ -302,6 +320,8 @@ module SplitIoClient
302
320
  case impressions_mode
303
321
  when :debug
304
322
  return :debug
323
+ when :none
324
+ return :none
305
325
  else
306
326
  @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
307
327
  return :optimized
@@ -468,6 +488,22 @@ module SplitIoClient
468
488
  3600
469
489
  end
470
490
 
491
+ def self.default_unique_keys_refresh_rate(adapter)
492
+ return 300 if adapter == :redis
493
+
494
+ 900
495
+ end
496
+
497
+ def self.default_unique_keys_cache_max_size
498
+ 30000
499
+ end
500
+
501
+ def self.default_unique_keys_bulk_size(adapter)
502
+ return 2000 if adapter == :redis
503
+
504
+ 5000
505
+ end
506
+
471
507
  def self.default_telemetry_service_url
472
508
  'https://telemetry.split.io/api/v1'
473
509
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SplitIoClient
2
4
  class SplitFactory
3
5
  ROOT_PROCESS_ID = Process.pid
@@ -34,8 +36,10 @@ module SplitIoClient
34
36
 
35
37
  build_telemetry_components
36
38
  build_repositories
37
- build_impressions_components
38
39
  build_telemetry_synchronizer
40
+ build_impressions_sender_adapter
41
+ build_unique_keys_tracker
42
+ build_impressions_components
39
43
 
40
44
  @status_manager = Engine::StatusManager.new(@config)
41
45
 
@@ -49,8 +53,10 @@ module SplitIoClient
49
53
  return start_localhost_components if @config.localhost_mode
50
54
 
51
55
  if @config.consumer?
52
- @status_manager.ready!
53
- @telemetry_synchronizer.synchronize_config
56
+ build_synchronizer
57
+ build_sync_manager
58
+
59
+ @sync_manager.start_consumer
54
60
  return
55
61
  end
56
62
 
@@ -59,7 +65,7 @@ module SplitIoClient
59
65
  build_streaming_components
60
66
  build_sync_manager
61
67
 
62
- @sync_manager.start
68
+ @sync_manager.start if @config.valid_mode
63
69
  end
64
70
 
65
71
  def stop!
@@ -121,10 +127,10 @@ module SplitIoClient
121
127
  def validate_api_key
122
128
  if(@api_key.nil?)
123
129
  @config.logger.error('Factory Instantiation: you passed a nil api_key, api_key must be a non-empty String')
124
- @config.valid_mode = false
130
+ @config.valid_mode = false
125
131
  elsif (@api_key.empty?)
126
132
  @config.logger.error('Factory Instantiation: you passed and empty api_key, api_key must be a non-empty String')
127
- @config.valid_mode = false
133
+ @config.valid_mode = false
128
134
  end
129
135
  end
130
136
 
@@ -167,11 +173,13 @@ module SplitIoClient
167
173
  split_fetcher: @split_fetcher,
168
174
  segment_fetcher: @segment_fetcher,
169
175
  imp_counter: @impression_counter,
170
- telemetry_runtime_producer: @runtime_producer,
171
- telemetry_synchronizer: @telemetry_synchronizer
176
+ telemetry_synchronizer: @telemetry_synchronizer,
177
+ impressions_sender_adapter: @impressions_sender_adapter,
178
+ impressions_api: @impressions_api,
179
+ unique_keys_tracker: @unique_keys_tracker
172
180
  }
173
181
 
174
- @synchronizer = Engine::Synchronizer.new(repositories, @api_key, @config, params)
182
+ @synchronizer = Engine::Synchronizer.new(repositories, @config, params)
175
183
  end
176
184
 
177
185
  def build_streaming_components
@@ -198,13 +206,51 @@ module SplitIoClient
198
206
  end
199
207
 
200
208
  def build_telemetry_synchronizer
201
- telemetry_api = Api::TelemetryApi.new(@config, @api_key, @runtime_producer)
202
- @telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, telemetry_api)
209
+ @telemetry_api = Api::TelemetryApi.new(@config, @api_key, @runtime_producer)
210
+ @telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, @telemetry_api)
211
+ end
212
+
213
+ def build_unique_keys_tracker
214
+ if @config.impressions_mode != :none
215
+ @unique_keys_tracker = Engine::Impressions::NoopUniqueKeysTracker.new
216
+ return
217
+ end
218
+
219
+ bf = Cache::Filter::BloomFilter.new(30_000_000)
220
+ filter_adapter = Cache::Filter::FilterAdapter.new(@config, bf)
221
+ cache = Concurrent::Hash.new
222
+ @unique_keys_tracker = Engine::Impressions::UniqueKeysTracker.new(@config, filter_adapter, @impressions_sender_adapter, cache)
223
+ end
224
+
225
+ def build_impressions_observer
226
+ if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized) ||
227
+ (@config.cache_adapter == :memory && @config.impressions_mode == :none)
228
+ @impression_observer = Observers::NoopImpressionObserver.new
229
+ else
230
+ @impression_observer = Observers::ImpressionObserver.new
231
+ end
232
+ end
233
+
234
+ def build_impression_counter
235
+ case @config.impressions_mode
236
+ when :debug
237
+ @impression_counter = Engine::Common::NoopImpressionCounter.new
238
+ else
239
+ @impression_counter = Engine::Common::ImpressionCounter.new
240
+ end
241
+ end
242
+
243
+ def build_impressions_sender_adapter
244
+ @impressions_api = Api::Impressions.new(@api_key, @config, @runtime_producer)
245
+ @impressions_sender_adapter = Cache::Senders::ImpressionsSenderAdapter.new(@config, @telemetry_api, @impressions_api)
203
246
  end
204
247
 
205
248
  def build_impressions_components
206
- @impression_counter = Engine::Common::ImpressionCounter.new
207
- @impressions_manager = Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter, @runtime_producer)
249
+ build_impressions_observer
250
+ build_impression_counter
251
+
252
+ impression_router = ImpressionRouter.new(@config)
253
+ @impressions_manager = Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter, @runtime_producer, @impression_observer, @unique_keys_tracker, impression_router)
208
254
  end
209
255
  end
210
256
  end
@@ -183,8 +183,10 @@ module SplitIoClient
183
183
  case @config.impressions_mode
184
184
  when :optimized
185
185
  0
186
- else
186
+ when :debug
187
187
  1
188
+ else
189
+ 2
188
190
  end
189
191
  end
190
192
  end
@@ -37,5 +37,17 @@ module SplitIoClient
37
37
 
38
38
  interval * random_factor
39
39
  end
40
+
41
+ def split_bulk_to_send(hash, divisions)
42
+ count = 0
43
+
44
+ hash.each_with_object([]) do |key_value, final|
45
+ final[count % divisions] ||= {}
46
+ final[count % divisions][key_value[0]] = key_value[1]
47
+ count += 1
48
+ end
49
+ rescue StandardError
50
+ []
51
+ end
40
52
  end
41
53
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '7.3.4.pre.rc2'
2
+ VERSION = '7.3.5.pre.rc3'
3
3
  end
@@ -12,8 +12,11 @@ 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/filter/bloom_filter'
16
+ require 'splitclient-rb/cache/filter/filter_adapter'
15
17
  require 'splitclient-rb/cache/hashers/impression_hasher'
16
18
  require 'splitclient-rb/cache/observers/impression_observer'
19
+ require 'splitclient-rb/cache/observers/noop_impression_observer'
17
20
  require 'splitclient-rb/cache/repositories/repository'
18
21
  require 'splitclient-rb/cache/repositories/segments_repository'
19
22
  require 'splitclient-rb/cache/repositories/splits_repository'
@@ -28,6 +31,9 @@ require 'splitclient-rb/cache/senders/impressions_sender'
28
31
  require 'splitclient-rb/cache/senders/events_sender'
29
32
  require 'splitclient-rb/cache/senders/impressions_count_sender'
30
33
  require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
34
+ require 'splitclient-rb/cache/senders/impressions_sender_adapter'
35
+ require 'splitclient-rb/cache/senders/impressions_adapter/memory_sender'
36
+ require 'splitclient-rb/cache/senders/impressions_adapter/redis_sender'
31
37
  require 'splitclient-rb/cache/stores/localhost_split_builder'
32
38
  require 'splitclient-rb/cache/stores/localhost_split_store'
33
39
  require 'splitclient-rb/cache/stores/store_utils'
@@ -52,6 +58,7 @@ require 'splitclient-rb/engine/api/events'
52
58
  require 'splitclient-rb/engine/api/telemetry_api'
53
59
  require 'splitclient-rb/engine/common/impressions_counter'
54
60
  require 'splitclient-rb/engine/common/impressions_manager'
61
+ require 'splitclient-rb/engine/common/noop_impressions_counter'
55
62
  require 'splitclient-rb/engine/parser/condition'
56
63
  require 'splitclient-rb/engine/parser/partition'
57
64
  require 'splitclient-rb/engine/parser/evaluator'
@@ -79,6 +86,8 @@ require 'splitclient-rb/engine/matchers/equal_to_boolean_matcher'
79
86
  require 'splitclient-rb/engine/matchers/equal_to_matcher'
80
87
  require 'splitclient-rb/engine/matchers/matches_string_matcher'
81
88
  require 'splitclient-rb/engine/evaluator/splitter'
89
+ require 'splitclient-rb/engine/impressions/noop_unique_keys_tracker'
90
+ require 'splitclient-rb/engine/impressions/unique_keys_tracker'
82
91
  require 'splitclient-rb/engine/metrics/binary_search_latency_tracker'
83
92
  require 'splitclient-rb/engine/models/split'
84
93
  require 'splitclient-rb/engine/models/label'
@@ -50,6 +50,7 @@ Gem::Specification.new do |spec|
50
50
  spec.add_development_dependency 'timecop', '~> 0.9'
51
51
  spec.add_development_dependency 'webmock', '~> 3.14'
52
52
 
53
+ spec.add_runtime_dependency 'bitarray', '~> 1.3'
53
54
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
54
55
  spec.add_runtime_dependency 'faraday', '>= 0.8', '< 2.0'
55
56
  spec.add_runtime_dependency 'json', '>= 1.8', '< 3.0'
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.3.4.pre.rc2
4
+ version: 7.3.5.pre.rc3
5
5
  platform: java
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2022-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: '3.14'
195
+ - !ruby/object:Gem::Dependency
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - "~>"
199
+ - !ruby/object:Gem::Version
200
+ version: '1.3'
201
+ name: bitarray
202
+ prerelease: false
203
+ type: :runtime
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1.3'
195
209
  - !ruby/object:Gem::Dependency
196
210
  requirement: !ruby/object:Gem::Requirement
197
211
  requirements:
@@ -363,6 +377,7 @@ extra_rdoc_files: []
363
377
  files:
364
378
  - ".github/pull_request_template.md"
365
379
  - ".github/workflows/ci.yml"
380
+ - ".github/workflows/update-license-year.yml"
366
381
  - ".gitignore"
367
382
  - ".rubocop.yml"
368
383
  - ".simplecov"
@@ -387,8 +402,11 @@ files:
387
402
  - lib/splitclient-rb/cache/adapters/redis_adapter.rb
388
403
  - lib/splitclient-rb/cache/fetchers/segment_fetcher.rb
389
404
  - lib/splitclient-rb/cache/fetchers/split_fetcher.rb
405
+ - lib/splitclient-rb/cache/filter/bloom_filter.rb
406
+ - lib/splitclient-rb/cache/filter/filter_adapter.rb
390
407
  - lib/splitclient-rb/cache/hashers/impression_hasher.rb
391
408
  - lib/splitclient-rb/cache/observers/impression_observer.rb
409
+ - lib/splitclient-rb/cache/observers/noop_impression_observer.rb
392
410
  - lib/splitclient-rb/cache/repositories/events/memory_repository.rb
393
411
  - lib/splitclient-rb/cache/repositories/events/redis_repository.rb
394
412
  - lib/splitclient-rb/cache/repositories/events_repository.rb
@@ -400,9 +418,12 @@ files:
400
418
  - lib/splitclient-rb/cache/repositories/splits_repository.rb
401
419
  - lib/splitclient-rb/cache/routers/impression_router.rb
402
420
  - lib/splitclient-rb/cache/senders/events_sender.rb
421
+ - lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb
422
+ - lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb
403
423
  - lib/splitclient-rb/cache/senders/impressions_count_sender.rb
404
424
  - lib/splitclient-rb/cache/senders/impressions_formatter.rb
405
425
  - lib/splitclient-rb/cache/senders/impressions_sender.rb
426
+ - lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb
406
427
  - lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb
407
428
  - lib/splitclient-rb/cache/stores/localhost_split_builder.rb
408
429
  - lib/splitclient-rb/cache/stores/localhost_split_store.rb
@@ -421,7 +442,10 @@ files:
421
442
  - lib/splitclient-rb/engine/back_off.rb
422
443
  - lib/splitclient-rb/engine/common/impressions_counter.rb
423
444
  - lib/splitclient-rb/engine/common/impressions_manager.rb
445
+ - lib/splitclient-rb/engine/common/noop_impressions_counter.rb
424
446
  - lib/splitclient-rb/engine/evaluator/splitter.rb
447
+ - lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb
448
+ - lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb
425
449
  - lib/splitclient-rb/engine/matchers/all_keys_matcher.rb
426
450
  - lib/splitclient-rb/engine/matchers/between_matcher.rb
427
451
  - lib/splitclient-rb/engine/matchers/combiners.rb