Skip to content

Commit e575e79

Browse files
authored
FFM-11211 Metrics enhancements (#40)
1 parent 44f4ed6 commit e575e79

File tree

3 files changed

+105
-51
lines changed

3 files changed

+105
-51
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def initialize
3535

3636
@frequency = @@min_frequency
3737

38-
@buffer_size = 2048
38+
@buffer_size = 10000
3939

4040
@all_attributes_private = false
4141

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

Lines changed: 95 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ def initialize(options = nil, &block)
1717

1818
def increment(key)
1919
compute(key) do |old_value|
20-
if old_value == nil; 1 else old_value + 1 end
20+
if old_value == nil;
21+
1
22+
else
23+
old_value + 1
24+
end
2125
end
2226
end
2327

@@ -39,7 +43,6 @@ def drain_to_map
3943
end
4044
end
4145

42-
4346
def init(connector, config, callback)
4447

4548
unless connector.kind_of?(Connector)
@@ -59,14 +62,12 @@ def init(connector, config, callback)
5962
@connector = connector
6063

6164
@sdk_type = "SDK_TYPE"
62-
@global_target_set = Set[]
63-
@staging_target_set = Set[]
6465
@target_attribute = "target"
65-
@global_target = "__global__cf_target" # <--- This target identifier is used to aggregate and send data for all
66+
@global_target_identifier = "__global__cf_target" # <--- This target identifier is used to aggregate and send data for all
6667
# targets as a summary
67-
68+
@global_target = Target.new("RubySDK1", identifier = @global_target_identifier, name = @global_target_name)
6869
@ready = false
69-
@jar_version = ""
70+
@jar_version = Ff::Ruby::Server::Sdk::VERSION
7071
@server = "server"
7172
@sdk_version = "SDK_VERSION"
7273
@sdk_language = "SDK_LANGUAGE"
@@ -76,10 +77,20 @@ def init(connector, config, callback)
7677

7778
@executor = Concurrent::FixedThreadPool.new(10)
7879

79-
@frequency_map = FrequencyMap.new
80+
@evaluation_metrics = FrequencyMap.new
81+
@target_metrics = Concurrent::Map.new
82+
83+
# Keep track of targets that have already been sent to avoid sending them again
84+
@seen_targets = Concurrent::Map.new
8085

8186
@max_buffer_size = config.buffer_size - 1
8287

88+
# Max 100k targets per interval
89+
@max_targets_buffer_size = 100000
90+
91+
@evaluation_warning_issued = Concurrent::AtomicBoolean.new
92+
@target_warning_issued = Concurrent::AtomicBoolean.new
93+
8394
@callback.on_metrics_ready
8495
end
8596

@@ -99,65 +110,102 @@ def close
99110
end
100111

101112
def register_evaluation(target, feature_config, variation)
113+
register_evaluation_metric(feature_config, variation)
114+
register_target_metric(target)
115+
end
102116

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
117+
private
118+
119+
def register_evaluation_metric(feature_config, variation)
120+
if @evaluation_metrics.size > @max_buffer_size
121+
unless @evaluation_warning_issued.true?
122+
SdkCodes.warn_metrics_evaluations_max_size_exceeded(@config.logger)
123+
@evaluation_warning_issued.make_true
107124
end
125+
return
108126
end
109127

110-
event = MetricsEvent.new(feature_config, target, variation)
111-
@frequency_map.increment event
128+
event = MetricsEvent.new(feature_config, @global_target, variation)
129+
@evaluation_metrics.increment event
112130
end
113131

114-
private
132+
def register_target_metric(target)
133+
if @target_metrics.size > @max_targets_buffer_size
134+
unless @target_warning_issued.true?
135+
SdkCodes.warn_metrics_targets_max_size_exceeded(@config.logger)
136+
@target_warning_issued.make_true
137+
end
138+
return
139+
end
140+
141+
if target.is_private
142+
return
143+
end
144+
145+
already_seen = @seen_targets.put_if_absent(target.identifier, true)
146+
147+
if already_seen
148+
return
149+
end
150+
151+
@target_metrics.put(target.identifier, target)
152+
end
115153

116154
def run_one_iteration
117-
send_data_and_reset_cache @frequency_map.drain_to_map
155+
send_data_and_reset_cache(@evaluation_metrics, @target_metrics)
118156

119-
@config.logger.debug "metrics: frequency map size #{@frequency_map.size}. global target size #{@global_target_set.size}"
157+
@config.logger.debug "metrics: frequency map size #{@evaluation_metrics.size}. targets map size #{@target_metrics.size} global target size #{@seen_targets.size}"
120158
end
121159

122-
def send_data_and_reset_cache(map)
123-
metrics = prepare_summary_metrics_body(map)
160+
def send_data_and_reset_cache(evaluation_metrics_map, target_metrics_map)
161+
evaluation_metrics_map_clone = evaluation_metrics_map.drain_to_map
162+
163+
target_metrics_map_clone = Concurrent::Map.new
164+
165+
target_metrics_map.each_pair do |key, value|
166+
target_metrics_map_clone[key] = value
167+
end
168+
169+
target_metrics_map.clear
170+
171+
@evaluation_warning_issued.make_false
172+
@target_warning_issued.make_false
173+
174+
metrics = prepare_summary_metrics_body(evaluation_metrics_map_clone, target_metrics_map_clone)
124175

125-
if !metrics.metrics_data.empty? && !metrics.target_data.empty?
176+
unless metrics.metrics_data.empty?
126177
start_time = (Time.now.to_f * 1000).to_i
127178
@connector.post_metrics(metrics)
128179
end_time = (Time.now.to_f * 1000).to_i
129180
if end_time - start_time > @config.metrics_service_acceptable_duration
130181
@config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
131182
end
132183
end
133-
134-
@global_target_set.merge(@staging_target_set)
135-
@staging_target_set.clear
136-
137184
end
138185

139-
def prepare_summary_metrics_body(freq_map)
186+
def prepare_summary_metrics_body(evaluation_metrics_map, target_metrics_map)
140187
metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
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)
144-
end
188+
145189
total_count = 0
146-
freq_map.each do |key, value|
190+
evaluation_metrics_map.each do |key, value|
147191
total_count += value
148192
metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
149193
metrics_data.timestamp = (Time.now.to_f * 1000).to_i
150194
metrics_data.count = value
151195
metrics_data.metrics_type = "FFMETRICS"
152196
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_config.feature }))
153197
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation.identifier }))
154-
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target }))
198+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target_identifier }))
155199
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
156200
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
157201
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
158202
metrics.metrics_data.push(metrics_data)
159203
end
160-
@config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{freq_map.size}"
204+
@config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{evaluation_metrics_map.size}. target_data count is #{target_metrics_map.size}"
205+
206+
target_metrics_map.each_pair do |_, value|
207+
add_target_data(metrics, value)
208+
end
161209

162210
metrics
163211
end
@@ -167,28 +215,25 @@ def add_target_data(metrics, target)
167215
target_data = OpenapiClient::TargetData.new({ :attributes => [] })
168216
private_attributes = target.private_attributes
169217

170-
if !@staging_target_set.include?(target) && !@global_target_set.include?(target) && !target.is_private
171-
@staging_target_set.add(target)
172-
attributes = target.attributes
173-
attributes.each do |k, v|
174-
key_value = OpenapiClient::KeyValue.new
175-
if !private_attributes.empty?
176-
unless private_attributes.include?(k)
177-
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
178-
end
179-
else
218+
attributes = target.attributes
219+
attributes.each do |k, v|
220+
key_value = OpenapiClient::KeyValue.new
221+
if !private_attributes.empty?
222+
unless private_attributes.include?(k)
180223
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
181224
end
182-
target_data.attributes.push(key_value)
183-
end
184-
target_data.identifier = target.identifier
185-
if target.name == nil || target.name == ""
186-
target_data.name = target.identifier
187225
else
188-
target_data.name = target.name
226+
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
189227
end
190-
metrics.target_data.push(target_data)
228+
target_data.attributes.push(key_value)
229+
end
230+
target_data.identifier = target.identifier
231+
if target.name == nil || target.name == ""
232+
target_data.name = target.identifier
233+
else
234+
target_data.name = target.name
191235
end
236+
metrics.target_data.push(target_data)
192237
end
193238

194239
def start_async
@@ -218,7 +263,7 @@ def get_version
218263
end
219264

220265
def get_frequency_map
221-
@frequency_map
266+
@evaluation_metrics
222267
end
223268

224269
end

lib/ff/ruby/server/sdk/common/sdk_codes.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ def self.info_metrics_thread_started(logger)
3434
logger.info SdkCodes.sdk_err_msg(7000)
3535
end
3636

37+
def self.warn_metrics_targets_max_size_exceeded(logger)
38+
logger.warn SdkCodes.sdk_err_msg(7004)
39+
end
40+
41+
def self.warn_metrics_evaluations_max_size_exceeded(logger)
42+
logger.warn SdkCodes.sdk_err_msg(7007)
43+
end
3744
def self.warn_auth_failed_srv_defaults(logger)
3845
logger.warn SdkCodes.sdk_err_msg(2001)
3946
end
@@ -89,6 +96,8 @@ def self.warn_bucket_by_attr_not_found(logger, attr_name, new_value)
8996
7000 => "Metrics thread started",
9097
7001 => "Metrics thread exited",
9198
7002 => "Posting metrics failed, reason:",
99+
7004 => "Target metrics exceeded max size, remaining targets for this analytics interval will not be sent",
100+
7007 => "Evaluation metrics exceeded max size, remaining evaluations for this analytics interval will not be sent"
92101
}
93102

94103
def self.sdk_err_msg(error_code, append_text = "")

0 commit comments

Comments
 (0)