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,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'cgi'
5
+
6
+ module SplitIoClient
7
+ module Engine
8
+ class AuthApiClient
9
+ def initialize(config, telemetry_runtime_producer)
10
+ @config = config
11
+ @api_client = SplitIoClient::Api::Client.new(@config)
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
13
+ end
14
+
15
+ def authenticate(api_key)
16
+ start = Time.now
17
+ response = @api_client.get_api("#{@config.auth_service_url}?s=#{SplitIoClient::Spec::FeatureFlags::SPEC_VERSION}", api_key)
18
+
19
+ return process_success(response, start) if response.success?
20
+
21
+ return process_error(response) if response.status >= 400 && response.status < 500
22
+
23
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i)
24
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled
25
+ { push_enabled: false, retry: true }
26
+ rescue StandardError => e
27
+ @config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled
28
+ { push_enabled: false, retry: false }
29
+ end
30
+
31
+ private
32
+
33
+ def expiration(token_decoded)
34
+ exp = token_decoded[0]['exp']
35
+ issued_at = token_decoded[0]['iat']
36
+
37
+ exp - issued_at - SplitIoClient::Constants::EXPIRATION_RATE
38
+ end
39
+
40
+ def channels(token_decoded)
41
+ capability = token_decoded[0]['x-ably-capability']
42
+ channels_hash = JSON.parse(capability)
43
+ channels_string = channels_hash.keys.join(',')
44
+ channels_string = control_channels(channels_string)
45
+ @config.logger.debug("Channels #{channels_string}") if @config.debug_enabled
46
+ CGI.escape(channels_string)
47
+ end
48
+
49
+ def decode_token(token)
50
+ JWT.decode token, nil, false
51
+ end
52
+
53
+ def process_error(response)
54
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled
55
+ @telemetry_runtime_producer.record_auth_rejections if response.status == 401
56
+
57
+ { push_enabled: false, retry: false }
58
+ end
59
+
60
+ def process_success(response, start)
61
+ @config.logger.debug("Success connection to: #{@config.auth_service_url}") if @config.debug_enabled
62
+ record_telemetry(start)
63
+
64
+ body_json = JSON.parse(response.body, symbolize_names: true)
65
+ push_enabled = body_json[:pushEnabled]
66
+ token = body_json[:token]
67
+
68
+ if push_enabled
69
+ decoded_token = decode_token(token)
70
+ channels = channels(decoded_token)
71
+ exp = expiration(decoded_token)
72
+
73
+ @telemetry_runtime_producer.record_token_refreshes
74
+ end
75
+
76
+ { push_enabled: push_enabled, token: token, channels: channels, exp: exp, retry: true }
77
+ end
78
+
79
+ def control_channels(channels_string)
80
+ prefix = SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX
81
+ control_pri = SplitIoClient::Constants::CONTROL_PRI
82
+ control_sec = SplitIoClient::Constants::CONTROL_SEC
83
+ channels_string = channels_string.gsub(control_pri, "#{prefix}#{control_pri}")
84
+
85
+ channels_string.gsub(control_sec, "#{prefix}#{control_sec}")
86
+ end
87
+
88
+ def record_telemetry(start)
89
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
90
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TOKEN_SYNC, bucket)
91
+ timestamp = (Time.now.to_f * 1000.0).to_i
92
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TOKEN_SYNC, timestamp)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: false
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ BACKOFF_MAX_ALLOWED = 1800
6
+ class BackOff
7
+ def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED)
8
+ @attempt = attempt
9
+ @back_off_base = back_off_base
10
+ @max_allowed = max_allowed
11
+ end
12
+
13
+ def interval
14
+ interval = 0
15
+ interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
16
+ @attempt += 1
17
+
18
+ interval >= @max_allowed ? @max_allowed : interval
19
+ end
20
+
21
+ def reset
22
+ @attempt = 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ TIME_INTERVAL_MS = 3600 * 1000
9
+
10
+ class ImpressionCounter
11
+ DEFAULT_AMOUNT = 1
12
+
13
+ def initialize
14
+ @cache = Concurrent::Hash.new
15
+ end
16
+
17
+ def inc(split_name, time_frame)
18
+ key = make_key(split_name, time_frame)
19
+
20
+ current_amount = @cache[key]
21
+ @cache[key] = current_amount.nil? ? DEFAULT_AMOUNT : (current_amount + DEFAULT_AMOUNT)
22
+ end
23
+
24
+ def pop_all
25
+ to_return = Concurrent::Hash.new
26
+
27
+ @cache.each do |key, value|
28
+ to_return[key] = value
29
+ end
30
+ @cache.clear
31
+
32
+ to_return
33
+ end
34
+
35
+ def make_key(split_name, time_frame)
36
+ "#{split_name}::#{ImpressionCounter.truncate_time_frame(time_frame)}"
37
+ end
38
+
39
+ def self.truncate_time_frame(timestamp_ms)
40
+ timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Common
6
+ class ImpressionManager
7
+ def initialize(config,
8
+ impressions_repository,
9
+ impression_counter,
10
+ telemetry_runtime_producer,
11
+ impression_observer,
12
+ unique_keys_tracker)
13
+ @config = config
14
+ @impressions_repository = impressions_repository
15
+ @impression_counter = impression_counter
16
+ @impression_observer = impression_observer
17
+ @telemetry_runtime_producer = telemetry_runtime_producer
18
+ @unique_keys_tracker = unique_keys_tracker
19
+ end
20
+
21
+ def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {},
22
+ evaluation_options = nil)
23
+ properties = get_properties(evaluation_options)
24
+ impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time], properties)
25
+ return impression(impression_data, params[:attributes]) if check_return_conditions(properties)
26
+
27
+ begin
28
+ if check_none_mode(impressions_disabled)
29
+ @impression_counter.inc(split_name, impression_data[:m])
30
+ @unique_keys_tracker.track(split_name, matching_key)
31
+ end
32
+ if check_observe_impressions
33
+ # In DEBUG mode we should calculate the pt only.
34
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data)
35
+ end
36
+ if check_impression_counter(impression_data)
37
+ # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
38
+ @impression_counter.inc(split_name, impression_data[:m])
39
+ end
40
+ rescue StandardError => e
41
+ @config.log_found_exception(__method__.to_s, e)
42
+ end
43
+
44
+ impression(impression_data, params[:attributes])
45
+ end
46
+
47
+ def track(impressions_decorator)
48
+ return if impressions_decorator.empty?
49
+
50
+ impressions_decorator.each do |impression_decorator|
51
+ impression_router.add_bulk([impression_decorator[:impression]])
52
+ stats = { dropped: 0, queued: 0, dedupe: 0 }
53
+ begin
54
+ next if @config.impressions_mode == :none || impression_decorator[:disabled]
55
+
56
+ if @config.impressions_mode == :debug
57
+ track_debug_mode([impression_decorator[:impression]], stats)
58
+ else
59
+ track_optimized_mode([impression_decorator[:impression]], stats)
60
+ end
61
+ rescue StandardError => e
62
+ @config.log_found_exception(__method__.to_s, e)
63
+ ensure
64
+ record_stats(stats)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def check_return_conditions(properties)
72
+ return (@config.impressions_mode == :debug || @config.impressions_mode == :optimized) && !properties.nil?
73
+ end
74
+
75
+ def check_none_mode(impressions_disabled)
76
+ return @config.impressions_mode == :none || impressions_disabled
77
+ end
78
+
79
+ def check_observe_impressions
80
+ return @config.impressions_mode == :debug || @config.impressions_mode == :optimized
81
+ end
82
+
83
+ def check_impression_counter(impression_data)
84
+ return @config.impressions_mode == :optimized && !impression_data[:pt].nil?
85
+ end
86
+
87
+ def get_properties(evaluation_options)
88
+ return evaluation_options.nil? ? nil : evaluation_options.properties
89
+ end
90
+
91
+ def impression_router
92
+ @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
93
+ rescue StandardError => e
94
+ @config.log_found_exception(__method__.to_s, e)
95
+ end
96
+
97
+ def record_stats(stats)
98
+ return if redis?
99
+
100
+ imp_queued = Telemetry::Domain::Constants::IMPRESSIONS_QUEUED
101
+ imp_dropped = Telemetry::Domain::Constants::IMPRESSIONS_DROPPED
102
+ imp_dedupe = Telemetry::Domain::Constants::IMPRESSIONS_DEDUPE
103
+ @telemetry_runtime_producer.record_impressions_stats(imp_queued, stats[:queued]) unless stats[:queued].zero?
104
+ @telemetry_runtime_producer.record_impressions_stats(imp_dropped, stats[:dropped]) unless stats[:dropped].zero?
105
+ @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, stats[:dedupe]) unless stats[:dedupe].zero?
106
+ end
107
+
108
+ # added param time for test
109
+ def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil,
110
+ properties = nil)
111
+ {
112
+ k: matching_key,
113
+ b: bucketing_key,
114
+ f: split_name,
115
+ t: treatment[:treatment],
116
+ r: applied_rule(treatment[:label]),
117
+ c: treatment[:change_number],
118
+ m: time || (Time.now.to_f * 1000.0).to_i,
119
+ pt: nil,
120
+ properties: properties
121
+ }
122
+ end
123
+
124
+ def metadata
125
+ {
126
+ s: "#{@config.language}-#{@config.version}",
127
+ i: @config.machine_ip,
128
+ n: @config.machine_name
129
+ }
130
+ end
131
+
132
+ def applied_rule(label)
133
+ @config.labels_enabled ? label : nil
134
+ end
135
+
136
+ def should_queue_impression?(impression)
137
+ impression[:pt].nil? ||
138
+ (ImpressionCounter.truncate_time_frame(impression[:pt]) != ImpressionCounter.truncate_time_frame(impression[:m]))
139
+ end
140
+
141
+ def impression(impression_data, attributes)
142
+ { m: metadata, i: impression_data, attributes: attributes }
143
+ end
144
+
145
+ def redis?
146
+ @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
147
+ end
148
+
149
+ def track_debug_mode(impressions, stats)
150
+ stats[:dropped] = @impressions_repository.add_bulk(impressions)
151
+ stats[:queued] = impressions.length - stats[:dropped]
152
+ end
153
+
154
+ def track_optimized_mode(impressions, stats)
155
+ optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
156
+ stats[:dedupe] = impressions.length - optimized_impressions.length
157
+ return if optimized_impressions.empty?
158
+
159
+ stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
160
+ stats[:queued] = optimized_impressions.length - stats[:dropped]
161
+ end
162
+ end
163
+ end
164
+ end
165
+ 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,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Events
6
+ class EventsDelivery
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def deliver(sdk_event, event_metadata, event_handler)
12
+ event_handler.call(event_metadata)
13
+ rescue StandardError => e
14
+ @config.logger.error("Exception when calling handler for Sdk Event #{sdk_event}")
15
+ @config.log_found_exception(__method__.to_s, e)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Events
6
+ class EventsManager
7
+ def initialize(events_manager_config, events_delivery, config)
8
+ @manager_config = events_manager_config
9
+ @events_delivery = events_delivery
10
+ @active_subscriptions = {}
11
+ @internal_events_status = {}
12
+ @mutex = Mutex.new
13
+ @config = config
14
+ end
15
+
16
+ def register(sdk_event, event_handler)
17
+ return if @active_subscriptions.key?(sdk_event) && !get_event_handler(sdk_event).nil?
18
+
19
+ @mutex.synchronize do
20
+ # SDK ready already fired
21
+ if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event)
22
+ @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler)
23
+ @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled
24
+ fire_sdk_event(sdk_event, nil)
25
+ return
26
+ end
27
+
28
+ @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled
29
+ @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler)
30
+ end
31
+ end
32
+
33
+ def unregister(sdk_event)
34
+ return unless @active_subscriptions.key?(sdk_event)
35
+
36
+ @mutex.synchronize do
37
+ @active_subscriptions.delete(sdk_event)
38
+ end
39
+ end
40
+
41
+ def notify_internal_event(sdk_internal_event, event_metadata)
42
+ @mutex.synchronize do
43
+ update_internal_event_status(sdk_internal_event, true)
44
+ @manager_config.evaluation_order.each do |sorted_event|
45
+ if get_sdk_event_if_applicable(sdk_internal_event).include?(sorted_event) &&
46
+ !get_event_handler(sorted_event).nil?
47
+ fire_sdk_event(sorted_event, event_metadata)
48
+ end
49
+
50
+ # if client is not subscribed to SDK_READY
51
+ if check_if_register_needed(sorted_event)
52
+ @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled
53
+ @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def destroy
60
+ @mutex.synchronize do
61
+ @active_subscriptions = {}
62
+ @internal_events_status = {}
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def check_if_register_needed(sorted_event)
69
+ sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY &&
70
+ get_event_handler(sorted_event).nil? &&
71
+ !@active_subscriptions.include?(sorted_event)
72
+ end
73
+
74
+ def fire_sdk_event(sdk_event, event_metadata)
75
+ @config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled
76
+ @config.threads[:sdk_event_notify] = Thread.new do
77
+ @events_delivery.deliver(sdk_event, event_metadata, get_event_handler(sdk_event))
78
+ end
79
+ sdk_event_triggered(sdk_event)
80
+ end
81
+
82
+ def event_already_triggered(sdk_event)
83
+ return @active_subscriptions[sdk_event].triggered if @active_subscriptions.key?(sdk_event)
84
+
85
+ false
86
+ end
87
+
88
+ def get_internal_event_status(sdk_internal_event)
89
+ return @internal_events_status[sdk_internal_event] if @internal_events_status.key?(sdk_internal_event)
90
+
91
+ false
92
+ end
93
+
94
+ def update_internal_event_status(sdk_internal_event, status)
95
+ @internal_events_status[sdk_internal_event] = status
96
+ end
97
+
98
+ def sdk_event_triggered(sdk_event)
99
+ return unless @active_subscriptions.key?(sdk_event)
100
+
101
+ return if @active_subscriptions[sdk_event].triggered
102
+
103
+ @active_subscriptions[sdk_event].triggered = true
104
+ end
105
+
106
+ def get_event_handler(sdk_event)
107
+ return nil unless @active_subscriptions.key?(sdk_event)
108
+
109
+ @active_subscriptions[sdk_event].handler
110
+ end
111
+
112
+ def get_sdk_event_if_applicable(sdk_internal_event)
113
+ final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
114
+
115
+ events_to_fire = []
116
+ require_any_sdk_event = check_require_any(sdk_internal_event)
117
+ if require_any_sdk_event.valid
118
+ if (!event_already_triggered(require_any_sdk_event.sdk_event) &&
119
+ execution_limit(require_any_sdk_event.sdk_event) == 1) ||
120
+ execution_limit(require_any_sdk_event.sdk_event) == -1
121
+ final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(
122
+ require_any_sdk_event.sdk_event,
123
+ check_prerequisites(require_any_sdk_event.sdk_event) &&
124
+ check_suppressed_by(require_any_sdk_event.sdk_event)
125
+ )
126
+ end
127
+ events_to_fire.push(final_sdk_event.sdk_event) if final_sdk_event.valid
128
+ end
129
+ check_require_all.each { |sdk_event| events_to_fire.push(sdk_event) }
130
+
131
+ events_to_fire
132
+ end
133
+
134
+ def check_require_all
135
+ events = []
136
+ @manager_config.require_all.each do |require_name, require_value|
137
+ final_status = true
138
+ require_value.each { |val| final_status &= get_internal_event_status(val) }
139
+ events.push(require_name) if check_event_eligible_conditions(final_status, require_name, require_value)
140
+ end
141
+
142
+ events
143
+ end
144
+
145
+ def check_event_eligible_conditions(final_status, require_name, require_value)
146
+ final_status &&
147
+ check_prerequisites(require_name) &&
148
+ ((!event_already_triggered(require_name) &&
149
+ execution_limit(require_name) == 1) ||
150
+ execution_limit(require_name) == -1) &&
151
+ require_value.length.positive?
152
+ end
153
+
154
+ def check_prerequisites(sdk_event)
155
+ @manager_config.prerequisites.each do |name, value|
156
+ value.each do |val|
157
+ return false if name == sdk_event && !event_already_triggered(val)
158
+ end
159
+ end
160
+
161
+ true
162
+ end
163
+
164
+ def check_suppressed_by(sdk_event)
165
+ @manager_config.suppressed_by.each do |name, value|
166
+ value.each do |val|
167
+ return false if name == sdk_event && event_already_triggered(val)
168
+ end
169
+ end
170
+
171
+ true
172
+ end
173
+
174
+ def execution_limit(sdk_event)
175
+ return -1 unless @manager_config.execution_limits.key?(sdk_event)
176
+
177
+ @manager_config.execution_limits[sdk_event]
178
+ end
179
+
180
+ def check_require_any(sdk_internal_event)
181
+ valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
182
+ @manager_config.require_any.each do |name, val|
183
+ if val.include?(sdk_internal_event)
184
+ valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true)
185
+ return valid_sdk_event
186
+ end
187
+ end
188
+
189
+ valid_sdk_event
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Events
6
+ class EventsManagerConfig
7
+ attr_accessor :require_all, :prerequisites, :require_any, :suppressed_by, :execution_limits, :evaluation_order
8
+
9
+ def initialize
10
+ @require_all = construct_require_all
11
+ @prerequisites = construct_prerequisites
12
+ @require_any = construct_require_any
13
+ @suppressed_by = construct_suppressed_by
14
+ @execution_limits = construct_execution_limits
15
+ @evaluation_order = construct_sorted_events
16
+ end
17
+
18
+ private
19
+
20
+ def construct_require_all
21
+ {
22
+ SplitIoClient::Engine::Models::SdkEvent::SDK_READY => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY])
23
+ }
24
+ end
25
+
26
+ def construct_prerequisites
27
+ {
28
+ SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkEvent::SDK_READY])
29
+ }
30
+ end
31
+
32
+ def construct_require_any
33
+ {
34
+ SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new(
35
+ [
36
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
37
+ SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
38
+ SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED,
39
+ SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED
40
+ ]
41
+ )
42
+ }
43
+ end
44
+
45
+ def construct_suppressed_by
46
+ {}
47
+ end
48
+
49
+ def construct_execution_limits
50
+ {
51
+ SplitIoClient::Engine::Models::SdkEvent::SDK_READY => 1,
52
+ SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => -1
53
+ }
54
+ end
55
+
56
+ def construct_sorted_events
57
+ sorted_events = []
58
+ [SplitIoClient::Engine::Models::SdkEvent::SDK_READY, SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].each do |sdk_event|
59
+ sorted_events = dfs_recursive(sdk_event, sorted_events)
60
+ end
61
+
62
+ sorted_events
63
+ end
64
+
65
+ def dfs_recursive(sdk_event, added)
66
+ return added if added.include?(sdk_event)
67
+
68
+ get_dependencies(sdk_event).each do |dependent_event|
69
+ added = dfs_recursive(dependent_event, added)
70
+ end
71
+
72
+ added.push(sdk_event)
73
+
74
+ added
75
+ end
76
+
77
+ def get_dependencies(sdk_event)
78
+ dependencies = Set.new
79
+ @prerequisites.each do |prerequisites_event_name, prerequisites_event_value|
80
+ next unless prerequisites_event_name == sdk_event
81
+
82
+ prerequisites_event_value.each do |prereq_event|
83
+ dependencies.add(prereq_event)
84
+ end
85
+ end
86
+
87
+ @suppressed_by.each do |suppressed_event_name, suppressed_event_value|
88
+ dependencies.add(suppressed_event_name) if suppressed_event_value.include?(sdk_event)
89
+ end
90
+
91
+ dependencies
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end