ff-ruby-server-sdk 1.4.3 → 1.4.5
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.
- checksums.yaml +4 -4
- data/lib/ff/ruby/server/sdk/api/evaluator.rb +18 -1
- data/lib/ff/ruby/server/sdk/api/inner_client.rb +62 -42
- data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +3 -3
- data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +103 -124
- data/lib/ff/ruby/server/sdk/api/update_processor.rb +1 -1
- data/lib/ff/ruby/server/sdk/version.rb +1 -1
- data/lib/ff/ruby/server/sdk.rb +0 -1
- data/scripts/sdk_specs.sh +1 -1
- metadata +4 -5
- data/lib/ff/ruby/server/sdk/api/metrics_event.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4ab2e35a2f4c9b26b92808b6bd3c8bc1b91dd7be7449fa1ed42324dc7a5dd8c
|
4
|
+
data.tar.gz: d426cc3eadd246b1db5fa67ec583fb4f5c38591195579b780687ad5582262e5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 970504a5a04c27c883b12efc6a54cec3486c14789c3a49389a33dad1d8f43fa0f6fb26046628f397368c4cfa9eb1bd6eca91fb28b72062cd7cec8f0c69ca487a
|
7
|
+
data.tar.gz: d4fc7208a07a8fec31990f693358b649c5b8e63f23f9b95c605b3321d6d45e9d7a5934b262fcd248c06d3322de450eb3a3686bac63ebc8bf426e18fba2f32cf5
|
@@ -91,6 +91,20 @@ class Evaluator < Evaluation
|
|
91
91
|
|
92
92
|
flag = @repository.get_flag(identifier)
|
93
93
|
|
94
|
+
# Check if flag exists
|
95
|
+
if flag.nil?
|
96
|
+
# Log a warning if the flag is not found
|
97
|
+
@logger.warn "Flag not found for identifier '#{identifier}'. Serving default variation."
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
|
101
|
+
# Check if the flag's kind matches the expected type
|
102
|
+
unless flag.kind == expected
|
103
|
+
@logger.warn "Flag kind mismatch: expected '#{expected}', but got '#{flag.kind}' for identifier '#{identifier}'. Serving default variation."
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Proceed with prerequisite check if flag type is as expected
|
94
108
|
if flag != nil && flag.kind == expected
|
95
109
|
unless flag.prerequisites.empty?
|
96
110
|
pre_req = check_pre_requisite(flag, target)
|
@@ -103,12 +117,15 @@ class Evaluator < Evaluation
|
|
103
117
|
|
104
118
|
if variation != nil
|
105
119
|
if callback != nil
|
106
|
-
|
120
|
+
feature_name = flag.feature
|
121
|
+
variation_identifier = variation.identifier
|
122
|
+
callback.process_evaluation(feature_name, target, variation_identifier)
|
107
123
|
end
|
108
124
|
return variation
|
109
125
|
end
|
110
126
|
end
|
111
127
|
|
128
|
+
# Returning nil will indicate to callers to serve the default variation
|
112
129
|
nil
|
113
130
|
end
|
114
131
|
|
@@ -50,6 +50,7 @@ class InnerClient < ClientCallback
|
|
50
50
|
|
51
51
|
@connector = connector
|
52
52
|
end
|
53
|
+
@condition = ConditionVariable.new
|
53
54
|
|
54
55
|
@closing = false
|
55
56
|
@failure = false
|
@@ -66,22 +67,34 @@ class InnerClient < ClientCallback
|
|
66
67
|
end
|
67
68
|
|
68
69
|
def bool_variation(identifier, target, default_value)
|
69
|
-
|
70
|
+
unless @initialized
|
71
|
+
log_sdk_not_initialized_warning(identifier, default_value)
|
72
|
+
return default_value
|
73
|
+
end
|
70
74
|
@evaluator.bool_variation(identifier, target, default_value, @evaluator_callback)
|
71
75
|
end
|
72
76
|
|
73
77
|
def string_variation(identifier, target, default_value)
|
74
|
-
|
78
|
+
unless @initialized
|
79
|
+
log_sdk_not_initialized_warning(identifier, default_value)
|
80
|
+
return default_value
|
81
|
+
end
|
75
82
|
@evaluator.string_variation(identifier, target, default_value, @evaluator_callback)
|
76
83
|
end
|
77
84
|
|
78
85
|
def number_variation(identifier, target, default_value)
|
79
|
-
|
86
|
+
unless @initialized
|
87
|
+
log_sdk_not_initialized_warning(identifier, default_value)
|
88
|
+
return default_value
|
89
|
+
end
|
80
90
|
@evaluator.number_variation(identifier, target, default_value, @evaluator_callback)
|
81
91
|
end
|
82
92
|
|
83
93
|
def json_variation(identifier, target, default_value)
|
84
|
-
|
94
|
+
unless @initialized
|
95
|
+
log_sdk_not_initialized_warning(identifier, default_value)
|
96
|
+
return default_value
|
97
|
+
end
|
85
98
|
@evaluator.json_variation(identifier, target, default_value, @evaluator_callback)
|
86
99
|
end
|
87
100
|
|
@@ -109,6 +122,7 @@ class InnerClient < ClientCallback
|
|
109
122
|
def on_auth_failed
|
110
123
|
SdkCodes::warn_auth_failed_srv_defaults @config.logger
|
111
124
|
@initialized = true
|
125
|
+
@condition.signal
|
112
126
|
end
|
113
127
|
|
114
128
|
def close
|
@@ -195,67 +209,74 @@ class InnerClient < ClientCallback
|
|
195
209
|
end
|
196
210
|
|
197
211
|
def on_processor_ready(processor)
|
212
|
+
@my_mutex.synchronize do
|
198
213
|
|
199
|
-
|
214
|
+
if @closing
|
200
215
|
|
201
|
-
|
202
|
-
|
216
|
+
return
|
217
|
+
end
|
203
218
|
|
204
|
-
|
219
|
+
if processor == @poll_processor
|
205
220
|
|
206
|
-
|
207
|
-
|
208
|
-
|
221
|
+
@poller_ready = true
|
222
|
+
@config.logger.debug "PollingProcessor ready"
|
223
|
+
end
|
209
224
|
|
210
|
-
|
225
|
+
if processor == @update_processor
|
211
226
|
|
212
|
-
|
213
|
-
|
214
|
-
|
227
|
+
@stream_ready = true
|
228
|
+
@config.logger.debug "Updater ready"
|
229
|
+
end
|
215
230
|
|
216
|
-
|
231
|
+
if processor == @metrics_processor
|
217
232
|
|
218
|
-
|
219
|
-
|
220
|
-
|
233
|
+
@metrics_ready = true
|
234
|
+
@config.logger.debug "Metrics ready"
|
235
|
+
end
|
221
236
|
|
222
|
-
|
223
|
-
|
224
|
-
|
237
|
+
if (@config.stream_enabled && !@stream_ready) ||
|
238
|
+
(@config.analytics_enabled && !@metrics_ready) ||
|
239
|
+
!@poller_ready
|
225
240
|
|
226
|
-
|
227
|
-
|
241
|
+
return
|
242
|
+
end
|
228
243
|
|
229
|
-
|
244
|
+
SdkCodes.info_sdk_init_ok @config.logger
|
230
245
|
|
231
|
-
|
246
|
+
@condition.signal
|
247
|
+
@initialized = true
|
248
|
+
end
|
232
249
|
end
|
233
250
|
|
234
251
|
def wait_for_initialization(timeout: nil)
|
235
|
-
|
236
|
-
|
252
|
+
SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout)
|
253
|
+
return if @initialized
|
237
254
|
|
255
|
+
@my_mutex.synchronize do
|
238
256
|
start_time = Time.now
|
257
|
+
remaining = timeout ? timeout / 1000.0 : nil # Convert timeout to seconds
|
239
258
|
|
240
259
|
until @initialized
|
241
|
-
# Check if a timeout is specified and has been exceeded
|
242
|
-
if timeout && (Time.now - start_time) > (timeout / 1000.0)
|
243
|
-
@config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms"
|
244
|
-
handle_initialization_failure
|
245
|
-
end
|
246
260
|
|
247
|
-
|
248
|
-
|
261
|
+
# Break if timeout has elapsed
|
262
|
+
if remaining && remaining <= 0
|
263
|
+
@config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms. The SDK will continue to initialize in the background. Default variations will be served until the SDK initializes."
|
264
|
+
break
|
265
|
+
end
|
266
|
+
# Wait for the signal or timeout
|
267
|
+
@condition.wait(@my_mutex, remaining)
|
249
268
|
|
250
|
-
|
251
|
-
|
269
|
+
# Recalculate the remaining time after the wait
|
270
|
+
if timeout
|
271
|
+
elapsed = Time.now - start_time
|
272
|
+
remaining = (timeout / 1000.0) - elapsed
|
273
|
+
end
|
252
274
|
end
|
253
275
|
|
254
|
-
@config.logger.debug "Waiting for initialization has completed"
|
276
|
+
@config.logger.debug "Waiting for initialization has completed" if @initialized
|
255
277
|
end
|
256
278
|
end
|
257
279
|
|
258
|
-
|
259
280
|
protected
|
260
281
|
|
261
282
|
def handle_initialization_failure
|
@@ -316,9 +337,8 @@ class InnerClient < ClientCallback
|
|
316
337
|
|
317
338
|
private
|
318
339
|
|
319
|
-
def
|
320
|
-
|
321
|
-
@my_mutex.synchronize(&block)
|
340
|
+
def log_sdk_not_initialized_warning(identifier, default_value)
|
341
|
+
@config.logger.warn "SDKCODE:6001: SDK is not initialized; serving default variation for bool variation: identifier=#{identifier}, default=#{default_value}"
|
322
342
|
end
|
323
343
|
|
324
344
|
end
|
@@ -21,10 +21,10 @@ class InnerClientFlagEvaluateCallback < FlagEvaluateCallback
|
|
21
21
|
@metrics_processor = metrics_processor
|
22
22
|
end
|
23
23
|
|
24
|
-
def process_evaluation(
|
24
|
+
def process_evaluation(feature_name, target, variation_identifier)
|
25
25
|
|
26
|
-
@logger.debug "Processing evaluation: #{
|
26
|
+
@logger.debug "Processing evaluation: #{feature_name || 'nil feature'}, #{variation_identifier || 'nil variation'}, #{target&.identifier || 'nil target'}"
|
27
27
|
|
28
|
-
@metrics_processor.register_evaluation(target,
|
28
|
+
@metrics_processor.register_evaluation(target, feature_name, variation_identifier)
|
29
29
|
end
|
30
30
|
end
|
@@ -1,50 +1,14 @@
|
|
1
1
|
require "time"
|
2
|
-
require
|
2
|
+
require 'thread'
|
3
|
+
require "set"
|
3
4
|
|
4
5
|
require_relative "../dto/target"
|
5
6
|
require_relative "../../sdk/version"
|
6
7
|
require_relative "../common/closeable"
|
7
8
|
require_relative "../common/sdk_codes"
|
8
|
-
require_relative "../api/metrics_event"
|
9
9
|
require_relative "../api/summary_metrics"
|
10
10
|
|
11
11
|
class MetricsProcessor < Closeable
|
12
|
-
GLOBAL_TARGET = Target.new(identifier: "__global__cf_target", name: "Global Target").freeze
|
13
|
-
|
14
|
-
class FrequencyMap < Concurrent::Map
|
15
|
-
def initialize(options = nil, &block)
|
16
|
-
super
|
17
|
-
end
|
18
|
-
|
19
|
-
def increment(key)
|
20
|
-
compute(key) do |old_value|
|
21
|
-
if old_value == nil;
|
22
|
-
1
|
23
|
-
else
|
24
|
-
old_value + 1
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def get(key)
|
30
|
-
self[key]
|
31
|
-
end
|
32
|
-
|
33
|
-
# TODO Will be removed in V2 in favour of simplified clearing. Currently not used outside of tests.
|
34
|
-
def drain_to_map
|
35
|
-
result = {}
|
36
|
-
each_key do |key|
|
37
|
-
result[key] = 0
|
38
|
-
end
|
39
|
-
result.each_key do |key|
|
40
|
-
value = get_and_set(key, 0)
|
41
|
-
result[key] = value
|
42
|
-
delete_pair(key, 0)
|
43
|
-
end
|
44
|
-
result
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
12
|
def init(connector, config, callback)
|
49
13
|
|
50
14
|
unless connector.kind_of?(Connector)
|
@@ -76,15 +40,20 @@ class MetricsProcessor < Closeable
|
|
76
40
|
@feature_name_attribute = "featureName"
|
77
41
|
@variation_identifier_attribute = "variationIdentifier"
|
78
42
|
|
79
|
-
|
80
|
-
|
81
|
-
# Used for locking the evalution and target metrics maps before we clone them
|
43
|
+
# Evaluation and target metrics
|
82
44
|
@metric_maps_mutex = Mutex.new
|
83
|
-
@evaluation_metrics =
|
84
|
-
@target_metrics =
|
45
|
+
@evaluation_metrics = {}
|
46
|
+
@target_metrics = {}
|
85
47
|
|
86
|
-
# Keep track of targets that have already been sent to avoid sending them again
|
87
|
-
|
48
|
+
# Keep track of targets that have already been sent to avoid sending them again. We track a max 500K targets
|
49
|
+
# to prevent unbounded growth.
|
50
|
+
@seen_targets_mutex = Mutex.new
|
51
|
+
@seen_targets = Set.new
|
52
|
+
@max_seen_targets = 500000
|
53
|
+
@seen_targets_full = false
|
54
|
+
|
55
|
+
# Mutex to protect aggregation and sending metrics at the end of an interval
|
56
|
+
@send_data_mutex = Mutex.new
|
88
57
|
|
89
58
|
@callback.on_metrics_ready
|
90
59
|
end
|
@@ -107,8 +76,8 @@ class MetricsProcessor < Closeable
|
|
107
76
|
@config.logger.debug "Closing metrics processor"
|
108
77
|
end
|
109
78
|
|
110
|
-
def register_evaluation(target,
|
111
|
-
register_evaluation_metric(
|
79
|
+
def register_evaluation(target, feature_name, variation_identifier)
|
80
|
+
register_evaluation_metric(feature_name, variation_identifier)
|
112
81
|
if target
|
113
82
|
register_target_metric(target)
|
114
83
|
end
|
@@ -116,128 +85,129 @@ class MetricsProcessor < Closeable
|
|
116
85
|
|
117
86
|
private
|
118
87
|
|
119
|
-
def register_evaluation_metric(
|
88
|
+
def register_evaluation_metric(feature_name, variation_identifier)
|
120
89
|
# Guard clause to ensure feature_config, @global_target, and variation are valid.
|
121
|
-
# While they should be, this adds protection for an edge case we are seeing with
|
122
|
-
#
|
123
|
-
# these checks in a future release.
|
124
|
-
|
125
|
-
@config.logger.warn("Skipping invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{
|
90
|
+
# While they should be, this adds protection for an edge case we are seeing where the the ConcurrentMap (now replaced with our own thread safe hash)
|
91
|
+
# seemed to be accessing invalid areas of memory and seg faulting.
|
92
|
+
# Issue being tracked in FFM-12192, and once resolved, can remove these checks in a future release && once the issue is resolved.
|
93
|
+
unless feature_name && !feature_name.empty?
|
94
|
+
@config.logger.warn("Skipping invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{feature_name.inspect}")
|
126
95
|
return
|
127
96
|
end
|
128
97
|
|
129
|
-
|
130
|
-
@config.logger.warn("Skipping
|
98
|
+
unless variation_identifier && !variation_identifier.empty?
|
99
|
+
@config.logger.warn("Skipping iInvalid MetricsEvent: variation is missing or incomplete. variation=#{variation_identifier.inspect}")
|
131
100
|
return
|
132
101
|
end
|
133
102
|
|
134
|
-
if variation.nil? || !variation.respond_to?(:identifier) || variation.identifier.nil?
|
135
|
-
@config.logger.warn("Skipping iInvalid MetricsEvent: variation is missing or incomplete. variation=#{variation.inspect}")
|
136
|
-
return
|
137
|
-
end
|
138
|
-
|
139
|
-
event = MetricsEvent.new(feature_config, GLOBAL_TARGET, variation, @config.logger)
|
140
103
|
@metric_maps_mutex.synchronize do
|
141
|
-
|
104
|
+
key = "#{feature_name}\0#{variation_identifier}"
|
105
|
+
@evaluation_metrics[key] = (@evaluation_metrics[key] || 0) + 1
|
142
106
|
end
|
143
107
|
end
|
144
108
|
|
145
109
|
def register_target_metric(target)
|
146
|
-
if target.is_private
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
110
|
+
return if target.is_private
|
111
|
+
|
112
|
+
add_to_target_metrics = @seen_targets_mutex.synchronize do
|
113
|
+
# If the set is full, directly allow adding to target_metrics
|
114
|
+
if @seen_targets_full
|
115
|
+
true
|
116
|
+
elsif @seen_targets.include?(target.identifier)
|
117
|
+
false
|
118
|
+
else
|
119
|
+
@seen_targets.add(target.identifier)
|
120
|
+
@seen_targets_full = @seen_targets.size >= @max_seen_targets
|
121
|
+
true
|
122
|
+
end
|
154
123
|
end
|
155
124
|
|
125
|
+
# Add to target_metrics if marked for inclusion
|
156
126
|
@metric_maps_mutex.synchronize do
|
157
|
-
@target_metrics
|
158
|
-
end
|
127
|
+
@target_metrics[target.identifier] = target
|
128
|
+
end if add_to_target_metrics
|
159
129
|
end
|
160
130
|
|
131
|
+
|
161
132
|
def run_one_iteration
|
162
133
|
send_data_and_reset_cache(@evaluation_metrics, @target_metrics)
|
163
134
|
end
|
164
135
|
|
165
136
|
def send_data_and_reset_cache(evaluation_metrics_map, target_metrics_map)
|
166
|
-
|
167
|
-
|
168
|
-
# metrics to be processed in an atomic unit.
|
169
|
-
evaluation_metrics_map_clone, target_metrics_map_clone = @metric_maps_mutex.synchronize do
|
170
|
-
|
171
|
-
clone_evaluations = Concurrent::Map.new
|
172
|
-
clone_targets = Concurrent::Map.new
|
173
|
-
# Clone and clear evaluation metrics map
|
174
|
-
evaluation_metrics_map.each_pair do |key, value|
|
175
|
-
clone_evaluations[key] = value
|
176
|
-
end
|
177
|
-
|
178
|
-
evaluation_metrics_map.clear
|
137
|
+
@send_data_mutex.synchronize do
|
138
|
+
begin
|
179
139
|
|
180
|
-
|
181
|
-
|
182
|
-
|
140
|
+
evaluation_metrics_map_clone, target_metrics_map_clone = @metric_maps_mutex.synchronize do
|
141
|
+
# Check if we have metrics to send; if not, skip sending metrics
|
142
|
+
if evaluation_metrics_map.empty? && target_metrics_map.empty?
|
143
|
+
@config.logger.debug "No metrics to send. Skipping sending metrics this interval"
|
144
|
+
return
|
145
|
+
end
|
183
146
|
|
184
|
-
|
147
|
+
# Deep clone the evaluation metrics
|
148
|
+
cloned_evaluations = Marshal.load(Marshal.dump(evaluation_metrics_map)).freeze
|
149
|
+
evaluation_metrics_map.clear
|
185
150
|
|
186
|
-
|
151
|
+
# Deep clone the target metrics
|
152
|
+
cloned_targets = Marshal.load(Marshal.dump(target_metrics_map)).freeze
|
153
|
+
target_metrics_map.clear
|
154
|
+
[cloned_evaluations, cloned_targets]
|
187
155
|
|
188
|
-
|
156
|
+
end
|
189
157
|
|
190
|
-
|
158
|
+
metrics = prepare_summary_metrics_body(evaluation_metrics_map_clone, target_metrics_map_clone)
|
191
159
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
160
|
+
unless metrics.metrics_data.empty?
|
161
|
+
start_time = (Time.now.to_f * 1000).to_i
|
162
|
+
@connector.post_metrics(metrics)
|
163
|
+
end_time = (Time.now.to_f * 1000).to_i
|
164
|
+
if end_time - start_time > @config.metrics_service_acceptable_duration
|
165
|
+
@config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
rescue => e
|
169
|
+
@config.logger.warn "Error when preparing and sending metrics: #{e.message}"
|
170
|
+
@config.logger.warn e.backtrace&.join("\n") || "No backtrace available"
|
198
171
|
end
|
199
172
|
end
|
200
173
|
end
|
201
174
|
|
202
|
-
def prepare_summary_metrics_body(
|
175
|
+
def prepare_summary_metrics_body(evaluation_metrics_clone, target_metrics_clone)
|
203
176
|
metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
|
204
177
|
|
205
178
|
total_count = 0
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
#
|
210
|
-
#
|
211
|
-
|
212
|
-
|
213
|
-
#
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
# If any components are missing, log a detailed warning and skip processing
|
220
|
-
unless missing_components.empty?
|
221
|
-
@config.logger.warn "Skipping invalid metrics event: missing #{missing_components.join(', ')} in key: #{key.inspect}, full details: #{key.inspect}"
|
179
|
+
evaluation_metrics_clone.each do |key, value|
|
180
|
+
feature_name, variation_identifier = key.split("\0", 2)
|
181
|
+
|
182
|
+
# Although feature_name and variation_identifier should always be present,
|
183
|
+
# this guard provides protection against an edge case where keys reference
|
184
|
+
# other objects in memory. In versions <= 1.4.4, we were keying on the MetricsEvent
|
185
|
+
# class (now deleted). To remediate this, we have transitioned to using strings as keys.
|
186
|
+
# This issue is being tracked in FFM-12192. Once resolved, these checks can be safely
|
187
|
+
# removed in a future release.
|
188
|
+
# If any required data is missing, log a detailed warning and skip processing.
|
189
|
+
unless feature_name && variation_identifier && value.is_a?(Integer) && value > 0
|
190
|
+
@config.logger.warn "Skipping invalid metrics event: missing or invalid feature_name, variation_identifier, or count. Key: #{key.inspect}, Count: #{value.inspect}"
|
222
191
|
next
|
223
192
|
end
|
224
193
|
|
225
194
|
total_count += value
|
195
|
+
|
226
196
|
metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
|
227
197
|
metrics_data.timestamp = (Time.now.to_f * 1000).to_i
|
228
198
|
metrics_data.count = value
|
229
199
|
metrics_data.metrics_type = "FFMETRICS"
|
230
|
-
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value =>
|
231
|
-
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value =>
|
200
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => feature_name }))
|
201
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => variation_identifier }))
|
232
202
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target_identifier }))
|
233
203
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
|
234
204
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
|
235
205
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
|
236
206
|
metrics.metrics_data.push(metrics_data)
|
237
207
|
end
|
238
|
-
@config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{
|
208
|
+
@config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{evaluation_metrics_clone.size}. target_data count is #{target_metrics_clone.size}"
|
239
209
|
|
240
|
-
|
210
|
+
target_metrics_clone.each_pair do |_, value|
|
241
211
|
add_target_data(metrics, value)
|
242
212
|
end
|
243
213
|
|
@@ -276,18 +246,27 @@ class MetricsProcessor < Closeable
|
|
276
246
|
@running = true
|
277
247
|
@thread = Thread.new do
|
278
248
|
@config.logger.debug "Async started: " + self.to_s
|
279
|
-
|
249
|
+
mutex = Mutex.new
|
250
|
+
condition = ConditionVariable.new
|
251
|
+
|
252
|
+
while @ready
|
280
253
|
unless @initialized
|
281
254
|
@initialized = true
|
282
|
-
SdkCodes::info_metrics_thread_started
|
255
|
+
SdkCodes::info_metrics_thread_started(@config.logger)
|
256
|
+
end
|
257
|
+
|
258
|
+
mutex.synchronize do
|
259
|
+
# Wait for the specified interval or until notified
|
260
|
+
condition.wait(mutex, @config.frequency)
|
283
261
|
end
|
284
|
-
|
285
|
-
|
262
|
+
|
263
|
+
# Re-check @ready before running the iteration
|
264
|
+
run_one_iteration if @ready
|
286
265
|
end
|
287
266
|
end
|
288
|
-
@thread.run
|
289
267
|
end
|
290
268
|
|
269
|
+
|
291
270
|
def stop_async
|
292
271
|
@ready = false
|
293
272
|
@initialized = false
|
data/lib/ff/ruby/server/sdk.rb
CHANGED
@@ -17,7 +17,6 @@ require_relative "sdk/api/cf_client"
|
|
17
17
|
require_relative "sdk/api/evaluation"
|
18
18
|
require_relative "sdk/api/inner_client"
|
19
19
|
require_relative "sdk/api/auth_service"
|
20
|
-
require_relative "sdk/api/metrics_event"
|
21
20
|
require_relative "sdk/api/default_cache"
|
22
21
|
require_relative "sdk/api/config_builder"
|
23
22
|
require_relative "sdk/api/file_map_store"
|
data/scripts/sdk_specs.sh
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ff-ruby-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 'Miloš Vasić, cyr.: Милош Васић'
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -142,14 +142,14 @@ dependencies:
|
|
142
142
|
requirements:
|
143
143
|
- - '='
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version: 0.1.
|
145
|
+
version: 0.1.7
|
146
146
|
type: :runtime
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - '='
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: 0.1.
|
152
|
+
version: 0.1.7
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: typhoeus
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -253,7 +253,6 @@ files:
|
|
253
253
|
- lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb
|
254
254
|
- lib/ff/ruby/server/sdk/api/inner_client_updater.rb
|
255
255
|
- lib/ff/ruby/server/sdk/api/metrics_callback.rb
|
256
|
-
- lib/ff/ruby/server/sdk/api/metrics_event.rb
|
257
256
|
- lib/ff/ruby/server/sdk/api/metrics_processor.rb
|
258
257
|
- lib/ff/ruby/server/sdk/api/operators.rb
|
259
258
|
- lib/ff/ruby/server/sdk/api/polling_processor.rb
|
@@ -1,38 +0,0 @@
|
|
1
|
-
class MetricsEvent
|
2
|
-
|
3
|
-
attr_accessor :feature_config, :target, :variation
|
4
|
-
|
5
|
-
def initialize(feature_config, target, variation, logger)
|
6
|
-
|
7
|
-
@target = target
|
8
|
-
@variation = variation
|
9
|
-
@feature_config = feature_config
|
10
|
-
@logger = logger
|
11
|
-
freeze
|
12
|
-
end
|
13
|
-
|
14
|
-
def ==(other)
|
15
|
-
eql?(other)
|
16
|
-
end
|
17
|
-
|
18
|
-
def eql?(other)
|
19
|
-
# Guard clause other is the same type.
|
20
|
-
# While it should be, this adds protection for an edge case we are seeing with very large
|
21
|
-
# project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
|
22
|
-
# these checks in a future release.
|
23
|
-
unless other.is_a?(MetricsEvent)
|
24
|
-
@logger.warn("Warning: Attempted to compare MetricsEvent with #{other.class.name}" )
|
25
|
-
return false
|
26
|
-
end
|
27
|
-
|
28
|
-
feature_config.feature == other.feature_config.feature and
|
29
|
-
variation.identifier == other.variation.identifier and
|
30
|
-
target.identifier == other.target.identifier
|
31
|
-
end
|
32
|
-
|
33
|
-
def hash
|
34
|
-
feature_config.feature.hash | variation.identifier.hash | target.identifier.hash
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
end
|