Skip to content

Commit 6ca9329

Browse files
committed
FFM-12192 Only create global target once / Add guard for nil target, and add test for invalid metric / Fix tests
1 parent 16e7023 commit 6ca9329

File tree

7 files changed

+71
-35
lines changed

7 files changed

+71
-35
lines changed

lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def initialize(metrics_processor, logger = nil)
2323

2424
def process_evaluation(feature_config, target, variation)
2525

26-
@logger.debug "Processing evaluation: " + feature_config.feature.to_s + ", " + target.identifier.to_s
26+
@logger.debug "Processing evaluation: #{feature_config&.feature || 'nil feature'}, #{target&.identifier || 'nil target'}"
2727

2828
@metrics_processor.register_evaluation(target, feature_config, variation)
2929
end

lib/ff/ruby/server/sdk/api/metrics_event.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def ==(other)
1616
end
1717

1818
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.
1923
unless other.is_a?(MetricsEvent)
2024
@logger.warn("Warning: Attempted to compare MetricsEvent with #{other.class.name}" )
2125
return false

lib/ff/ruby/server/sdk/api/metrics_processor.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require_relative "../api/summary_metrics"
1010

1111
class MetricsProcessor < Closeable
12+
GLOBAL_TARGET = Target.new(identifier: "__global__cf_target", name: "Global Target").freeze
1213

1314
class FrequencyMap < Concurrent::Map
1415
def initialize(options = nil, &block)
@@ -66,7 +67,6 @@ def init(connector, config, callback)
6667
@target_attribute = "target"
6768
@global_target_identifier = "__global__cf_target" # <--- This target identifier is used to aggregate and send data for all
6869
# targets as a summary
69-
@global_target = Target.new("RubySDK1", identifier = @global_target_identifier, name = @global_target_name)
7070
@ready = false
7171
@jar_version = Ff::Ruby::Server::Sdk::VERSION
7272
@server = "server"
@@ -112,7 +112,9 @@ def close
112112

113113
def register_evaluation(target, feature_config, variation)
114114
register_evaluation_metric(feature_config, variation)
115-
register_target_metric(target)
115+
if target
116+
register_target_metric(target)
117+
end
116118
end
117119

118120
private
@@ -123,17 +125,17 @@ def register_evaluation_metric(feature_config, variation)
123125
# project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
124126
# these checks in a future release.
125127
if feature_config.nil? || !feature_config.respond_to?(:feature) || feature_config.feature.nil?
126-
@config.logger.warn("Invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{feature_config.inspect}")
128+
@config.logger.warn("Skipping invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{feature_config.inspect}")
127129
return
128130
end
129131

130-
if @global_target.nil? || !@global_target.respond_to?(:identifier) || @global_target.identifier.nil?
131-
@config.logger.warn("Invalid MetricsEvent: global_target is missing or incomplete. global_target=#{@global_target.inspect}")
132+
if GLOBAL_TARGET.nil? || !GLOBAL_TARGET.respond_to?(:identifier) || GLOBAL_TARGET.identifier.nil?
133+
@config.logger.warn("Skipping invalid MetricsEvent: global_target is missing or incomplete. global_target=#{GLOBAL_TARGET.inspect}")
132134
return
133135
end
134136

135137
if variation.nil? || !variation.respond_to?(:identifier) || variation.identifier.nil?
136-
@config.logger.warn("Invalid MetricsEvent: variation is missing or incomplete. variation=#{variation.inspect}")
138+
@config.logger.warn("Skipping iInvalid MetricsEvent: variation is missing or incomplete. variation=#{variation.inspect}")
137139
return
138140
end
139141

@@ -146,7 +148,7 @@ def register_evaluation_metric(feature_config, variation)
146148
return
147149
end
148150

149-
event = MetricsEvent.new(feature_config, @global_target, variation, @config.logger)
151+
event = MetricsEvent.new(feature_config, GLOBAL_TARGET, variation, @config.logger)
150152
@evaluation_metrics.increment event
151153
end
152154

lib/ff/ruby/server/sdk/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Ruby
55
module Server
66
module Sdk
77

8-
VERSION = "1.4.0"
8+
VERSION = "1.4.1"
99
end
1010
end
1111
end

scripts/sdk_specs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22

33
export ff_ruby_sdk="ff-ruby-server-sdk"
4-
export ff_ruby_sdk_version="1.4.0"
4+
export ff_ruby_sdk_version="1.4.1"

test/ff/ruby/server/sdk/cf_client_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class CfClientTest < Minitest::Test
3737
.event_url("http://localhost:#{port}/api/1.0")
3838
.config_url("http://localhost:#{port}/api/1.0").build)
3939

40-
client.wait_for_initialization
40+
client.wait_for_initialization(timeout_ms: 5000)
4141

4242
target = Target.new("RubyTestSDK", identifier="rubytestsdk", attributes={"location": "emea"})
4343

test/ff/ruby/server/sdk/metrics_processor_test.rb

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def test_metrics
132132

133133
assert_equal 0, freq_map.size, "not all pending metrics were flushed"
134134
assert_equal 1, connector.get_captured_metrics.size
135-
assert_equal 2, connector.get_captured_metrics[0].target_data.size, "there should only be 2 targets"
135+
assert_equal 1, connector.get_captured_metrics[0].target_data.size, "there should only be 1 targets"
136136

137137
connector.get_captured_metrics.each do |metric|
138138
metric.metrics_data.each do |metric_data|
@@ -156,16 +156,15 @@ def assert_target_data(target_data)
156156
end
157157
end
158158

159-
assert targets.key?("__global__cf_target")
160159
assert targets.key?("target-id")
161160
end
162161

163162
def test_frequency_map_increment
164163

165164
map = MetricsProcessor::FrequencyMap.new
166165

167-
event1 = MetricsEvent.new(@feature1, @target, @variation1)
168-
event2 = MetricsEvent.new(@feature2, @target, @variation2)
166+
event1 = MetricsEvent.new(@feature1, @target, @variation1, Logger.new(STDOUT))
167+
event2 = MetricsEvent.new(@feature2, @target, @variation2, Logger.new(STDOUT))
169168

170169
map.increment event1
171170
map.increment event2
@@ -180,8 +179,8 @@ def test_frequency_map_increment
180179
def test_frequency_map_drain_to_map
181180
map = MetricsProcessor::FrequencyMap.new
182181

183-
event1 = MetricsEvent.new(@feature1, @target, @variation1)
184-
event2 = MetricsEvent.new(@feature2, @target, @variation2)
182+
event1 = MetricsEvent.new(@feature1, @target, @variation1, Logger.new(STDOUT))
183+
event2 = MetricsEvent.new(@feature2, @target, @variation2, Logger.new(STDOUT))
185184

186185
map.increment event1
187186
map.increment event2
@@ -209,39 +208,70 @@ def test_comparable_metrics_event_equals_and_hash
209208
assert(event1 != event2)
210209
end
211210

212-
def test_metrics_event_eql_with_invalid_object
213-
event = MetricsEvent.new(@feature1, @target, @variation1,Logger.new(STDOUT))
214-
non_event = "Not a MetricsEvent"
215-
216-
refute_equal(event, non_event, "MetricsEvent should not be equal to a non-MetricsEvent object")
217-
end
218-
219-
def test_flush_map_when_buffer_fills
220-
211+
def test_metrics_processor_prevents_invalid_metrics_event
221212
logger = Logger.new(STDOUT)
222213
callback = TestCallback.new
223214
connector = TestConnector.new
224215
config = Minitest::Mock.new
225216
config.expect :kind_of?, true, [Config.class]
226217
config.expect :metrics_service_acceptable_duration, 10000
227218

228-
(1..30).each { |_| config.expect :buffer_size, 2 }
229-
(1..30).each { |_| config.expect :logger, logger }
219+
(1..20).each { |_| config.expect :buffer_size, 10 }
220+
(1..20).each { |_| config.expect :logger, logger }
230221

231222
metrics_processor = MetricsProcessor.new
232-
metrics_processor.init connector, config, callback
223+
metrics_processor.init(connector, config, callback)
233224

234225
callback.wait_until_ready
235226

236-
# several evaluations with a buffer size of 2
237-
metrics_processor.register_evaluation @target, @feature1, @variation1
238-
metrics_processor.register_evaluation @target, @feature1, @variation2
239-
metrics_processor.register_evaluation @target, @feature2, @variation1
240-
metrics_processor.register_evaluation @target, @feature2, @variation2
227+
# Attempt to register invalid evaluations
228+
metrics_processor.register_evaluation(@target,nil, @variation1)
229+
metrics_processor.register_evaluation(@target,nil, nil)
230+
metrics_processor.register_evaluation(nil, @feature1, nil)
231+
241232

242-
assert connector.wait_for_metrics, "no metrics were posted"
233+
# Register some valid evaluations
234+
metrics_processor.register_evaluation(@target, @feature1, @variation1)
235+
metrics_processor.register_evaluation(@target, @feature2, @variation2)
236+
# Nil target, which is a valid input to variation methods
237+
metrics_processor.register_evaluation(nil, @feature2, @variation2)
243238

239+
# Run iteration
240+
metrics_processor.send(:run_one_iteration)
241+
242+
# Wait for metrics to be posted
243+
connector.wait_for_metrics
244+
245+
# Check that only valid metrics are sent
246+
captured_metrics = connector.get_captured_metrics
247+
assert_equal 1, captured_metrics.size, "Only one metrics batch should be sent"
248+
249+
metrics_data = captured_metrics[0].metrics_data
250+
251+
# Since we have two valid events, two metrics_data should be present
252+
assert_equal 2, metrics_data.size, "Invalid metrics should be ignored"
253+
254+
# Verify that only valid features are present in sent metrics
255+
sent_features = metrics_data.map { |md| md.attributes.find { |kv| kv.key == "featureName" }.value }
256+
assert_includes(sent_features, "feature-name")
257+
assert_includes(sent_features, "feature-name2") # Assuming @feature2 has "feature-name2"
258+
259+
# Invalid event is not among the sent features
260+
refute_includes(sent_features, nil, "Invalid MetricsEvent should not be included in metrics_data")
261+
262+
# Valid events were processed correctly
263+
assert_equal 2, metrics_data.size, "There should be two metrics_data entries for valid events"
264+
265+
# Target data is still correctly sent
266+
assert_equal 1, captured_metrics[0].target_data.size, "There should only be a single target"
244267
end
245268

246269

270+
def test_metrics_event_eql_with_invalid_object
271+
event = MetricsEvent.new(@feature1, @target, @variation1,Logger.new(STDOUT))
272+
non_event = "Not a MetricsEvent"
273+
274+
refute_equal(event, non_event, "MetricsEvent should not be equal to a non-MetricsEvent object")
275+
end
276+
247277
end

0 commit comments

Comments
 (0)