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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SplitIoClient::Constants
4
+ EXPIRATION_RATE = 600
5
+ CONTROL_PRI = 'control_pri'
6
+ CONTROL_SEC = 'control_sec'
7
+ OCCUPANCY_CHANNEL_PREFIX = '[?occupancy=metrics.publishers]'
8
+ FETCH_BACK_OFF_BASE_RETRIES = 1
9
+ PUSH_CONNECTED = 'PUSH_CONNECTED'
10
+ PUSH_RETRYABLE_ERROR = 'PUSH_RETRYABLE_ERROR'
11
+ PUSH_NONRETRYABLE_ERROR = 'PUSH_NONRETRYABLE_ERROR'
12
+ PUSH_SUBSYSTEM_DOWN = 'PUSH_SUBSYSTEM_DOWN'
13
+ PUSH_SUBSYSTEM_READY = 'PUSH_SUBSYSTEM_READY'
14
+ PUSH_SUBSYSTEM_OFF = 'PUSH_SUBSYSTEM_OFF'
15
+ PUSH_FORCED_STOP = 'PUSH_FORCED_STOP'
16
+ end
@@ -1,23 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http/persistent'
4
-
5
3
  module SplitIoClient
6
4
  module Api
7
5
  class Client
8
- RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
6
+ def initialize(config)
7
+ @config = config
8
+
9
+ check_faraday_compatibility
10
+ end
9
11
 
10
- def get_api(url, api_key, params = {})
12
+ def get_api(url, api_key, params = {}, cache_control_headers = false)
13
+ api_client.options.params_encoder.sort_params = false
11
14
  api_client.get(url, params) do |req|
12
15
  req.headers = common_headers(api_key).merge('Accept-Encoding' => 'gzip')
16
+ req.headers = req.headers.merge('Cache-Control' => 'no-cache') if cache_control_headers
13
17
 
14
- req.options[:timeout] = SplitIoClient.configuration.read_timeout
15
- req.options[:open_timeout] = SplitIoClient.configuration.connection_timeout
18
+ req.options[:timeout] = @config.read_timeout
19
+ req.options[:open_timeout] = @config.connection_timeout
16
20
 
17
- SplitLogger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
21
+ @config.split_logger.log_if_debug("GET #{url} proxy: #{api_client.proxy}")
18
22
  end
23
+
24
+ rescue Zlib::GzipFile::Error => e
25
+ @config.logger.warn("Incorrect formatted response exception: #{e}\n")
26
+ SplitIoClient::Engine::Models::SplitHttpResponse.new(400, '', true)
27
+
19
28
  rescue StandardError => e
20
- SplitIoClient.configuration.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
29
+ @config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
21
30
  raise e, 'Split SDK failed to connect to backend to retrieve information', e.backtrace
22
31
  end
23
32
 
@@ -27,65 +36,51 @@ module SplitIoClient
27
36
  .merge('Content-Type' => 'application/json')
28
37
  .merge(headers)
29
38
 
39
+ machine_ip = @config.machine_ip
40
+ machine_name = @config.machine_name
41
+
42
+ req.headers = req.headers.merge('SplitSDKMachineIP' => machine_ip) unless machine_ip.empty? || machine_ip == 'unknown'
43
+ req.headers = req.headers.merge('SplitSDKMachineName' => machine_name) unless machine_name.empty? || machine_name == 'unknown'
44
+
30
45
  req.body = data.to_json
31
46
 
32
- req.options[:timeout] = SplitIoClient.configuration.read_timeout
33
- req.options[:open_timeout] = SplitIoClient.configuration.connection_timeout
47
+ req.options[:timeout] = @config.read_timeout
48
+ req.options[:open_timeout] = @config.connection_timeout
34
49
 
35
- SplitLogger.log_if_transport("POST #{url} #{req.body}")
36
- SplitLogger.log_if_debug("POST #{url}")
50
+ @config.split_logger.log_if_transport("POST #{url} #{req.body}")
51
+ @config.split_logger.log_if_debug("POST #{url}")
37
52
  end
38
53
  rescue StandardError => e
39
- SplitIoClient.configuration.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
54
+ @config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
40
55
  raise e, 'Split SDK failed to connect to backend to post information', e.backtrace
41
56
  end
42
57
 
58
+ def sdk_url_overriden?
59
+ @config.sdk_url_overriden?
60
+ end
43
61
  private
44
62
 
45
63
  def api_client
46
- if needs_patched_net_http_persistent_adapter?
47
- require 'splitclient-rb/engine/api/faraday_adapter/patched_net_http_persistent'
48
-
49
- Faraday::Adapter.register_middleware(
50
- net_http_persistent: SplitIoClient::FaradayAdapter::PatchedNetHttpPersistent
51
- )
52
- end
53
-
54
64
  @api_client ||= Faraday.new do |builder|
55
65
  builder.use SplitIoClient::FaradayMiddleware::Gzip
56
66
  builder.adapter :net_http_persistent
67
+ builder.options.params_encoder = Faraday::FlatParamsEncoder
57
68
  end
58
69
  end
59
70
 
60
- def needs_patched_net_http_persistent_adapter?
61
- new_net_http_persistent? && incompatible_faraday_version?
62
- end
63
-
64
- def incompatible_faraday_version?
65
- version = Faraday::VERSION.split('.')[0..1]
66
- version[0].to_i == 0 && version[1].to_i < 13
67
- end
68
-
69
- def new_net_http_persistent?
70
- Net::HTTP::Persistent::VERSION.split('.').first.to_i >= 3
71
- end
72
-
73
71
  def common_headers(api_key)
74
72
  {
75
73
  'Authorization' => "Bearer #{api_key}",
76
- 'SplitSDKVersion' => "#{SplitIoClient.configuration.language}-#{SplitIoClient.configuration.version}",
77
- 'SplitSDKMachineName' => SplitIoClient.configuration.machine_name,
78
- 'SplitSDKMachineIP' => SplitIoClient.configuration.machine_ip,
79
- 'Referer' => referer
74
+ 'SplitSDKVersion' => "#{@config.language}-#{@config.version}",
80
75
  }
81
76
  end
82
77
 
83
- def referer
84
- result = "#{SplitIoClient.configuration.language}-#{SplitIoClient.configuration.version}"
78
+ def check_faraday_compatibility
79
+ version = Faraday::VERSION.split('.')[0]
85
80
 
86
- result = "#{result}::#{SplitIoClient::SplitConfig.machine_hostname}" unless SplitIoClient::SplitConfig.machine_hostname == 'localhost'
87
-
88
- result
81
+ require 'faraday/net_http_persistent' if version.to_i >= 2
82
+ rescue StandardError => e
83
+ @config.logger.warn(e)
89
84
  end
90
85
  end
91
86
  end
@@ -3,30 +3,37 @@
3
3
  module SplitIoClient
4
4
  module Api
5
5
  class Events < Client
6
- def initialize(api_key)
6
+ def initialize(api_key, config, telemetry_runtime_producer)
7
+ super(config)
7
8
  @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
8
10
  end
9
11
 
10
12
  def post(events)
11
13
  if events.empty?
12
- SplitLogger.log_if_debug('No events to report')
14
+ @config.split_logger.log_if_debug('No events to report')
13
15
  return
14
16
  end
15
17
 
16
- events.each_slice(SplitIoClient.configuration.events_queue_size) do |events_slice|
18
+ start = Time.now
19
+
20
+ events.each_slice(@config.events_queue_size) do |events_slice|
17
21
  response = post_api(
18
- "#{SplitIoClient.configuration.events_uri}/events/bulk",
22
+ "#{@config.events_uri}/events/bulk",
19
23
  @api_key,
20
- events_slice.map { |event| formatted_event(event[:e]) },
21
- 'SplitSDKMachineIP' => events_slice[0][:m][:i],
22
- 'SplitSDKMachineName' => events_slice[0][:m][:n],
23
- 'SplitSDKVersion' => events_slice[0][:m][:s]
24
+ events_slice.map { |event| formatted_event(event[:e]) }
24
25
  )
25
26
 
26
27
  if response.success?
27
- SplitLogger.log_if_debug("Events reported: #{events_slice.size}")
28
+ @config.split_logger.log_if_debug("Events reported: #{events_slice.size}")
29
+
30
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
31
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::EVENT_SYNC, bucket)
32
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::EVENT_SYNC, (Time.now.to_f * 1000.0).to_i)
28
33
  else
29
- SplitLogger.log_error("Unexpected status code while posting events: #{response.status}." \
34
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::EVENT_SYNC, response.status)
35
+
36
+ @config.logger.error("Unexpected status code while posting events: #{response.status}." \
30
37
  ' - Check your API key and base URI')
31
38
  raise 'Split SDK failed to connect to backend to post events'
32
39
  end
@@ -41,7 +48,8 @@ module SplitIoClient
41
48
  trafficTypeName: event[:trafficTypeName],
42
49
  eventTypeId: event[:eventTypeId],
43
50
  value: event[:value].to_f,
44
- timestamp: event[:timestamp].to_i
51
+ timestamp: event[:timestamp].to_i,
52
+ properties: event[:properties]
45
53
  }
46
54
  end
47
55
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'faraday'
4
+ require 'stringio'
4
5
 
5
6
  module SplitIoClient
6
7
  module FaradayMiddleware
@@ -3,26 +3,59 @@
3
3
  module SplitIoClient
4
4
  module Api
5
5
  class Impressions < Client
6
- def initialize(api_key)
6
+ def initialize(api_key, config, telemetry_runtime_producer)
7
+ super(config)
7
8
  @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
8
10
  end
9
11
 
10
12
  def post(impressions)
11
13
  if impressions.empty?
12
- SplitLogger.log_if_debug('No impressions to report')
14
+ @config.split_logger.log_if_debug('No impressions to report')
13
15
  return
14
16
  end
15
17
 
16
- impressions_by_ip(impressions).each do |ip, impressions_ip|
17
- response = post_api("#{SplitIoClient.configuration.events_uri}/testImpressions/bulk", @api_key, impressions_ip, 'SplitSDKMachineIP' => ip)
18
+ start = Time.now
18
19
 
19
- if response.success?
20
- SplitLogger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
21
- else
22
- SplitLogger.log_error("Unexpected status code while posting impressions: #{response.status}." \
23
- ' - Check your API key and base URI')
24
- raise 'Split SDK failed to connect to backend to post impressions'
25
- end
20
+ response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
21
+
22
+ if response.success?
23
+ @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
24
+
25
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
26
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, bucket)
27
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, (Time.now.to_f * 1000.0).to_i)
28
+ else
29
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, response.status)
30
+
31
+ @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
32
+ ' - Check your API key and base URI')
33
+ raise 'Split SDK failed to connect to backend to post impressions'
34
+ end
35
+ end
36
+
37
+ def post_count(impressions_count)
38
+ if impressions_count.nil? || impressions_count[:pf].empty?
39
+ @config.split_logger.log_if_debug('No impressions count to send')
40
+ return
41
+ end
42
+
43
+ start = Time.now
44
+
45
+ response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
46
+
47
+ if response.success?
48
+ @config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
49
+
50
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
51
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, bucket)
52
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, (Time.now.to_f * 1000.0).to_i)
53
+ else
54
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, response.status)
55
+
56
+ @config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
57
+ ' - Check your API key and base URI')
58
+ raise 'Split SDK failed to connect to backend to post impressions'
26
59
  end
27
60
  end
28
61
 
@@ -30,14 +63,16 @@ module SplitIoClient
30
63
  return 0 if impressions.nil?
31
64
 
32
65
  impressions.reduce(0) do |impressions_count, impression|
33
- impressions_count += impression[:keyImpressions].length
66
+ impressions_count += impression[:i].length
34
67
  end
35
68
  end
36
69
 
37
70
  private
38
71
 
39
- def impressions_by_ip(impressions)
40
- impressions.group_by { |impression| impression[:ip] }
72
+ def impressions_headers
73
+ {
74
+ 'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
75
+ }
41
76
  end
42
77
  end
43
78
  end
@@ -4,26 +4,24 @@ module SplitIoClient
4
4
  module Api
5
5
  # Retrieves segment changes from the Split Backend
6
6
  class Segments < Client
7
- METRICS_PREFIX = 'segmentChangeFetcher'
8
-
9
- def initialize(api_key, metrics, segments_repository)
10
- @metrics = metrics
7
+ def initialize(api_key, segments_repository, config, telemetry_runtime_producer)
8
+ super(config)
11
9
  @api_key = api_key
12
10
  @segments_repository = segments_repository
11
+ @telemetry_runtime_producer = telemetry_runtime_producer
13
12
  end
14
13
 
15
- def store_segments_by_names(names)
16
- start = Time.now
17
-
14
+ def fetch_segments_by_names(names, fetch_options = { cache_control_headers: false, till: nil })
18
15
  return if names.nil? || names.empty?
19
16
 
20
17
  names.each do |name|
21
18
  since = @segments_repository.get_change_number(name)
19
+
22
20
  loop do
23
- segment = fetch_segment_changes(name, since)
21
+ segment = fetch_segment_changes(name, since, fetch_options)
24
22
  @segments_repository.add_to_segment(segment)
25
23
 
26
- SplitLogger.log_if_debug("Segment #{name} fetched before: #{since}, \
24
+ @config.split_logger.log_if_debug("Segment #{name} fetched before: #{since}, \
27
25
  till: #{@segments_repository.get_change_number(name)}")
28
26
 
29
27
  break if since.to_i >= @segments_repository.get_change_number(name).to_i
@@ -31,38 +29,47 @@ module SplitIoClient
31
29
  since = @segments_repository.get_change_number(name)
32
30
  end
33
31
  end
34
-
35
- latency = (Time.now - start) * 1000.0
36
- @metrics.time(METRICS_PREFIX + '.time', latency)
37
32
  end
38
33
 
39
34
  private
40
35
 
41
- def fetch_segment_changes(name, since)
42
- response = get_api("#{SplitIoClient.configuration.base_uri}/segmentChanges/#{name}", @api_key, since: since)
36
+ def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil })
37
+ start = Time.now
38
+
39
+ params = { since: since }
40
+ params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
41
+ response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, params, fetch_options[:cache_control_headers])
42
+
43
43
  if response.success?
44
44
  segment = JSON.parse(response.body, symbolize_names: true)
45
45
  @segments_repository.set_change_number(name, segment[:till])
46
- @metrics.count(METRICS_PREFIX + '.status.' + response.status.to_s, 1)
47
46
 
48
- SplitLogger.log_if_debug("\'#{segment[:name]}\' segment retrieved.")
47
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' segment retrieved.")
49
48
  unless segment[:added].empty?
50
- SplitLogger.log_if_debug("\'#{segment[:name]}\' #{segment[:added].size} added keys")
49
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:added].size} added keys")
51
50
  end
52
51
  unless segment[:removed].empty?
53
- SplitLogger.log_if_debug("\'#{segment[:name]}\' #{segment[:removed].size} removed keys")
52
+ @config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:removed].size} removed keys")
54
53
  end
55
- SplitLogger.log_if_transport(segment.to_s)
54
+ @config.split_logger.log_if_transport("Segment changes response: #{segment.to_s}")
55
+
56
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
57
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SEGMENT_SYNC, bucket)
58
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SEGMENT_SYNC, (Time.now.to_f * 1000.0).to_i)
56
59
 
57
60
  segment
58
61
  elsif response.status == 403
59
- SplitIoClient.configuration.logger.error('Factory Instantiation: You passed a browser type api_key, ' \
60
- 'please grab an api key from the Split console that is of type sdk')
61
- SplitIoClient.configuration.valid_mode = false
62
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
63
+
64
+ @config.logger.error('Factory Instantiation: You passed a browser type api_key, ' \
65
+ 'please grab an api key from the Split console that is of type sdk')
66
+ @config.valid_mode = false
62
67
  else
63
- SplitLogger.log_error("Unexpected status code while fetching segments: #{response.status}." \
68
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
69
+
70
+ @config.logger.error("Unexpected status code while fetching segments: #{response.status}." \
64
71
  "Since #{since} - Check your API key and base URI")
65
- @metrics.count(METRICS_PREFIX + '.status.' + response.status.to_s, 1)
72
+
66
73
  raise 'Split SDK failed to connect to backend to fetch segments'
67
74
  end
68
75
  end
@@ -4,60 +4,135 @@ module SplitIoClient
4
4
  module Api
5
5
  # Retrieves split definitions from the Split Backend
6
6
  class Splits < Client
7
- METRICS_PREFIX = 'splitChangeFetcher'
8
7
 
9
- def initialize(api_key, metrics)
8
+ PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
9
+ SPEC_1_1 = "1.1"
10
+
11
+ def initialize(api_key, config, telemetry_runtime_producer)
12
+ super(config)
10
13
  @api_key = api_key
11
- @metrics = metrics
14
+ @telemetry_runtime_producer = telemetry_runtime_producer
15
+ @flag_sets_filter = @config.flag_sets_filter
16
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
17
+ @last_proxy_check_timestamp = 0
18
+ @clear_storage = false
19
+ @old_spec_since = nil
12
20
  end
13
21
 
14
- def since(since)
22
+ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
15
23
  start = Time.now
24
+
25
+ if check_last_proxy_check_timestamp
26
+ @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
27
+ @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled
28
+ @old_spec_since = since
29
+ since = -1
30
+ since_rbs = -1
31
+ fetch_options = { cache_control_headers: false, till: nil, sets: nil}
32
+ end
16
33
 
17
- response = get_api("#{SplitIoClient.configuration.base_uri}/splitChanges", @api_key, since: since)
34
+ if @spec_version == Splits::SPEC_1_1
35
+ since = @old_spec_since unless @old_spec_since.nil?
36
+ params = { s: @spec_version, since: since }
37
+ @old_spec_since = nil
38
+ else
39
+ params = { s: @spec_version, since: since, rbSince: since_rbs }
40
+ end
18
41
 
42
+ params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
43
+ params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
44
+ @config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled
45
+ response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
46
+ if response.status == 414
47
+ @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
48
+ raise ApiException.new response.body, 414
49
+ end
50
+
51
+ if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
52
+ @config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
53
+ @spec_version = Splits::SPEC_1_1
54
+ @last_proxy_check_timestamp = Time.now
55
+ return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
56
+ sets: fetch_options[:sets]})
57
+ end
19
58
  if response.success?
20
- result = splits_with_segment_names(response.body)
59
+ result = JSON.parse(response.body, symbolize_names: true)
21
60
 
22
- @metrics.count(METRICS_PREFIX + '.status.' + response.status.to_s, 1)
23
- unless result[:splits].empty?
24
- SplitLogger.log_if_debug("#{result[:splits].length} splits retrieved. since=#{since}")
25
- end
26
- SplitLogger.log_if_transport(result.to_s)
61
+ return process_result(result, since, since_rbs, start)
62
+ end
63
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
27
64
 
28
- latency = (Time.now - start) * 1000.0
29
- @metrics.time(METRICS_PREFIX + '.time', latency)
65
+ @config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
66
+ 'Check your API key and base URI')
30
67
 
31
- result
32
- else
33
- @metrics.count(METRICS_PREFIX + '.status.' + response.status.to_s, 1)
34
- SplitLogger.log_error("Unexpected status code while fetching splits: #{response.status}. " \
35
- 'Check your API key and base URI')
36
- raise 'Split SDK failed to connect to backend to fetch split definitions'
37
- end
68
+ raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
69
+ end
70
+
71
+ def clear_storage
72
+ @clear_storage
38
73
  end
39
74
 
40
75
  private
41
76
 
42
- def splits_with_segment_names(splits_json)
43
- parsed_splits = JSON.parse(splits_json, symbolize_names: true)
77
+ def process_result(result, since, since_rbs, start)
78
+ if @spec_version == Splits::SPEC_1_1
79
+ result = convert_to_new_spec(result)
80
+ end
81
+
82
+ result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
83
+ result = objects_with_segment_names(result)
84
+
85
+ if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
86
+ @clear_storage = @last_proxy_check_timestamp != 0
87
+ @last_proxy_check_timestamp = 0
88
+ end
44
89
 
45
- parsed_splits[:segment_names] =
46
- parsed_splits[:splits].each_with_object(Set.new) do |split, splits|
47
- splits << segment_names(split)
48
- end.flatten
90
+ unless result[:ff][:d].empty?
91
+ @config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
92
+ end
93
+ @config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
94
+
95
+ unless result[:rbs][:d].empty?
96
+ @config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
97
+ end
98
+ @config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
49
99
 
50
- parsed_splits
100
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
101
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
102
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
103
+
104
+ result
51
105
  end
52
106
 
53
- def segment_names(split)
54
- split[:conditions].each_with_object(Set.new) do |condition, names|
55
- condition[:matcherGroup][:matchers].each do |matcher|
56
- next if matcher[:userDefinedSegmentMatcherData].nil?
107
+ def check_rbs_data(rbs_data)
108
+ rbs_data.each do |rb_segment|
109
+ rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
110
+ rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
111
+ rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
112
+ end
113
+ rbs_data
114
+ end
57
115
 
58
- names << matcher[:userDefinedSegmentMatcherData][:segmentName]
59
- end
116
+ def objects_with_segment_names(parsed_objects)
117
+ parsed_objects[:segment_names] = Set.new
118
+ parsed_objects[:segment_names] =
119
+ parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
120
+ splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
121
+ end.flatten
122
+
123
+ parsed_objects[:rbs][:d].each do |rule_based_segment|
124
+ parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
60
125
  end
126
+
127
+ parsed_objects
128
+ end
129
+
130
+ def check_last_proxy_check_timestamp
131
+ @spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
132
+ end
133
+
134
+ def convert_to_new_spec(body)
135
+ {:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
61
136
  end
62
137
  end
63
138
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class TelemetryApi < Client
6
+ def initialize(config, api_key, telemetry_runtime_producer)
7
+ super(config)
8
+ @api_key = api_key
9
+ @telemetry_runtime_producer = telemetry_runtime_producer
10
+ end
11
+
12
+ def record_init(config_init)
13
+ post_telemetry("#{@config.telemetry_service_url}/metrics/config", config_init, 'init')
14
+ end
15
+
16
+ def record_stats(stats)
17
+ post_telemetry("#{@config.telemetry_service_url}/metrics/usage", stats, 'stats')
18
+ end
19
+
20
+ def record_unique_keys(uniques)
21
+ return if uniques[:keys].empty?
22
+
23
+ post_telemetry("#{@config.telemetry_service_url}/keys/ss", uniques, 'unique_keys')
24
+ rescue StandardError => e
25
+ @config.log_found_exception(__method__.to_s, e)
26
+ end
27
+
28
+ private
29
+
30
+ def post_telemetry(url, obj, method)
31
+ start = Time.now
32
+ response = post_api(url, @api_key, obj)
33
+
34
+ if response.success?
35
+ @config.split_logger.log_if_debug("Telemetry post succeeded: record #{method}.")
36
+
37
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
38
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TELEMETRY_SYNC, bucket)
39
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TELEMETRY_SYNC, (Time.now.to_f * 1000.0).to_i)
40
+ else
41
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TELEMETRY_SYNC, response.status)
42
+ @config.logger.error("Unexpected status code while posting telemetry #{method}: #{response.status}.")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end