ff-ruby-server-sdk 1.0.6 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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