splitclient-rb 6.3.0 → 8.11.0

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/pull_request_template.md +9 -0
  4. data/.github/workflows/ci.yml +90 -0
  5. data/.github/workflows/update-license-year.yml +45 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +46 -3
  8. data/CHANGES.txt +158 -11
  9. data/CONTRIBUTORS-GUIDE.md +49 -0
  10. data/LICENSE +169 -13
  11. data/NOTICE.txt +5 -0
  12. data/README.md +67 -27
  13. data/Rakefile +1 -8
  14. data/ext/murmurhash/3_x64_128.c +117 -0
  15. data/ext/murmurhash/murmurhash.c +5 -1
  16. data/lib/murmurhash/murmurhash.jar +0 -0
  17. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +3 -3
  18. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
  19. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +7 -0
  20. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +12 -4
  21. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  22. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  23. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  24. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  25. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  26. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  27. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  28. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  29. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +26 -14
  30. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +9 -14
  31. data/lib/splitclient-rb/cache/repositories/events_repository.rb +31 -10
  32. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  33. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  34. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +22 -23
  35. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +15 -22
  36. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +6 -31
  37. data/lib/splitclient-rb/cache/repositories/repository.rb +6 -5
  38. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  39. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +46 -6
  40. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +232 -43
  41. data/lib/splitclient-rb/cache/routers/impression_router.rb +24 -22
  42. data/lib/splitclient-rb/cache/senders/events_sender.rb +12 -29
  43. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  44. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  45. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  46. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +27 -13
  47. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +11 -25
  48. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  49. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  50. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  51. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  52. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  53. data/lib/splitclient-rb/clients/split_client.rb +385 -138
  54. data/lib/splitclient-rb/constants.rb +16 -0
  55. data/lib/splitclient-rb/engine/api/client.rb +38 -43
  56. data/lib/splitclient-rb/engine/api/events.rb +19 -11
  57. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
  58. data/lib/splitclient-rb/engine/api/impressions.rb +49 -14
  59. data/lib/splitclient-rb/engine/api/segments.rb +31 -24
  60. data/lib/splitclient-rb/engine/api/splits.rb +108 -33
  61. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  62. data/lib/splitclient-rb/engine/auth_api_client.rb +96 -0
  63. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  64. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  65. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  66. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  67. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  68. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  69. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  70. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  71. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  72. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  73. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  74. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  75. data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +1 -1
  76. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +7 -5
  77. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  78. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +10 -8
  79. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +2 -6
  80. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +1 -5
  81. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +7 -5
  82. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +6 -5
  83. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +5 -4
  84. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +3 -2
  85. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +6 -4
  86. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  87. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +1 -5
  88. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +6 -4
  89. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  90. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  91. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +6 -4
  92. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  93. data/lib/splitclient-rb/engine/matchers/matcher.rb +22 -0
  94. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +3 -2
  95. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +3 -2
  96. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +2 -6
  97. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  98. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  99. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  100. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +2 -1
  101. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +4 -3
  102. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +3 -2
  103. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +7 -5
  104. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +3 -65
  105. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  106. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  107. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  108. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  109. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  110. data/lib/splitclient-rb/engine/models/label.rb +3 -0
  111. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  112. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  113. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  114. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  116. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  117. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  118. data/lib/splitclient-rb/engine/parser/condition.rb +81 -20
  119. data/lib/splitclient-rb/engine/parser/evaluator.rb +40 -51
  120. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  121. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  122. data/lib/splitclient-rb/engine/sync_manager.rb +180 -0
  123. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  124. data/lib/splitclient-rb/exceptions.rb +20 -1
  125. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  126. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  127. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  128. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  129. data/lib/splitclient-rb/helpers/util.rb +26 -0
  130. data/lib/splitclient-rb/managers/split_manager.rb +58 -20
  131. data/lib/splitclient-rb/spec.rb +9 -0
  132. data/lib/splitclient-rb/split_config.rb +336 -54
  133. data/lib/splitclient-rb/split_factory.rb +219 -33
  134. data/lib/splitclient-rb/split_factory_builder.rb +1 -22
  135. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  136. data/lib/splitclient-rb/split_logger.rb +9 -10
  137. data/lib/splitclient-rb/sse/event_source/client.rb +263 -0
  138. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  139. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  140. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  141. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  142. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  143. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  144. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  145. data/lib/splitclient-rb/sse/workers/splits_worker.rb +149 -0
  146. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  147. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  148. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  149. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  150. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  151. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  152. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  153. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  154. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  155. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  156. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  157. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  158. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  159. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  160. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  161. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  162. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  163. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  164. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  165. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  166. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  167. data/lib/splitclient-rb/utilitites.rb +8 -0
  168. data/lib/splitclient-rb/validators.rb +142 -38
  169. data/lib/splitclient-rb/version.rb +1 -1
  170. data/lib/splitclient-rb.rb +101 -16
  171. data/sonar-project.properties +6 -0
  172. data/splitclient-rb.gemspec +28 -23
  173. metadata +262 -82
  174. data/.travis.yml +0 -11
  175. data/Appraisals +0 -10
  176. data/Detailed-README.md +0 -588
  177. data/NEWS +0 -141
  178. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +0 -127
  179. data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +0 -96
  180. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +0 -21
  181. data/lib/splitclient-rb/cache/senders/metrics_sender.rb +0 -56
  182. data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +0 -46
  183. data/lib/splitclient-rb/cache/stores/segment_store.rb +0 -81
  184. data/lib/splitclient-rb/cache/stores/split_store.rb +0 -102
  185. data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -183
  186. data/lib/splitclient-rb/engine/api/faraday_adapter/patched_net_http_persistent.rb +0 -46
  187. data/lib/splitclient-rb/engine/api/metrics.rb +0 -60
  188. data/lib/splitclient-rb/engine/metrics/metrics.rb +0 -80
  189. data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -81
  190. data/lib/splitclient-rb/localhost_split_factory.rb +0 -13
  191. data/lib/splitclient-rb/localhost_utils.rb +0 -59
  192. data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
@@ -0,0 +1,70 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Fetchers
4
+ class SplitFetcher
5
+ attr_reader :splits_repository, :rule_based_segments_repository
6
+
7
+ def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer)
8
+ @splits_repository = splits_repository
9
+ @rule_based_segments_repository = rule_based_segments_repository
10
+ @api_key = api_key
11
+ @config = config
12
+ @semaphore = Mutex.new
13
+ @telemetry_runtime_producer = telemetry_runtime_producer
14
+ end
15
+
16
+ def call
17
+ if ENV['SPLITCLIENT_ENV'] == 'test'
18
+ fetch_splits
19
+ return
20
+ end
21
+
22
+ splits_thread
23
+ end
24
+
25
+ def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
26
+ @semaphore.synchronize do
27
+ data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options)
28
+ SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage)
29
+ SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config)
30
+ @splits_repository.set_segment_names(data[:segment_names])
31
+ @rule_based_segments_repository.set_segment_names(data[:segment_names])
32
+ @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
33
+
34
+ { segment_names: data[:segment_names], success: true }
35
+ end
36
+ rescue StandardError => e
37
+ @config.log_found_exception(__method__.to_s, e)
38
+ { segment_names: [], success: false }
39
+ end
40
+
41
+ def stop_splits_thread
42
+ SplitIoClient::Helpers::ThreadHelper.stop(:split_fetcher, @config)
43
+ end
44
+
45
+ private
46
+
47
+ def splits_thread
48
+ @config.threads[:split_fetcher] = Thread.new do
49
+ @config.logger.info('Starting feature flags fetcher service') if @config.debug_enabled
50
+ loop do
51
+ fetch_splits
52
+
53
+ sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.features_refresh_rate)
54
+ @config.logger.debug("Feature flags fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
55
+ sleep(sleep_for)
56
+ end
57
+ end
58
+ end
59
+
60
+ def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil })
61
+ splits_api.since(since, since_rbs, fetch_options)
62
+ end
63
+
64
+ def splits_api
65
+ @splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @config, @telemetry_runtime_producer)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,67 @@
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
+ reset_filter
13
+ @k = best_k(capacity)
14
+ end
15
+
16
+ def add(string)
17
+ return false if contains?(string)
18
+
19
+ positions = hashes(string)
20
+ positions.each { |position| @ba[position] = 1 }
21
+
22
+ true
23
+ end
24
+
25
+ def contains?(string)
26
+ !hashes(string).any? { |ea| @ba[ea] == 0 }
27
+ end
28
+
29
+ def clear
30
+ @ba = nil
31
+ reset_filter
32
+ end
33
+
34
+ private
35
+
36
+ def reset_filter
37
+ @ba = BitArray.new(@m.round)
38
+ end
39
+
40
+ # m is the required number of bits in the array
41
+ def best_m(capacity, false_positive_probability)
42
+ -(capacity * Math.log(false_positive_probability)) / (Math.log(2) ** 2)
43
+ end
44
+
45
+ # k is the number of hash functions that minimizes the probability of false positives
46
+ def best_k(capacity)
47
+ (Math.log(2) * (@ba.size / capacity)).round
48
+ end
49
+
50
+ def hashes(data)
51
+ m = @ba.size
52
+ h = Digest::MD5.hexdigest(data.to_s).to_i(16)
53
+ x = h % m
54
+ h /= m
55
+ y = h % m
56
+ h /= m
57
+ z = h % m
58
+ [x] + 1.upto(@k - 1).collect do |i|
59
+ x = (x + y) % m
60
+ y = (y + z) % m
61
+ x
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ 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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Filter
8
+ class FlagSetsFilter
9
+ def initialize(flag_sets = [])
10
+ @flag_sets = Set.new(flag_sets)
11
+ @should_filter = @flag_sets.any?
12
+ end
13
+
14
+ def should_filter?
15
+ @should_filter
16
+ end
17
+
18
+ def flag_set_exist?(flag_set)
19
+ return true unless @should_filter
20
+
21
+ if not flag_set.is_a?(String) or flag_set.empty?
22
+ return false
23
+ end
24
+
25
+ @flag_sets.intersection([flag_set]).any?
26
+ end
27
+
28
+ def intersect?(flag_sets)
29
+ return true unless @should_filter
30
+
31
+ if not flag_sets.is_a?(Array) or flag_sets.empty?
32
+ return false
33
+ end
34
+
35
+ @flag_sets.intersection(Set.new(flag_sets)).any?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -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
@@ -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
+
@@ -3,29 +3,41 @@ module SplitIoClient
3
3
  module Repositories
4
4
  module Events
5
5
  class MemoryRepository < EventsRepository
6
- EVENTS_SLICE = 100
6
+ EVENTS_MAX_SIZE_BYTES = 5242880
7
7
 
8
- def initialize(adapter)
9
- @adapter = adapter
8
+ def initialize(config, telemetry_runtime_producer)
9
+ @config = config
10
+ @adapter = @config.events_adapter
11
+ @size = 0
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
10
13
  end
11
14
 
12
- def add(key, traffic_type, event_type, time, value)
13
- @adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value))
14
- rescue ThreadError # queue is full
15
- if SplitIoClient.configuration.debug_enabled
16
- SplitIoClient.configuration.logger.warn("Dropping events. Current size is #{SplitIoClient.configuration.events_queue_size}. " \
17
- "Consider increasing events_queue_size")
18
- end
19
- @adapter.clear
20
- end
15
+ def add(key, traffic_type, event_type, time, value, properties, event_size)
16
+ @adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value, properties))
17
+ @size += event_size
21
18
 
22
- def batch
23
- @adapter.get_batch(EVENTS_SLICE)
19
+ post_events if @size >= EVENTS_MAX_SIZE_BYTES || @adapter.length == @config.events_queue_size
20
+
21
+ @telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_QUEUED, 1)
22
+ rescue StandardError => e
23
+ @config.log_found_exception(__method__.to_s, e)
24
+ @telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_DROPPED, 1)
24
25
  end
25
26
 
26
27
  def clear
28
+ @size = 0
27
29
  @adapter.clear
28
30
  end
31
+
32
+ def empty?
33
+ @adapter.empty?
34
+ end
35
+
36
+ def batch
37
+ return [] if @config.events_queue_size.zero?
38
+
39
+ @adapter.get_batch(@config.events_queue_size)
40
+ end
29
41
  end
30
42
  end
31
43
  end
@@ -3,36 +3,31 @@ module SplitIoClient
3
3
  module Repositories
4
4
  module Events
5
5
  class RedisRepository < EventsRepository
6
- EVENTS_SLICE = 100
7
6
 
8
- def initialize(adapter)
9
- @adapter = adapter
7
+ def initialize(config)
8
+ @config = config
9
+ @adapter = @config.events_adapter
10
10
  end
11
11
 
12
- def add(key, traffic_type, event_type, time, value)
12
+ def add(key, traffic_type, event_type, time, value, properties, size)
13
13
  @adapter.add_to_queue(
14
14
  namespace_key('.events'),
15
- { m: metadata, e: event(key, traffic_type, event_type, time, value) }.to_json,
15
+ { m: metadata, e: event(key, traffic_type, event_type, time, value, properties) }.to_json
16
16
  )
17
17
  end
18
18
 
19
- def get_events(number_of_events = 0)
20
- @adapter.get_from_queue(namespace_key('.events'), number_of_events).map do |e|
19
+ def clear
20
+ @adapter.get_from_queue(namespace_key('.events'), 0).map do |e|
21
21
  JSON.parse(e, symbolize_names: true)
22
22
  end
23
23
  rescue StandardError => e
24
- SplitIoClient.configuration.logger.error("Exception while clearing events cache: #{e}")
24
+ @config.logger.error("Exception while clearing events cache: #{e}")
25
25
  []
26
26
  end
27
27
 
28
28
  def batch
29
- get_events(EVENTS_SLICE)
29
+ clear()
30
30
  end
31
-
32
- def clear
33
- get_events
34
- end
35
-
36
31
  end
37
32
  end
38
33
  end
@@ -4,36 +4,57 @@ module SplitIoClient
4
4
  # Repository which forwards events interface to the selected adapter
5
5
  class EventsRepository < Repository
6
6
  extend Forwardable
7
- def_delegators :@adapter, :add, :clear, :batch
7
+ def_delegators :@repository, :add, :clear, :batch
8
8
 
9
- def initialize(adapter)
10
- @adapter = case adapter.class.to_s
9
+ def initialize(config, api_key, telemetry_runtime_producer)
10
+ super(config)
11
+ @repository = case @config.events_adapter.class.to_s
11
12
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
12
- Repositories::Events::MemoryRepository.new(adapter)
13
+ Repositories::Events::MemoryRepository.new(@config, telemetry_runtime_producer)
13
14
  when 'SplitIoClient::Cache::Adapters::RedisAdapter'
14
- Repositories::Events::RedisRepository.new(adapter)
15
+ Repositories::Events::RedisRepository.new(@config)
15
16
  end
17
+
18
+ @api_key = api_key
19
+ @telemetry_runtime_producer = telemetry_runtime_producer
20
+ end
21
+
22
+ def post_events
23
+ events_api.post(self.clear)
24
+ rescue StandardError => e
25
+ @config.log_found_exception(__method__.to_s, e)
16
26
  end
17
27
 
28
+ def empty?
29
+ @repository.empty?
30
+ end
31
+
18
32
  protected
19
33
 
20
34
  def metadata
21
35
  {
22
- s: "#{SplitIoClient.configuration.language}-#{SplitIoClient.configuration.version}",
23
- i: SplitIoClient.configuration.machine_ip,
24
- n: SplitIoClient.configuration.machine_name
36
+ s: "#{@config.language}-#{@config.version}",
37
+ i: @config.machine_ip,
38
+ n: @config.machine_name
25
39
  }
26
40
  end
27
41
 
28
- def event(key, traffic_type, event_type, time, value)
42
+ def event(key, traffic_type, event_type, time, value, properties)
29
43
  {
30
44
  key: key,
31
45
  trafficTypeName: traffic_type,
32
46
  eventTypeId: event_type,
33
47
  value: value,
34
- timestamp: time
48
+ timestamp: time,
49
+ properties: properties
35
50
  }.reject { |_, v| v.nil? }
36
51
  end
52
+
53
+ private
54
+
55
+ def events_api
56
+ @events_api ||= SplitIoClient::Api::Events.new(@api_key, @config, @telemetry_runtime_producer)
57
+ end
37
58
  end
38
59
  end
39
60
  end
@@ -0,0 +1,40 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class MemoryFlagSetsRepository
7
+ def initialize(flag_sets = [])
8
+ @sets_feature_flag_map = {}
9
+ flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] }
10
+ end
11
+
12
+ def flag_set_exist?(flag_set)
13
+ @sets_feature_flag_map.key?(flag_set)
14
+ end
15
+
16
+ def get_flag_sets(flag_sets)
17
+ to_return = Array.new
18
+ flag_sets.each { |flag_set| to_return.concat(@sets_feature_flag_map[flag_set].to_a)}
19
+ to_return.uniq
20
+ end
21
+
22
+ def add_flag_set(flag_set)
23
+ @sets_feature_flag_map[flag_set] = Set[] if !flag_set_exist?(flag_set)
24
+ end
25
+
26
+ def remove_flag_set(flag_set)
27
+ @sets_feature_flag_map.delete(flag_set) if flag_set_exist?(flag_set)
28
+ end
29
+
30
+ def add_feature_flag_to_flag_set(flag_set, feature_flag)
31
+ @sets_feature_flag_map[flag_set].add(feature_flag) if flag_set_exist?(flag_set)
32
+ end
33
+
34
+ def remove_feature_flag_from_flag_set(flag_set, feature_flag)
35
+ @sets_feature_flag_map[flag_set].delete(feature_flag) if flag_set_exist?(flag_set)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class RedisFlagSetsRepository < Repository
7
+
8
+ def initialize(config)
9
+ super(config)
10
+ @adapter = SplitIoClient::Cache::Adapters::RedisAdapter.new(@config.redis_url)
11
+ end
12
+
13
+ def flag_set_exist?(flag_set)
14
+ @adapter.exists?(namespace_key(".flagSet.#{flag_set}"))
15
+ end
16
+
17
+ def get_flag_sets(flag_sets)
18
+ result = @adapter.redis.pipelined do |pipeline|
19
+ flag_sets.each do |flag_set|
20
+ pipeline.smembers(namespace_key(".flagSet.#{flag_set}"))
21
+ end
22
+ end
23
+ to_return = Array.new
24
+ result.each do |flag_set|
25
+ flag_set.each { |feature_flag_name| to_return.push(feature_flag_name.to_s)}
26
+ end
27
+ to_return.uniq
28
+ end
29
+
30
+ def add_flag_set(flag_set)
31
+ # not implemented
32
+ end
33
+
34
+ def remove_flag_set(flag_set)
35
+ # not implemented
36
+ end
37
+
38
+ def add_feature_flag_to_flag_set(flag_set, feature_flag)
39
+ # not implemented
40
+ end
41
+
42
+ def remove_feature_flag_from_flag_set(flag_set, feature_flag)
43
+ # not implemented
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -5,46 +5,45 @@ module SplitIoClient
5
5
  module Repositories
6
6
  module Impressions
7
7
  class MemoryRepository < ImpressionsRepository
8
- def initialize(adapter)
9
- @adapter = adapter
8
+ def initialize(config)
9
+ @config = config
10
+ @adapter = @config.impressions_adapter
10
11
  end
11
12
 
12
- # Store impression data in the selected adapter
13
- def add(matching_key, bucketing_key, split_name, treatment, time)
14
- @adapter.add_to_queue(
15
- m: metadata,
16
- i: impression_data(
17
- matching_key,
18
- bucketing_key,
19
- split_name,
20
- treatment,
21
- time
22
- )
23
- )
13
+ def add_bulk(impressions)
14
+ return 0 if impressions.nil?
15
+
16
+ count = 0
17
+ impressions.each do |impression|
18
+ @adapter.add_to_queue(impression)
19
+ count += 1
20
+ end
21
+
22
+ 0
24
23
  rescue ThreadError # queue is full
25
24
  if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
26
- SplitIoClient.configuration.logger.warn("Dropping impressions. Current size is \
27
- #{SplitIoClient.configuration.impressions_queue_size}. " \
25
+ @config.logger.warn("Dropping impressions. Current size is \
26
+ #{@config.impressions_queue_size}. " \
28
27
  'Consider increasing impressions_queue_size')
29
28
  end
30
- end
31
29
 
32
- def add_bulk(key, bucketing_key, treatments, time)
33
- treatments.each do |split_name, treatment|
34
- add(key, bucketing_key, split_name, treatment, time)
35
- end
30
+ impressions.length - count
36
31
  end
37
32
 
38
33
  def batch
39
- return [] if SplitIoClient.configuration.impressions_bulk_size.zero?
34
+ return [] if @config.impressions_bulk_size.zero?
40
35
 
41
- @adapter.get_batch(SplitIoClient.configuration.impressions_bulk_size)
36
+ @adapter.get_batch(@config.impressions_bulk_size)
42
37
  end
43
38
 
44
39
  def clear
45
40
  @adapter.clear
46
41
  end
47
42
 
43
+ def empty?
44
+ @adapter.empty?
45
+ end
46
+
48
47
  private
49
48
 
50
49
  def random_sampler
@@ -7,32 +7,25 @@ module SplitIoClient
7
7
  class RedisRepository < ImpressionsRepository
8
8
  EXPIRE_SECONDS = 3600
9
9
 
10
- def initialize(adapter)
11
- @adapter = adapter
10
+ def initialize(config)
11
+ @config = config
12
+ @adapter = @config.impressions_adapter
12
13
  end
13
14
 
14
- def add(matching_key, bucketing_key, split_name, treatment, time)
15
- add_bulk(matching_key, bucketing_key, { split_name => treatment }, time)
16
- end
17
-
18
- def add_bulk(matching_key, bucketing_key, treatments, time)
19
- impressions = treatments.map do |split_name, treatment|
20
- {
21
- m: metadata,
22
- i: impression_data(
23
- matching_key,
24
- bucketing_key,
25
- split_name,
26
- treatment,
27
- time
28
- )
29
- }.to_json
15
+ def add_bulk(impressions)
16
+ impressions_json = impressions.map do |impression|
17
+ impression[:i][:properties] = impression[:i][:properties].to_json.to_s unless impression[:i][:properties].nil?
18
+ impression.to_json
30
19
  end
31
20
 
32
- impressions_list_size = @adapter.add_to_queue(key, impressions)
21
+ impressions_list_size = @adapter.add_to_queue(key, impressions_json)
33
22
 
34
23
  # Synchronizer might not be running
35
- @adapter.expire(key, EXPIRE_SECONDS) if impressions.size == impressions_list_size
24
+ @adapter.expire(key, EXPIRE_SECONDS) if impressions_json.size == impressions_list_size
25
+ 0
26
+ rescue StandardError => e
27
+ @config.logger.error("Exception while add_bulk: #{e}")
28
+ 0
36
29
  end
37
30
 
38
31
  def get_impressions(number_of_impressions = 0)
@@ -42,12 +35,12 @@ module SplitIoClient
42
35
  impression
43
36
  end
44
37
  rescue StandardError => e
45
- SplitIoClient.configuration.logger.error("Exception while clearing impressions cache: #{e}")
38
+ @config.logger.error("Exception while clearing impressions cache: #{e}")
46
39
  []
47
40
  end
48
41
 
49
42
  def batch
50
- get_impressions(SplitIoClient.configuration.impressions_bulk_size)
43
+ get_impressions(@config.impressions_bulk_size)
51
44
  end
52
45
 
53
46
  def clear