splitclient-rb 7.2.2.pre.rc1-java → 7.3.0.pre.rc2-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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -0
  3. data/CHANGES.txt +6 -0
  4. data/lib/splitclient-rb.rb +24 -9
  5. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +4 -0
  6. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +21 -9
  7. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -9
  8. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +6 -3
  9. data/lib/splitclient-rb/cache/repositories/events_repository.rb +4 -3
  10. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +8 -0
  11. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +2 -0
  12. data/lib/splitclient-rb/cache/repositories/repository.rb +0 -4
  13. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +20 -0
  14. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +4 -0
  15. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +1 -3
  16. data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +9 -0
  17. data/lib/splitclient-rb/clients/split_client.rb +59 -25
  18. data/lib/splitclient-rb/constants.rb +6 -1
  19. data/lib/splitclient-rb/engine/api/client.rb +3 -2
  20. data/lib/splitclient-rb/engine/api/events.rb +10 -1
  21. data/lib/splitclient-rb/engine/api/impressions.rb +19 -2
  22. data/lib/splitclient-rb/engine/api/segments.rb +20 -18
  23. data/lib/splitclient-rb/engine/api/splits.rb +10 -10
  24. data/lib/splitclient-rb/engine/api/telemetry_api.rb +39 -0
  25. data/lib/splitclient-rb/engine/auth_api_client.rb +21 -8
  26. data/lib/splitclient-rb/engine/common/impressions_manager.rb +27 -3
  27. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +3 -65
  28. data/lib/splitclient-rb/engine/push_manager.rb +12 -3
  29. data/lib/splitclient-rb/engine/sync_manager.rb +85 -46
  30. data/lib/splitclient-rb/engine/synchronizer.rb +14 -22
  31. data/lib/splitclient-rb/split_config.rb +46 -21
  32. data/lib/splitclient-rb/split_factory.rb +31 -13
  33. data/lib/splitclient-rb/split_factory_registry.rb +12 -0
  34. data/lib/splitclient-rb/sse/event_source/client.rb +53 -28
  35. data/lib/splitclient-rb/sse/event_source/event_parser.rb +10 -1
  36. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +45 -26
  37. data/lib/splitclient-rb/sse/sse_handler.rb +16 -21
  38. data/lib/splitclient-rb/sse/workers/segments_worker.rb +5 -4
  39. data/lib/splitclient-rb/sse/workers/splits_worker.rb +6 -3
  40. data/lib/splitclient-rb/telemetry/domain/constants.rb +42 -0
  41. data/lib/splitclient-rb/telemetry/domain/structs.rb +31 -0
  42. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  43. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  44. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  45. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  46. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  47. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  48. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  49. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  50. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +112 -0
  51. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +81 -0
  52. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +192 -0
  53. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  54. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  55. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +28 -0
  56. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +24 -0
  57. data/lib/splitclient-rb/telemetry/runtime_producer.rb +24 -0
  58. data/lib/splitclient-rb/telemetry/storages/memory.rb +139 -0
  59. data/lib/splitclient-rb/telemetry/sync_task.rb +38 -0
  60. data/lib/splitclient-rb/telemetry/synchronizer.rb +29 -0
  61. data/lib/splitclient-rb/version.rb +1 -1
  62. metadata +24 -9
  63. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +0 -163
  64. data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +0 -131
  65. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +0 -23
  66. data/lib/splitclient-rb/cache/senders/metrics_sender.rb +0 -55
  67. data/lib/splitclient-rb/engine/api/metrics.rb +0 -61
  68. data/lib/splitclient-rb/engine/metrics/metrics.rb +0 -80
  69. data/lib/splitclient-rb/redis_metrics_fixer.rb +0 -36
@@ -6,22 +6,21 @@ require 'cgi'
6
6
  module SplitIoClient
7
7
  module Engine
8
8
  class AuthApiClient
9
- def initialize(config)
9
+ def initialize(config, telemetry_runtime_producer)
10
10
  @config = config
11
11
  @api_client = SplitIoClient::Api::Client.new(@config)
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
12
13
  end
13
14
 
14
15
  def authenticate(api_key)
16
+ start = Time.now
15
17
  response = @api_client.get_api(@config.auth_service_url, api_key)
16
18
 
17
- return process_success(response) if response.success?
19
+ return process_success(response, start) if response.success?
18
20
 
19
- if response.status >= 400 && response.status < 500
20
- @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
21
-
22
- return { push_enabled: false, retry: false }
23
- end
21
+ return process_error(response) if response.status >= 400 && response.status < 500
24
22
 
23
+ @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i)
25
24
  @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
26
25
  { push_enabled: false, retry: true }
27
26
  rescue StandardError => e
@@ -51,9 +50,21 @@ module SplitIoClient
51
50
  JWT.decode token, nil, false
52
51
  end
53
52
 
54
- def process_success(response)
53
+ def process_error(response)
54
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
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)
55
61
  @config.logger.debug("Success connection to: #{@config.auth_service_url}") if @config.debug_enabled
56
62
 
63
+ bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
64
+ @telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TOKEN_SYNC, bucket)
65
+ timestamp = (Time.now.to_f * 1000.0).to_i
66
+ @telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TOKEN_SYNC, timestamp)
67
+
57
68
  body_json = JSON.parse(response.body, symbolize_names: true)
58
69
  push_enabled = body_json[:pushEnabled]
59
70
  token = body_json[:token]
@@ -62,6 +73,8 @@ module SplitIoClient
62
73
  decoded_token = decode_token(token)
63
74
  channels = channels(decoded_token)
64
75
  exp = expiration(decoded_token)
76
+
77
+ @telemetry_runtime_producer.record_token_refreshes
65
78
  end
66
79
 
67
80
  { push_enabled: push_enabled, token: token, channels: channels, exp: exp, retry: false }
@@ -4,11 +4,12 @@ module SplitIoClient
4
4
  module Engine
5
5
  module Common
6
6
  class ImpressionManager
7
- def initialize(config, impressions_repository, impression_counter)
7
+ def initialize(config, impressions_repository, impression_counter, telemetry_runtime_producer)
8
8
  @config = config
9
9
  @impressions_repository = impressions_repository
10
10
  @impression_counter = impression_counter
11
11
  @impression_observer = SplitIoClient::Observers::ImpressionObserver.new
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
12
13
  end
13
14
 
14
15
  # added param time for test
@@ -29,18 +30,41 @@ module SplitIoClient
29
30
 
30
31
  impression_router.add_bulk(impressions)
31
32
 
33
+ dropped = 0
34
+ queued = 0
35
+ dedupe = 0
36
+
32
37
  if optimized? && !redis?
33
38
  optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
34
- @impressions_repository.add_bulk(optimized_impressions)
39
+
40
+ unless optimized_impressions.empty?
41
+ dropped = @impressions_repository.add_bulk(optimized_impressions)
42
+ dedupe = impressions.length - optimized_impressions.length
43
+ queued = optimized_impressions.length - dropped
44
+ end
35
45
  else
36
- @impressions_repository.add_bulk(impressions)
46
+ dropped = @impressions_repository.add_bulk(impressions)
47
+ queued = impressions.length - dropped
37
48
  end
49
+
50
+ record_stats(queued, dropped, dedupe)
38
51
  rescue StandardError => error
39
52
  @config.log_found_exception(__method__.to_s, error)
40
53
  end
41
54
 
42
55
  private
43
56
 
57
+ def record_stats(queued, dropped, dedupe)
58
+ return if redis?
59
+
60
+ imp_queued = Telemetry::Domain::Constants::IMPRESSIONS_QUEUED
61
+ imp_dropped = Telemetry::Domain::Constants::IMPRESSIONS_DROPPED
62
+ imp_dedupe = Telemetry::Domain::Constants::IMPRESSIONS_DEDUPE
63
+ @telemetry_runtime_producer.record_impressions_stats(imp_queued, queued) unless queued.zero?
64
+ @telemetry_runtime_producer.record_impressions_stats(imp_dropped, dropped) unless dropped.zero?
65
+ @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, dedupe) unless dedupe.zero?
66
+ end
67
+
44
68
  # added param time for test
45
69
  def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil)
46
70
  {
@@ -42,75 +42,13 @@ module SplitIoClient
42
42
 
43
43
  MAX_LATENCY = 7481828
44
44
 
45
- attr_accessor :latencies
46
-
47
- def initialize
48
- @latencies = Array.new(BUCKETS.length, 0)
49
- end
50
-
51
- #
52
- # Increment the internal counter for the bucket this latency falls into.
53
- # @param millis
54
- #
55
- def add_latency_millis(millis, return_index = false)
56
- index = find_bucket_index(millis * 1000)
57
-
58
- return index if return_index
59
-
60
- @latencies[index] += 1
61
- @latencies
62
- end
63
-
64
- # Increment the internal counter for the bucket this latency falls into.
65
- # @param micros
66
- def add_latency_micros(micros, return_index = false)
67
- index = find_bucket_index(micros)
68
-
69
- return index if return_index
70
-
71
- @latencies[index] += 1
72
- @latencies
73
- end
74
-
75
- # Returns the list of latencies buckets as an array.
76
- #
77
- #
78
- # @return the list of latencies buckets as an array.
79
- def get_latencies
80
- @latencies
81
- end
82
-
83
- def get_latency(index)
84
- return @latencies[index]
85
- end
86
-
87
- def clear
88
- @latencies = Array.new(BUCKETS.length, 0)
89
- end
90
-
91
- #
92
- # Returns the counts in the bucket this latency falls into.
93
- # The latencies will not be updated.
94
- # @param latency
95
- # @return the bucket content for the latency.
96
- #
97
- def get_bucket_for_latency_millis(latency)
98
- return @latencies[find_bucket_index(latency * 1000)]
99
- end
100
-
101
- #
102
- # Returns the counts in the bucket this latency falls into.
103
- # The latencies will not be updated.
104
- # @param latency
105
- # @return the bucket content for the latency.
106
- #
107
- def get_bucket_for_latency_micros(latency)
108
- return @latencies[find_bucket_index(latency)]
45
+ def self.get_bucket(latency)
46
+ return find_bucket_index(latency * 1000)
109
47
  end
110
48
 
111
49
  private
112
50
 
113
- def find_bucket_index(micros)
51
+ def self.find_bucket_index(micros)
114
52
  if (micros > MAX_LATENCY) then
115
53
  return BUCKETS.length - 1
116
54
  end
@@ -3,12 +3,13 @@
3
3
  module SplitIoClient
4
4
  module Engine
5
5
  class PushManager
6
- def initialize(config, sse_handler, api_key)
6
+ def initialize(config, sse_handler, api_key, telemetry_runtime_producer)
7
7
  @config = config
8
8
  @sse_handler = sse_handler
9
- @auth_api_client = AuthApiClient.new(@config)
9
+ @auth_api_client = AuthApiClient.new(@config, telemetry_runtime_producer)
10
10
  @api_key = api_key
11
11
  @back_off = SplitIoClient::SSE::EventSource::BackOff.new(@config.auth_retry_back_off_base, 1)
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
12
13
  end
13
14
 
14
15
  def start_sse
@@ -19,12 +20,15 @@ module SplitIoClient
19
20
  if response[:push_enabled] && @sse_handler.start(response[:token], response[:channels])
20
21
  schedule_next_token_refresh(response[:exp])
21
22
  @back_off.reset
22
- return
23
+ record_telemetry(response[:exp])
24
+
25
+ return true
23
26
  end
24
27
 
25
28
  stop_sse
26
29
 
27
30
  schedule_next_token_refresh(@back_off.interval) if response[:retry]
31
+ false
28
32
  rescue StandardError => e
29
33
  @config.logger.error("start_sse: #{e.inspect}")
30
34
  end
@@ -50,6 +54,11 @@ module SplitIoClient
50
54
  end
51
55
  end
52
56
  end
57
+
58
+ def record_telemetry(time)
59
+ data = (Time.now.to_f * 1000.0).to_i + (time * 1000.0).to_i
60
+ @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::TOKEN_REFRESH, data)
61
+ end
53
62
  end
54
63
  end
55
64
  end
@@ -3,31 +3,38 @@
3
3
  module SplitIoClient
4
4
  module Engine
5
5
  class SyncManager
6
+ SYNC_MODE_STREAMING = 0
7
+ SYNC_MODE_POLLING = 1
8
+
6
9
  def initialize(
7
10
  repositories,
8
11
  api_key,
9
12
  config,
10
- synchronizer
13
+ synchronizer,
14
+ telemetry_runtime_producer,
15
+ sdk_blocker,
16
+ telemetry_synchronizer
11
17
  )
12
18
  @synchronizer = synchronizer
13
- notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
14
- manager.on_occupancy { |publisher_available| process_occupancy(publisher_available) }
15
- manager.on_push_shutdown { process_push_shutdown }
19
+ notification_manager_keeper = SSE::NotificationManagerKeeper.new(config, telemetry_runtime_producer) do |manager|
20
+ manager.on_action { |action| process_action(action) }
16
21
  end
17
- @sse_handler = SplitIoClient::SSE::SSEHandler.new(
18
- config,
22
+ @sse_handler = SSE::SSEHandler.new(
23
+ { config: config, api_key: api_key },
19
24
  @synchronizer,
20
- repositories[:splits],
21
- repositories[:segments],
22
- notification_manager_keeper
25
+ repositories,
26
+ notification_manager_keeper,
27
+ telemetry_runtime_producer
23
28
  ) do |handler|
24
- handler.on_connected { process_connected }
25
- handler.on_disconnect { |reconnect| process_disconnect(reconnect) }
29
+ handler.on_action { |action| process_action(action) }
26
30
  end
27
31
 
28
- @push_manager = PushManager.new(config, @sse_handler, api_key)
32
+ @push_manager = PushManager.new(config, @sse_handler, api_key, telemetry_runtime_producer)
29
33
  @sse_connected = Concurrent::AtomicBoolean.new(false)
30
34
  @config = config
35
+ @telemetry_runtime_producer = telemetry_runtime_producer
36
+ @sdk_blocker = sdk_blocker
37
+ @telemetry_synchronizer = telemetry_synchronizer
31
38
  end
32
39
 
33
40
  def start
@@ -37,6 +44,8 @@ module SplitIoClient
37
44
  elsif @config.standalone?
38
45
  start_poll
39
46
  end
47
+
48
+ synchronize_telemetry_config
40
49
  end
41
50
 
42
51
  private
@@ -44,38 +53,29 @@ module SplitIoClient
44
53
  # Starts tasks if stream is enabled.
45
54
  def start_stream
46
55
  @config.logger.debug('Starting push mode ...')
47
- stream_start_thread
56
+ @synchronizer.sync_all
48
57
  @synchronizer.start_periodic_data_recording
49
58
 
50
- stream_start_sse_thread
59
+ start_sse_connection_thread
51
60
  end
52
61
 
53
62
  def start_poll
54
63
  @config.logger.debug('Starting polling mode ...')
55
64
  @synchronizer.start_periodic_fetch
56
65
  @synchronizer.start_periodic_data_recording
66
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
57
67
  rescue StandardError => e
58
68
  @config.logger.error("start_poll error : #{e.inspect}")
59
69
  end
60
70
 
61
- # Starts thread which fetch splits and segments once and trigger task to periodic data recording.
62
- def stream_start_thread
63
- @config.threads[:sync_manager_start_stream] = Thread.new do
64
- begin
65
- @synchronizer.sync_all
66
- rescue StandardError => e
67
- @config.logger.error("stream_start_thread error : #{e.inspect}")
68
- end
69
- end
70
- end
71
-
72
71
  # Starts thread which connect to sse and after that fetch splits and segments once.
73
- def stream_start_sse_thread
72
+ def start_sse_connection_thread
74
73
  @config.threads[:sync_manager_start_sse] = Thread.new do
75
74
  begin
76
- @push_manager.start_sse
75
+ connected = @push_manager.start_sse
76
+ @synchronizer.start_periodic_fetch unless connected
77
77
  rescue StandardError => e
78
- @config.logger.error("stream_start_sse_thread error : #{e.inspect}")
78
+ @config.logger.error("start_sse_connection_thread error : #{e.inspect}")
79
79
  end
80
80
  end
81
81
  end
@@ -84,6 +84,49 @@ module SplitIoClient
84
84
  PhusionPassenger.on_event(:starting_worker_process) { |forked| start_stream if forked }
85
85
  end
86
86
 
87
+ def process_action(action)
88
+ case action
89
+ when Constants::PUSH_CONNECTED
90
+ process_connected
91
+ when Constants::PUSH_RETRYABLE_ERROR
92
+ process_disconnect(true)
93
+ when Constants::PUSH_NONRETRYABLE_ERROR
94
+ process_disconnect(false)
95
+ when Constants::PUSH_SUBSYSTEM_DOWN
96
+ process_subsystem_down
97
+ when Constants::PUSH_SUBSYSTEM_READY
98
+ process_subsystem_ready
99
+ when Constants::PUSH_SUBSYSTEM_OFF
100
+ process_push_shutdown
101
+ else
102
+ @config.logger.debug('Incorrect action type.')
103
+ end
104
+ rescue StandardError => e
105
+ @config.logger.error("process_action error: #{e.inspect}")
106
+ end
107
+
108
+ def process_subsystem_ready
109
+ @synchronizer.stop_periodic_fetch
110
+ @synchronizer.sync_all
111
+ @sse_handler.start_workers
112
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_STREAMING)
113
+ end
114
+
115
+ def process_subsystem_down
116
+ @sse_handler.stop_workers
117
+ @synchronizer.start_periodic_fetch
118
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
119
+ end
120
+
121
+ def process_push_shutdown
122
+ @push_manager.stop_sse
123
+ @sse_handler.stop_workers
124
+ @synchronizer.start_periodic_fetch
125
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
126
+ rescue StandardError => e
127
+ @config.logger.error("process_push_shutdown error: #{e.inspect}")
128
+ end
129
+
87
130
  def process_connected
88
131
  if @sse_connected.value
89
132
  @config.logger.debug('Streaming already connected.')
@@ -94,6 +137,7 @@ module SplitIoClient
94
137
  @synchronizer.stop_periodic_fetch
95
138
  @synchronizer.sync_all
96
139
  @sse_handler.start_workers
140
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_STREAMING)
97
141
  rescue StandardError => e
98
142
  @config.logger.error("process_connected error: #{e.inspect}")
99
143
  end
@@ -107,6 +151,7 @@ module SplitIoClient
107
151
  @sse_connected.make_false
108
152
  @sse_handler.stop_workers
109
153
  @synchronizer.start_periodic_fetch
154
+ record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
110
155
 
111
156
  if reconnect
112
157
  @synchronizer.sync_all
@@ -116,26 +161,20 @@ module SplitIoClient
116
161
  @config.logger.error("process_disconnect error: #{e.inspect}")
117
162
  end
118
163
 
119
- def process_occupancy(push_enable)
120
- if push_enable
121
- @synchronizer.stop_periodic_fetch
122
- @synchronizer.sync_all
123
- @sse_handler.start_workers
124
- return
125
- end
126
-
127
- @sse_handler.stop_workers
128
- @synchronizer.start_periodic_fetch
129
- rescue StandardError => e
130
- @config.logger.error("process_occupancy error: #{e.inspect}")
164
+ def record_telemetry(type, data)
165
+ @telemetry_runtime_producer.record_streaming_event(type, data)
131
166
  end
132
167
 
133
- def process_push_shutdown
134
- @push_manager.stop_sse
135
- @sse_handler.stop_workers
136
- @synchronizer.start_periodic_fetch
137
- rescue StandardError => e
138
- @config.logger.error("process_push_shutdown error: #{e.inspect}")
168
+ def synchronize_telemetry_config
169
+ @config.threads[:telemetry_config_sender] = Thread.new do
170
+ begin
171
+ @sdk_blocker.wait_unitil_internal_ready unless @config.consumer?
172
+ @telemetry_synchronizer.synchronize_config
173
+ rescue SplitIoClient::SDKShutdownException
174
+ @telemetry_synchronizer.synchronize_config
175
+ @config.logger.info('Posting Telemetry config due to shutdown')
176
+ end
177
+ end
139
178
  end
140
179
  end
141
180
  end