splitclient-rb 1.0.2.wip → 8.11.1.pre.rc1-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 (210) hide show
  1. checksums.yaml +5 -5
  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 +20 -0
  7. data/.rubocop.yml +76 -0
  8. data/.simplecov +1 -0
  9. data/CHANGES.txt +361 -2
  10. data/CONTRIBUTORS-GUIDE.md +49 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE +3 -36
  13. data/NOTICE.txt +5 -0
  14. data/README.md +59 -127
  15. data/Rakefile +27 -3
  16. data/ext/murmurhash/MurmurHash3.java +302 -0
  17. data/gemfiles/faraday_after_0.13.gemfile +7 -0
  18. data/gemfiles/faraday_before_0.13.gemfile +8 -0
  19. data/lib/murmurhash/base.rb +58 -0
  20. data/lib/murmurhash/murmurhash.jar +0 -0
  21. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  22. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
  23. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  24. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
  25. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
  26. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
  27. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  28. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  29. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  30. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  31. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  32. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  33. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  34. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  35. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +45 -0
  36. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
  37. data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
  38. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  39. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  40. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +56 -0
  41. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
  42. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
  43. data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
  44. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  45. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
  46. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
  47. data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
  48. data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
  49. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  50. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  51. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  52. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +95 -0
  53. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
  54. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  55. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  56. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  57. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  58. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  59. data/lib/splitclient-rb/clients/split_client.rb +530 -0
  60. data/lib/splitclient-rb/constants.rb +16 -0
  61. data/lib/splitclient-rb/engine/api/client.rb +87 -0
  62. data/lib/splitclient-rb/engine/api/events.rb +57 -0
  63. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
  64. data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
  65. data/lib/splitclient-rb/engine/api/segments.rb +78 -0
  66. data/lib/splitclient-rb/engine/api/splits.rb +139 -0
  67. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  68. data/lib/splitclient-rb/engine/auth_api_client.rb +100 -0
  69. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  70. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  71. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  72. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  73. data/lib/{splitclient-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
  74. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  75. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  76. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  77. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  78. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  79. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  80. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  81. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  82. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
  83. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
  84. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  85. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
  86. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
  87. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
  88. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
  89. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
  90. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
  91. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
  92. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
  93. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
  94. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  95. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
  96. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
  97. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  98. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  99. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +43 -0
  100. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  101. data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
  102. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
  103. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
  104. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
  105. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  106. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  107. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  108. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +27 -0
  109. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
  110. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
  111. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  112. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
  113. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  114. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  116. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  117. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  118. data/lib/splitclient-rb/engine/models/label.rb +10 -0
  119. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  120. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  121. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  122. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +16 -0
  123. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  124. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  125. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  126. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  127. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  128. data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
  129. data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
  130. data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
  131. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  132. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  133. data/lib/splitclient-rb/engine/sync_manager.rb +183 -0
  134. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  135. data/lib/splitclient-rb/exceptions.rb +26 -0
  136. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  137. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  138. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  139. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  140. data/lib/splitclient-rb/helpers/util.rb +26 -0
  141. data/lib/splitclient-rb/managers/split_manager.rb +121 -0
  142. data/lib/splitclient-rb/spec.rb +9 -0
  143. data/lib/splitclient-rb/split_config.rb +591 -47
  144. data/lib/splitclient-rb/split_factory.rb +284 -0
  145. data/lib/splitclient-rb/split_factory_builder.rb +7 -0
  146. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  147. data/lib/splitclient-rb/split_logger.rb +23 -0
  148. data/lib/splitclient-rb/sse/event_source/client.rb +264 -0
  149. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  150. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  151. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  152. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  153. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  154. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  155. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  156. data/lib/splitclient-rb/sse/workers/splits_worker.rb +153 -0
  157. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  158. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  159. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  160. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  161. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  162. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  163. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  164. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  165. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  166. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  167. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  168. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  169. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  170. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  171. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  172. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  173. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  174. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  175. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  176. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  177. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  178. data/lib/splitclient-rb/utilitites.rb +49 -0
  179. data/lib/splitclient-rb/validators.rb +361 -0
  180. data/lib/splitclient-rb/version.rb +1 -1
  181. data/lib/splitclient-rb.rb +177 -25
  182. data/sonar-project.properties +6 -0
  183. data/splitclient-rb.gemspec +53 -26
  184. data/tasks/benchmark_get_treatment.rake +31 -17
  185. data/tasks/irb.rake +6 -0
  186. metadata +411 -86
  187. data/NEWS +0 -9
  188. data/lib/splitclient-cache/local_store.rb +0 -45
  189. data/lib/splitclient-engine/impressions/impressions.rb +0 -79
  190. data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
  191. data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
  192. data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
  193. data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
  194. data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
  195. data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
  196. data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
  197. data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
  198. data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
  199. data/lib/splitclient-engine/metrics/metrics.rb +0 -158
  200. data/lib/splitclient-engine/parser/condition.rb +0 -143
  201. data/lib/splitclient-engine/parser/segment.rb +0 -84
  202. data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
  203. data/lib/splitclient-engine/parser/split.rb +0 -68
  204. data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
  205. data/lib/splitclient-engine/parser/split_parser.rb +0 -139
  206. data/lib/splitclient-engine/partitions/treatments.rb +0 -40
  207. data/lib/splitclient-rb/split_client.rb +0 -156
  208. data/lib/splitclient-rb_utilitites.rb +0 -26
  209. data/tasks/console.rake +0 -4
  210. data/tasks/rspec.rake +0 -3
@@ -0,0 +1,296 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class SplitsRepository < Repository
7
+ attr_reader :adapter
8
+ DEFAULT_CONDITIONS_TEMPLATE = [{
9
+ conditionType: "ROLLOUT",
10
+ matcherGroup: {
11
+ combiner: "AND",
12
+ matchers: [
13
+ {
14
+ keySelector: nil,
15
+ matcherType: "ALL_KEYS",
16
+ negate: false,
17
+ userDefinedSegmentMatcherData: nil,
18
+ whitelistMatcherData: nil,
19
+ unaryNumericMatcherData: nil,
20
+ betweenMatcherData: nil,
21
+ dependencyMatcherData: nil,
22
+ booleanMatcherData: nil,
23
+ stringMatcherData: nil
24
+ }]
25
+ },
26
+ partitions: [
27
+ {
28
+ treatment: "control",
29
+ size: 100
30
+ }
31
+ ],
32
+ label: "targeting rule type unsupported by sdk"
33
+ }]
34
+ TILL_PREFIX = '.splits.till'
35
+ SPLIT_PREFIX = '.split.'
36
+ READY_PREFIX = '.splits.ready'
37
+
38
+ def initialize(config, flag_sets_repository, flag_set_filter, internal_events_queue)
39
+ super(config)
40
+ @tt_cache = {}
41
+ @adapter = case @config.cache_adapter.class.to_s
42
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
43
+ SplitIoClient::Cache::Adapters::CacheAdapter.new(@config)
44
+ else
45
+ @config.cache_adapter
46
+ end
47
+ @flag_sets = flag_sets_repository
48
+ @flag_set_filter = flag_set_filter
49
+ @internal_events_queue = internal_events_queue
50
+ initialize_keys
51
+ end
52
+
53
+ def update(to_add, to_delete, new_change_number)
54
+ to_add.each{ |feature_flag| add_feature_flag(feature_flag) }
55
+ to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) }
56
+ set_change_number(new_change_number)
57
+
58
+ if to_add.length > 0 || to_delete.length > 0
59
+ @internal_events_queue.push(
60
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
61
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
62
+ SplitIoClient::Engine::Models::EventsMetadata.new(
63
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
64
+ to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]}
65
+ )
66
+ )
67
+ )
68
+ end
69
+ end
70
+
71
+ def get_split(name)
72
+ split = @adapter.string(namespace_key(".split.#{name}"))
73
+
74
+ JSON.parse(split, symbolize_names: true) if split
75
+ end
76
+
77
+ def splits(filtered_names=nil)
78
+ symbolize = true
79
+ if filtered_names.nil?
80
+ filtered_names = split_names
81
+ symbolize = false
82
+ end
83
+ get_splits(filtered_names, symbolize)
84
+ end
85
+
86
+ def traffic_type_exists(tt_name)
87
+ case @adapter
88
+ when SplitIoClient::Cache::Adapters::CacheAdapter
89
+ tt_count = @adapter.string(namespace_key(".trafficType.#{tt_name}"))
90
+ begin
91
+ !tt_count.nil? && Integer(tt_count, 10) > 0
92
+ rescue StandardError => e
93
+ @config.logger.error("Error while parsing Traffic Type count: #{e.message}")
94
+ false
95
+ end
96
+ else
97
+ @tt_cache.key?(tt_name) && @tt_cache[tt_name] > 0
98
+ end
99
+ end
100
+
101
+ # Return an array of Split Names excluding control keys like splits.till
102
+ def split_names
103
+ @adapter.find_strings_by_prefix(namespace_key(SPLIT_PREFIX))
104
+ .map { |split| split.gsub(namespace_key(SPLIT_PREFIX), '') }
105
+ end
106
+
107
+ def set_change_number(since)
108
+ @adapter.set_string(namespace_key(TILL_PREFIX), since)
109
+ end
110
+
111
+ def get_change_number
112
+ @adapter.string(namespace_key(TILL_PREFIX))
113
+ end
114
+
115
+ def set_segment_names(names)
116
+ return if names.nil? || names.empty?
117
+
118
+ names.each do |name|
119
+ @adapter.add_to_set(namespace_key('.segments.registered'), name)
120
+ end
121
+ end
122
+
123
+ def exists?(name)
124
+ @adapter.exists?(namespace_key(".split.#{name}"))
125
+ end
126
+
127
+ def ready?
128
+ @adapter.string(namespace_key(READY_PREFIX)).to_i != -1
129
+ end
130
+
131
+ def not_ready!
132
+ @adapter.set_string(namespace_key(READY_PREFIX), -1)
133
+ end
134
+
135
+ def ready!
136
+ @adapter.set_string(namespace_key(READY_PREFIX), Time.now.utc.to_i)
137
+ end
138
+
139
+ def clear
140
+ @tt_cache.clear
141
+
142
+ @adapter.clear(namespace_key)
143
+ initialize_keys
144
+ end
145
+
146
+ def kill(change_number, split_name, default_treatment)
147
+ split = get_split(split_name)
148
+
149
+ return if split.nil?
150
+
151
+ split[:killed] = true
152
+ split[:defaultTreatment] = default_treatment
153
+ split[:changeNumber] = change_number
154
+
155
+ @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json)
156
+ @internal_events_queue.push(
157
+ SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
158
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
159
+ SplitIoClient::Engine::Models::EventsMetadata.new(
160
+ SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
161
+ [split_name]
162
+ )
163
+ )
164
+ )
165
+ end
166
+
167
+ def splits_count
168
+ split_names.length
169
+ end
170
+
171
+ def get_feature_flags_by_sets(flag_sets)
172
+ sets_to_fetch = Array.new
173
+ flag_sets.each do |flag_set|
174
+ unless @flag_sets.flag_set_exist?(flag_set)
175
+ @config.logger.warn("Flag set #{flag_set} is not part of the configured flag set list, ignoring it.")
176
+ next
177
+ end
178
+ sets_to_fetch.push(flag_set)
179
+ end
180
+ @flag_sets.get_flag_sets(flag_sets)
181
+ end
182
+
183
+ def is_flag_set_exist(flag_set)
184
+ @flag_sets.flag_set_exist?(flag_set)
185
+ end
186
+
187
+ def flag_set_filter
188
+ @flag_set_filter
189
+ end
190
+
191
+ private
192
+
193
+ def initialize_keys
194
+ unless @config.mode.equal?(:consumer)
195
+ @adapter.set_string(namespace_key(TILL_PREFIX), '-1')
196
+ @adapter.initialize_map(namespace_key('.segments.registered'))
197
+ end
198
+ end
199
+
200
+ def add_feature_flag(split)
201
+ return unless split[:name]
202
+ existing_split = get_split(split[:name])
203
+
204
+ if(!existing_split)
205
+ increase_tt_name_count(split[:trafficTypeName])
206
+ elsif(existing_split[:trafficTypeName] != split[:trafficTypeName])
207
+ increase_tt_name_count(split[:trafficTypeName])
208
+ decrease_tt_name_count(existing_split[:trafficTypeName])
209
+ remove_from_flag_sets(existing_split)
210
+ elsif(existing_split[:sets] != split[:sets])
211
+ remove_from_flag_sets(existing_split)
212
+ end
213
+
214
+ if check_undefined_matcher(split)
215
+ @config.logger.warn("Feature Flag #{split[:name]} has undefined matcher, setting conditions to default template.")
216
+ split[:conditions] = SplitsRepository::DEFAULT_CONDITIONS_TEMPLATE
217
+ end
218
+ if !split[:sets].nil?
219
+ for flag_set in split[:sets]
220
+ if !@flag_sets.flag_set_exist?(flag_set)
221
+ if @flag_set_filter.should_filter?
222
+ next
223
+ end
224
+ @flag_sets.add_flag_set(flag_set)
225
+ end
226
+ @flag_sets.add_feature_flag_to_flag_set(flag_set, split[:name])
227
+ end
228
+ end
229
+
230
+ @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
231
+ end
232
+
233
+ def check_undefined_matcher(split)
234
+ for condition in split[:conditions]
235
+ for matcher in condition[:matcherGroup][:matchers]
236
+ if !SplitIoClient::Condition.instance_methods(false).map(&:to_s).include?("matcher_#{matcher[:matcherType].downcase}")
237
+ @config.logger.error("Detected undefined matcher #{matcher[:matcherType].downcase} in feature flag #{split[:name]}")
238
+ return true
239
+ end
240
+ end
241
+ end
242
+ return false
243
+ end
244
+
245
+ def remove_feature_flag(split)
246
+ decrease_tt_name_count(split[:trafficTypeName])
247
+ remove_from_flag_sets(split)
248
+ @adapter.delete(namespace_key(".split.#{split[:name]}"))
249
+ end
250
+
251
+ def get_splits(names, symbolize_names = true)
252
+ splits = {}
253
+ split_names = names.map { |name| namespace_key(".split.#{name}") }
254
+ splits.merge!(
255
+ @adapter
256
+ .multiple_strings(split_names)
257
+ .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
258
+ )
259
+
260
+ splits.map do |name, data|
261
+ parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
262
+ split_name = symbolize_names ? name.to_sym : name
263
+ [split_name, parsed_data]
264
+ end.to_h
265
+ end
266
+
267
+ def remove_from_flag_sets(feature_flag)
268
+ name = feature_flag[:name]
269
+ flag_sets = get_split(name)[:sets] if exists?(name)
270
+ if !flag_sets.nil?
271
+ for flag_set in flag_sets
272
+ @flag_sets.remove_feature_flag_from_flag_set(flag_set, feature_flag[:name])
273
+ if is_flag_set_exist(flag_set) && @flag_sets.get_flag_sets([flag_set]).length == 0 && !@flag_set_filter.should_filter?
274
+ @flag_sets.remove_flag_set(flag_set)
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ def increase_tt_name_count(tt_name)
281
+ return unless tt_name
282
+
283
+ @tt_cache[tt_name] = 0 unless @tt_cache[tt_name]
284
+ @tt_cache[tt_name] += 1
285
+ end
286
+
287
+ def decrease_tt_name_count(tt_name)
288
+ return unless tt_name
289
+
290
+ @tt_cache[tt_name] -= 1 if @tt_cache[tt_name]
291
+ @tt_cache.delete(tt_name) if @tt_cache[tt_name] == 0
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,60 @@
1
+ module SplitIoClient
2
+ class ImpressionRouter
3
+ attr_reader :router_thread
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ @listener = @config.impression_listener
8
+
9
+ return unless @listener
10
+
11
+ @queue = Queue.new
12
+ router_thread
13
+
14
+ if defined?(PhusionPassenger)
15
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
16
+ router_thread if forked
17
+ end
18
+ end
19
+ end
20
+
21
+ def add_bulk(impressions)
22
+ impressions.each do |impression|
23
+ enqueue(impression)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def enqueue(impression)
30
+ imp = {
31
+ split_name: impression[:i][:f],
32
+ matching_key: impression[:i][:k],
33
+ bucketing_key: impression[:i][:b],
34
+ time: impression[:i][:m],
35
+ treatment: {
36
+ label: impression[:i][:r],
37
+ treatment: impression[:i][:t],
38
+ change_number: impression[:i][:c]
39
+ },
40
+ previous_time: impression[:i][:pt],
41
+ attributes: impression[:attributes]
42
+ }
43
+ @queue.push(imp) if @listener
44
+ rescue StandardError => e
45
+ @config.log_found_exception(__method__.to_s, e)
46
+ end
47
+
48
+ def router_thread
49
+ @config.threads[:impression_router] = Thread.new do
50
+ loop do
51
+ begin
52
+ @listener.log(@queue.pop)
53
+ rescue StandardError => e
54
+ @config.log_found_exception(__method__.to_s, e)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class EventsSender
7
+ def initialize(events_repository, config)
8
+ @events_repository = events_repository
9
+ @config = config
10
+ end
11
+
12
+ def call
13
+ events_thread
14
+ end
15
+
16
+ private
17
+
18
+ def events_thread
19
+ @config.threads[:events_sender] = Thread.new do
20
+ begin
21
+ @config.logger.info('Starting events service')
22
+
23
+ loop do
24
+ post_events
25
+
26
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.events_push_rate))
27
+ end
28
+ rescue SplitIoClient::SDKShutdownException
29
+ post_events
30
+
31
+ @config.logger.info('Posting events due to shutdown')
32
+ end
33
+ end
34
+ end
35
+
36
+ def post_events
37
+ @events_repository.post_events
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -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 = { keys: [] }
35
+ uniques.each do |key, value|
36
+ to_return[:keys] << {
37
+ f: key,
38
+ ks: value.to_a
39
+ }
40
+ end
41
+
42
+ to_return
43
+ rescue StandardError => e
44
+ @config.log_found_exception(__method__.to_s, e)
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 => e
65
+ @config.log_found_exception(__method__.to_s, e)
66
+ nil
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,69 @@
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
+ return if uniques.nil? || uniques.empty?
16
+
17
+ size = @adapter.add_to_queue(unique_keys_key, uniques_formatter(uniques))
18
+
19
+ @adapter.expire(unique_keys_key, EXPIRE_SECONDS) if uniques.length == size
20
+ rescue StandardError => e
21
+ @config.log_found_exception(__method__.to_s, e)
22
+ end
23
+
24
+ def record_impressions_count(impressions_count)
25
+ return if impressions_count.nil? || impressions_count.empty?
26
+
27
+ result = @adapter.redis.pipelined do |pipeline|
28
+ impressions_count.each do |key, value|
29
+ pipeline.hincrby(impressions_count_key, key, value)
30
+ end
31
+ end
32
+
33
+ expire_impressions_count_key(impressions_count, result)
34
+ rescue StandardError => e
35
+ @config.log_found_exception(__method__.to_s, e)
36
+ end
37
+
38
+ private
39
+
40
+ def expire_impressions_count_key(impressions_count, pipeline_result)
41
+ total_count = impressions_count.sum { |_, value| value }
42
+ hlen = pipeline_result.last
43
+
44
+ @adapter.expire(impressions_count_key, EXPIRE_SECONDS) if impressions_count.size == hlen && (pipeline_result.sum - hlen) == total_count
45
+ end
46
+
47
+ def uniques_formatter(uniques)
48
+ to_return = []
49
+ uniques.each do |key, value|
50
+ to_return << {
51
+ f: key,
52
+ ks: value.to_a
53
+ }.to_json
54
+ end
55
+
56
+ to_return
57
+ end
58
+
59
+ def impressions_count_key
60
+ "#{@config.redis_namespace}.impressions.count"
61
+ end
62
+
63
+ def unique_keys_key
64
+ "#{@config.redis_namespace}.uniquekeys"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsCountSender
7
+ def initialize(config, impression_counter, impressions_sender_adapter)
8
+ @config = config
9
+ @impression_counter = impression_counter
10
+ @impressions_sender_adapter = impressions_sender_adapter
11
+ end
12
+
13
+ def call
14
+ impressions_count_thread
15
+ end
16
+
17
+ private
18
+
19
+ def impressions_count_thread
20
+ @config.threads[:impressions_count_sender] = Thread.new do
21
+ begin
22
+ @config.logger.info('Starting impressions count service')
23
+ loop do
24
+ sleep(@config.counter_refresh_rate)
25
+ post_impressions_count
26
+ end
27
+ rescue SplitIoClient::SDKShutdownException
28
+ post_impressions_count
29
+
30
+ @config.logger.info('Posting impressions count due to shutdown')
31
+ end
32
+ end
33
+ end
34
+
35
+ def post_impressions_count
36
+ @impressions_sender_adapter.record_impressions_count(@impression_counter.pop_all)
37
+ rescue StandardError => e
38
+ @config.log_found_exception(__method__.to_s, e)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Senders
6
+ class ImpressionsFormatter
7
+ def initialize(impressions_repository)
8
+ @impressions_repository = impressions_repository
9
+ end
10
+
11
+ def call(fetch_all_impressions, raw_impressions = nil)
12
+ impressions = raw_impressions || (fetch_all_impressions ? @impressions_repository.clear : @impressions_repository.batch)
13
+
14
+ filtered_impressions = filter_impressions(impressions)
15
+
16
+ return [] if impressions.empty? || filtered_impressions.empty?
17
+
18
+ formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
19
+ feature_impressions = feature_impressions(filtered_impressions, feature)
20
+ current_impressions = current_impressions(feature_impressions)
21
+ memo << {
22
+ f: feature.to_sym,
23
+ i: current_impressions
24
+ }
25
+ end
26
+
27
+ formatted_impressions
28
+ end
29
+
30
+ private
31
+
32
+ def feature_impressions(filtered_impressions, feature)
33
+ filtered_impressions.select do |impression|
34
+ impression[:i][:f] == feature
35
+ end
36
+ end
37
+
38
+ def current_impressions(feature_impressions)
39
+ feature_impressions.map do |impression|
40
+ if impression[:i][:properties].nil?
41
+ impression = {
42
+ k: impression[:i][:k],
43
+ t: impression[:i][:t],
44
+ m: impression[:i][:m],
45
+ b: impression[:i][:b],
46
+ r: impression[:i][:r],
47
+ c: impression[:i][:c],
48
+ pt: impression[:i][:pt]
49
+ }
50
+ else
51
+ impression = {
52
+ k: impression[:i][:k],
53
+ t: impression[:i][:t],
54
+ m: impression[:i][:m],
55
+ b: impression[:i][:b],
56
+ r: impression[:i][:r],
57
+ c: impression[:i][:c],
58
+ pt: impression[:i][:pt],
59
+ properties: impression[:i][:properties].to_json.to_s
60
+ }
61
+ end
62
+ end
63
+ end
64
+
65
+ def unique_features(impressions)
66
+ impressions.map { |impression| impression[:i][:f] }.uniq
67
+ end
68
+
69
+ # Filter seen impressions by impression_hash
70
+ def filter_impressions(unfiltered_impressions)
71
+ impressions_seen = []
72
+
73
+ unfiltered_impressions.each_with_object([]) do |impression, impressions|
74
+ impression_hash = impression_hash(impression)
75
+
76
+ next if impressions_seen.include?(impression_hash)
77
+
78
+ impressions_seen << impression_hash
79
+ impressions << impression
80
+ end
81
+ end
82
+
83
+ def impression_hash(impression)
84
+ "#{impression[:i][:f]}:" \
85
+ "#{impression[:i][:k]}:" \
86
+ "#{impression[:i][:b]}:" \
87
+ "#{impression[:i][:c]}:" \
88
+ "#{impression[:i][:t]}:" \
89
+ "#{impression[:i][:pt]}" \
90
+ "#{impression[:i][:properties]}" \
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end