ff-ruby-server-sdk 1.0.6 → 1.1.1

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.
@@ -12,14 +12,14 @@ require_relative "inner_client_repository_callback"
12
12
  require_relative "inner_client_flag_evaluate_callback"
13
13
 
14
14
  require_relative "../connector/harness_connector"
15
+ require_relative "../common/sdk_codes"
15
16
 
16
17
  class InnerClient < ClientCallback
17
18
 
18
19
  def initialize(api_key = nil, config = nil, connector = nil)
19
20
 
20
21
  if api_key == nil || api_key == ""
21
-
22
- raise "SDK key is not provided"
22
+ SdkCodes::raise_missing_sdk_key config.logger
23
23
  end
24
24
 
25
25
  if config == nil
@@ -87,7 +87,7 @@ class InnerClient < ClientCallback
87
87
 
88
88
  def on_auth_success
89
89
 
90
- @config.logger.info "SDK successfully logged in"
90
+ SdkCodes::info_sdk_auth_ok @config.logger
91
91
 
92
92
  if @closing
93
93
 
@@ -97,17 +97,20 @@ class InnerClient < ClientCallback
97
97
  @poll_processor.start
98
98
 
99
99
  if @config.stream_enabled
100
-
101
100
  @update_processor.start
102
101
  end
103
102
 
104
103
  if @config.analytics_enabled
105
-
106
104
  @metrics_processor.start
107
105
  end
108
106
 
109
107
  end
110
108
 
109
+ def on_auth_failed
110
+ SdkCodes::warn_auth_failed_srv_defaults @config.logger
111
+ @initialized = true
112
+ end
113
+
111
114
  def close
112
115
 
113
116
  @config.logger.info "Closing the client: " + self.to_s
@@ -201,19 +204,19 @@ class InnerClient < ClientCallback
201
204
  if processor == @poll_processor
202
205
 
203
206
  @poller_ready = true
204
- @config.logger.info "PollingProcessor ready"
207
+ @config.logger.debug "PollingProcessor ready"
205
208
  end
206
209
 
207
210
  if processor == @update_processor
208
211
 
209
212
  @stream_ready = true
210
- @config.logger.info "Updater ready"
213
+ @config.logger.debug "Updater ready"
211
214
  end
212
215
 
213
216
  if processor == @metrics_processor
214
217
 
215
218
  @metrics_ready = true
216
- @config.logger.info "Metrics ready"
219
+ @config.logger.debug "Metrics ready"
217
220
  end
218
221
 
219
222
  if (@config.stream_enabled && !@stream_ready) ||
@@ -223,21 +226,16 @@ class InnerClient < ClientCallback
223
226
  return
224
227
  end
225
228
 
226
- @config.logger.info "All processors now ready"
229
+ SdkCodes.info_sdk_init_ok @config.logger
227
230
 
228
231
  @initialized = true
229
-
230
- # TODO: notify - Reactivity support
231
- # TODO: notify_consumers - Reactivity support
232
-
233
- @config.logger.info "Initialization is completed"
234
232
  end
235
233
 
236
234
  def wait_for_initialization
237
235
 
238
236
  synchronize do
239
237
 
240
- @config.logger.info "Waiting for the initialization to finish"
238
+ @config.logger.debug "Waiting for initialization to finish"
241
239
 
242
240
  until @initialized
243
241
 
@@ -249,7 +247,7 @@ class InnerClient < ClientCallback
249
247
  raise "Initialization failed"
250
248
  end
251
249
 
252
- @config.logger.info "Waiting for the initialization completed"
250
+ @config.logger.debug "Waiting for initialization has completed"
253
251
  end
254
252
  end
255
253
 
@@ -257,10 +255,6 @@ class InnerClient < ClientCallback
257
255
 
258
256
  def setup
259
257
 
260
- @config.logger.info "SDK is not initialized yet! If store is used then values will be loaded from store" +
261
- " otherwise default values will be used in meantime. You can use waitForInitialization method for SDK" +
262
- " to be ready."
263
-
264
258
  @repository = StorageRepository.new(@config.cache, @repository_callback, @config.store, @config.logger)
265
259
 
266
260
  @metrics_callback = InnerClientMetricsCallback.new(self, @config.logger)
@@ -268,12 +262,13 @@ class InnerClient < ClientCallback
268
262
  @metrics_processor.init(@connector, @config, @metrics_callback)
269
263
 
270
264
  @evaluator = Evaluator.new(@repository, logger = @config.logger)
271
- @evaluator_callback = InnerClientFlagEvaluateCallback.new(@metrics_processor, logger = @config.logger)
272
265
 
273
- @auth_service = AuthService.new(
266
+ if @config.analytics_enabled
267
+ @evaluator_callback = InnerClientFlagEvaluateCallback.new(@metrics_processor, logger = @config.logger)
268
+ end
274
269
 
270
+ @auth_service = AuthService.new(
275
271
  connector = @connector,
276
- poll_interval_in_sec = @config.poll_interval_in_seconds,
277
272
  callback = self,
278
273
  logger = @config.logger
279
274
  )
@@ -312,4 +307,5 @@ class InnerClient < ClientCallback
312
307
 
313
308
  @my_mutex.synchronize(&block)
314
309
  end
310
+
315
311
  end
@@ -25,6 +25,6 @@ class InnerClientFlagEvaluateCallback < FlagEvaluateCallback
25
25
 
26
26
  @logger.debug "Processing evaluation: " + feature_config.feature.to_s + ", " + target.identifier.to_s
27
27
 
28
- @metrics_processor.push_to_queue(target, feature_config, variation)
28
+ @metrics_processor.register_evaluation(target, feature_config, variation)
29
29
  end
30
30
  end
@@ -53,7 +53,7 @@ class InnerClientUpdater < Updater
53
53
 
54
54
  def on_error
55
55
 
56
- @logger.error "Error occurred"
56
+ @logger.error "InnerClientUpdater error occurred"
57
57
  end
58
58
 
59
59
  def update(message)
@@ -2,15 +2,27 @@ class MetricsEvent
2
2
 
3
3
  attr_accessor :feature_config, :target, :variation
4
4
 
5
- def initialize(
6
-
7
- feature_config,
8
- target,
9
- variation
10
- )
5
+ def initialize(feature_config, target, variation)
11
6
 
12
7
  @target = target
13
8
  @variation = variation
14
9
  @feature_config = feature_config
10
+ freeze
11
+ end
12
+
13
+ def ==(other)
14
+ eql?(other)
15
+ end
16
+
17
+ def eql?(other)
18
+ feature_config.feature == other.feature_config.feature and
19
+ variation.identifier == other.variation.identifier and
20
+ target.identifier == other.target.identifier
15
21
  end
22
+
23
+ def hash
24
+ feature_config.feature.hash | variation.identifier.hash | target.identifier.hash
25
+ end
26
+
27
+
16
28
  end
@@ -4,30 +4,53 @@ require "concurrent-ruby"
4
4
  require_relative "../dto/target"
5
5
  require_relative "../../sdk/version"
6
6
  require_relative "../common/closeable"
7
+ require_relative "../common/sdk_codes"
7
8
  require_relative "../api/metrics_event"
8
9
  require_relative "../api/summary_metrics"
9
10
 
10
11
  class MetricsProcessor < Closeable
11
12
 
12
- def init(
13
+ class FrequencyMap < Concurrent::Map
14
+ def initialize(options = nil, &block)
15
+ super
16
+ end
13
17
 
14
- connector,
15
- config,
16
- callback
17
- )
18
+ def increment(key)
19
+ compute(key) do |old_value|
20
+ if old_value == nil; 1 else old_value + 1 end
21
+ end
22
+ end
18
23
 
19
- unless connector.kind_of?(Connector)
24
+ def get(key)
25
+ self[key]
26
+ end
27
+
28
+ def drain_to_map
29
+ result = {}
30
+ each_key do |key|
31
+ result[key] = 0
32
+ end
33
+ result.each_key do |key|
34
+ value = get_and_set(key, 0)
35
+ result[key] = value
36
+ delete_pair(key, 0)
37
+ end
38
+ result
39
+ end
40
+ end
20
41
 
42
+
43
+ def init(connector, config, callback)
44
+
45
+ unless connector.kind_of?(Connector)
21
46
  raise "The 'connector' must be of '" + Connector.to_s + "' data type"
22
47
  end
23
48
 
24
49
  unless callback.kind_of?(MetricsCallback)
25
-
26
50
  raise "The 'callback' must be of '" + MetricsCallback.to_s + "' data type"
27
51
  end
28
52
 
29
53
  unless config.kind_of?(Config)
30
-
31
54
  raise "The 'config' must be of '" + Config.to_s + "' data type"
32
55
  end
33
56
 
@@ -51,247 +74,151 @@ class MetricsProcessor < Closeable
51
74
  @feature_name_attribute = "featureName"
52
75
  @variation_identifier_attribute = "variationIdentifier"
53
76
 
54
- @queue = SizedQueue.new(@config.buffer_size)
55
- @executor = Concurrent::FixedThreadPool.new(100)
77
+ @executor = Concurrent::FixedThreadPool.new(10)
78
+
79
+ @frequency_map = FrequencyMap.new
80
+
81
+ @max_buffer_size = config.buffer_size - 1
56
82
 
57
83
  @callback.on_metrics_ready
58
84
  end
59
85
 
60
86
  def start
61
-
62
- @config.logger.info "Starting metrics processor with request interval: " + @config.frequency.to_s
87
+ @config.logger.debug "Starting metrics processor with request interval: " + @config.frequency.to_s
63
88
  start_async
64
89
  end
65
90
 
66
91
  def stop
67
-
68
- @config.logger.info "Stopping metrics processor"
92
+ @config.logger.debug "Stopping metrics processor"
69
93
  stop_async
70
94
  end
71
95
 
72
96
  def close
73
-
74
97
  stop
75
- @config.logger.info "Closing metrics processor"
76
- end
77
-
78
- def push_to_queue(
79
-
80
- target,
81
- feature_config,
82
- variation
83
- )
84
-
85
- @executor.post do
86
-
87
- @config.logger.debug "Pushing to the metrics queue: START"
88
-
89
- event = MetricsEvent.new(feature_config, target, variation)
90
- @queue.push(event)
91
-
92
- @config.logger.debug "Pushing to the metrics queue: END, queue size: " + @queue.size.to_s
93
-
94
- end
98
+ @config.logger.debug "Closing metrics processor"
95
99
  end
96
100
 
97
- def send_data_and_reset_cache(data)
98
-
99
- @config.logger.debug "Reading from queue and building cache"
100
-
101
- @jar_version = get_version
102
-
103
- unless data.empty?
104
-
105
- map = {}
101
+ def register_evaluation(target, feature_config, variation)
106
102
 
107
- data.each do |event|
108
-
109
- new_value = 1
110
- current = map[event]
111
-
112
- if current != nil
113
-
114
- new_value = current + 1
115
- end
116
-
117
- map[event] = new_value
118
- end
119
-
120
- metrics = prepare_summary_metrics_body(map)
121
-
122
- if !metrics.metrics_data.empty? && !metrics.target_data.empty?
123
-
124
- start_time = (Time.now.to_f * 1000).to_i
125
-
126
- @connector.post_metrics(metrics)
127
-
128
- end_time = (Time.now.to_f * 1000).to_i
129
-
130
- if end_time - start_time > @config.metrics_service_acceptable_duration
131
-
132
- @config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
133
- end
103
+ if @frequency_map.size > @max_buffer_size
104
+ @config.logger.warn "metrics buffer is full #{@frequency_map.size} - flushing metrics"
105
+ @executor.post do
106
+ run_one_iteration
134
107
  end
135
-
136
- @global_target_set.merge(@staging_target_set)
137
- @staging_target_set.clear
138
108
  end
109
+
110
+ event = MetricsEvent.new(feature_config, target, variation)
111
+ @frequency_map.increment event
139
112
  end
140
113
 
141
- protected
114
+ private
142
115
 
143
116
  def run_one_iteration
117
+ send_data_and_reset_cache @frequency_map.drain_to_map
144
118
 
145
- @config.logger.debug "Async metrics iteration: " + @thread.to_s
146
-
147
- data = []
119
+ @config.logger.debug "metrics: frequency map size #{@frequency_map.size}. global target size #{@global_target_set.size}"
120
+ end
148
121
 
149
- until @queue.empty?
122
+ def send_data_and_reset_cache(map)
123
+ metrics = prepare_summary_metrics_body(map)
150
124
 
151
- item = @queue.pop
152
- data.push(item)
125
+ if !metrics.metrics_data.empty? && !metrics.target_data.empty?
126
+ start_time = (Time.now.to_f * 1000).to_i
127
+ @connector.post_metrics(metrics)
128
+ end_time = (Time.now.to_f * 1000).to_i
129
+ if end_time - start_time > @config.metrics_service_acceptable_duration
130
+ @config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
131
+ end
153
132
  end
154
133
 
155
- send_data_and_reset_cache(data)
156
- end
134
+ @global_target_set.merge(@staging_target_set)
135
+ @staging_target_set.clear
157
136
 
158
- def prepare_summary_metrics_body(data)
137
+ end
159
138
 
160
- summary_metrics_data = {}
139
+ def prepare_summary_metrics_body(freq_map)
161
140
  metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
162
-
163
- add_target_data(
164
-
165
- metrics,
166
- Target.new(
167
-
168
- name = @global_target_name,
169
- identifier = @global_target
170
- )
171
- )
172
-
173
- data.each do |key, value|
174
-
175
- target = key.target
176
-
177
- add_target_data(metrics, target)
178
-
179
- summary_metrics = prepare_summary_metrics_key(key)
180
-
181
- summary_metrics_data[summary_metrics] = value
141
+ add_target_data(metrics, Target.new(name = @global_target_name, identifier = @global_target))
142
+ freq_map.each_key do |key|
143
+ add_target_data(metrics, key.target)
182
144
  end
183
-
184
- summary_metrics_data.each do |key, value|
185
-
145
+ total_count = 0
146
+ freq_map.each do |key, value|
147
+ total_count += value
186
148
  metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
187
149
  metrics_data.timestamp = (Time.now.to_f * 1000).to_i
188
150
  metrics_data.count = value
189
151
  metrics_data.metrics_type = "FFMETRICS"
190
- metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_name }))
191
- metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation_identifier }))
152
+ metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_config.feature }))
153
+ metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation.identifier }))
192
154
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target }))
193
155
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
194
156
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
195
157
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
196
-
197
158
  metrics.metrics_data.push(metrics_data)
198
159
  end
160
+ @config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{freq_map.size}"
199
161
 
200
162
  metrics
201
163
  end
202
164
 
203
- private
204
-
205
- def start_async
206
-
207
- @config.logger.debug "Async starting: " + self.to_s
208
-
209
- @ready = true
210
-
211
- @thread = Thread.new do
212
-
213
- @config.logger.debug "Async started: " + self.to_s
214
-
215
- while @ready do
216
-
217
- unless @initialized
218
-
219
- @initialized = true
220
- @config.logger.info "Metrics processor initialized"
221
- end
222
-
223
- sleep(@config.frequency)
224
-
225
- run_one_iteration
226
- end
227
- end
228
-
229
- @thread.run
230
- end
231
-
232
- def stop_async
233
-
234
- @queue.clear
235
-
236
- @ready = false
237
- @initialized = false
238
- end
239
-
240
- def prepare_summary_metrics_key(key)
241
-
242
- SummaryMetrics.new(
243
-
244
- feature_name = key.feature_config.feature,
245
- variation_identifier = key.variation.identifier,
246
- variation_value = key.variation.value
247
- )
248
- end
249
-
250
165
  def add_target_data(metrics, target)
251
166
 
252
167
  target_data = OpenapiClient::TargetData.new({ :attributes => [] })
253
168
  private_attributes = target.private_attributes
254
169
 
255
170
  if !@staging_target_set.include?(target) && !@global_target_set.include?(target) && !target.is_private
256
-
257
171
  @staging_target_set.add(target)
258
-
259
172
  attributes = target.attributes
260
-
261
173
  attributes.each do |k, v|
262
-
263
174
  key_value = OpenapiClient::KeyValue.new
264
-
265
175
  if !private_attributes.empty?
266
-
267
176
  unless private_attributes.include?(k)
268
-
269
177
  key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
270
178
  end
271
179
  else
272
-
273
180
  key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
274
181
  end
275
-
276
182
  target_data.attributes.push(key_value)
277
183
  end
278
-
279
184
  target_data.identifier = target.identifier
280
-
281
185
  if target.name == nil || target.name == ""
282
-
283
186
  target_data.name = target.identifier
284
187
  else
285
-
286
188
  target_data.name = target.name
287
189
  end
288
-
289
190
  metrics.target_data.push(target_data)
290
191
  end
291
192
  end
292
193
 
293
- def get_version
194
+ def start_async
195
+ @config.logger.debug "Async starting: " + self.to_s
196
+ @ready = true
197
+ @thread = Thread.new do
198
+ @config.logger.debug "Async started: " + self.to_s
199
+ while @ready do
200
+ unless @initialized
201
+ @initialized = true
202
+ SdkCodes::info_metrics_thread_started @config.logger
203
+ end
204
+ sleep(@config.frequency)
205
+ run_one_iteration
206
+ end
207
+ end
208
+ @thread.run
209
+ end
294
210
 
211
+ def stop_async
212
+ @ready = false
213
+ @initialized = false
214
+ end
215
+
216
+ def get_version
295
217
  Ff::Ruby::Server::Sdk::VERSION
296
218
  end
219
+
220
+ def get_frequency_map
221
+ @frequency_map
222
+ end
223
+
297
224
  end
@@ -29,13 +29,13 @@ class PollingProcessor < Closeable
29
29
 
30
30
  flags = []
31
31
 
32
- @logger.info "Fetching flags started"
32
+ @logger.debug "Fetching flags started"
33
33
 
34
34
  result = @connector.get_flags
35
35
 
36
36
  if result != nil
37
37
 
38
- @logger.info "Flags are fetched"
38
+ @logger.debug "Flags are fetched"
39
39
 
40
40
  result.each { |fc|
41
41
 
@@ -47,7 +47,7 @@ class PollingProcessor < Closeable
47
47
  }
48
48
  end
49
49
 
50
- @logger.info "Fetching flags finished"
50
+ @logger.debug "Fetching flags finished"
51
51
 
52
52
  flags
53
53
  end
@@ -56,13 +56,13 @@ class PollingProcessor < Closeable
56
56
 
57
57
  segments = []
58
58
 
59
- @logger.info "Fetching segments started"
59
+ @logger.debug "Fetching segments started"
60
60
 
61
61
  result = @connector.get_segments
62
62
 
63
63
  if result != nil
64
64
 
65
- @logger.info "Segments are fetched"
65
+ @logger.debug "Segments are fetched"
66
66
 
67
67
  result.each { |s|
68
68
 
@@ -74,7 +74,7 @@ class PollingProcessor < Closeable
74
74
  }
75
75
  end
76
76
 
77
- @logger.info "Fetching segments finished"
77
+ @logger.debug "Fetching segments finished"
78
78
 
79
79
  segments
80
80
  end
@@ -106,7 +106,7 @@ class PollingProcessor < Closeable
106
106
  unless @initialized
107
107
 
108
108
  @initialized = true
109
- @logger.info "PollingProcessor initialized"
109
+ SdkCodes::info_poll_started(@logger, @poll_interval_in_sec)
110
110
 
111
111
  if @callback != nil
112
112
 
@@ -137,17 +137,16 @@ class PollingProcessor < Closeable
137
137
 
138
138
  def start
139
139
 
140
- @logger.info "Starting PollingProcessor with request interval: " + @poll_interval_in_sec.to_s
140
+ @logger.debug "Starting PollingProcessor with request interval: " + @poll_interval_in_sec.to_s
141
141
  start_async
142
142
  end
143
143
 
144
144
  def stop
145
145
 
146
- @logger.info "Stopping PollingProcessor"
146
+ @logger.debug "Stopping PollingProcessor"
147
147
  stop_async
148
148
  unless @ready
149
-
150
- @logger.info "PollingProcessor stopped"
149
+ SdkCodes::info_polling_stopped @logger
151
150
  end
152
151
  end
153
152
 
@@ -28,7 +28,7 @@ class UpdateProcessor < Closeable
28
28
 
29
29
  def start
30
30
 
31
- @logger.info "Starting updater (EventSource)"
31
+ @logger.debug "Starting updater (EventSource)"
32
32
 
33
33
  if @updater != nil
34
34
 
@@ -105,8 +105,6 @@ class UpdateProcessor < Closeable
105
105
 
106
106
  def process_flag(message)
107
107
 
108
- @logger.info "Processing flag message: " + message.to_s
109
-
110
108
  config = @connector.get_flag(message["identifier"])
111
109
 
112
110
  if config != nil