Skip to content

Commit 4ef124f

Browse files
authored
Merge pull request #542 from splitio/impressions-toggle
Impressions toggle
2 parents 49d9abe + 0e065e3 commit 4ef124f

23 files changed

+694
-168
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
CHANGES
22

3+
8.5.0 (Jan 17, 2025)
4+
- Fixed high cpu usage when unique keys are cleared every 24 hours.
5+
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
6+
37
8.4.0 (May 3, 2024)
48
- Fixed issue preventing Impressopns and Events posting if client.destroy is called before the post threads started
59
- Added support for targeting rules based on semantic versions (https://semver.org/).

lib/splitclient-rb/clients/split_client.rb

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
267267
to_return = Hash.new
268268
sanitized_feature_flag_names.each {|name|
269269
to_return[name.to_sym] = control_treatment_with_config
270-
impressions << @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ label: Engine::Models::Label::NOT_READY }), { attributes: attributes, time: nil })
270+
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }), :disabled => false }
271271
}
272272
@impressions_manager.track(impressions)
273273
return to_return
@@ -278,7 +278,6 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
278278
valid_feature_flag_names << feature_flag_name unless feature_flag_name.nil?
279279
}
280280
start = Time.now
281-
impressions_total = []
282281

283282
feature_flags = @splits_repository.splits(valid_feature_flag_names)
284283
treatments = Hash.new
@@ -291,15 +290,14 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
291290
next
292291
end
293292
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
294-
impressions_total.concat(impressions) unless impressions.nil?
295293
treatments[key] =
296294
{
297295
treatment: treatments_labels_change_numbers[:treatment],
298296
config: treatments_labels_change_numbers[:config]
299297
}
298+
@impressions_manager.track(impressions) unless impressions.empty?
300299
end
301300
record_latency(calling_method, start)
302-
@impressions_manager.track(impressions_total) unless impressions_total.empty?
303301

304302
treatments.merge(invalid_treatments)
305303
end
@@ -332,50 +330,50 @@ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_i
332330
end
333331

334332
feature_flag = @splits_repository.get_split(feature_flag_name)
335-
treatments, impressions = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
333+
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
336334

337-
@impressions_manager.track(impressions) unless impressions.nil?
335+
@impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
338336
treatments
339337
end
340338

341339
def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
342-
impressions = []
340+
impressions_decorator = []
343341
begin
344342
start = Time.now
345343
if feature_flag.nil? && ready?
346344
@config.logger.warn("#{calling_method}: you passed #{feature_flag_name} that " \
347345
'does not exist in this environment, please double check what feature flags exist in the Split user interface')
348-
return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::NOT_FOUND }), multiple), nil
346+
return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::NOT_FOUND }), multiple), nil
349347
end
350-
treatment_data =
348+
351349
if !feature_flag.nil? && ready?
352-
@evaluator.evaluate_feature_flag(
350+
treatment_data = @evaluator.evaluate_feature_flag(
353351
{ bucketing_key: bucketing_key, matching_key: matching_key }, feature_flag, attributes
354352
)
353+
impressions_disabled = feature_flag[:impressionsDisabled]
355354
else
356355
@config.logger.error("#{calling_method}: the SDK is not ready, results may be incorrect for feature flag #{feature_flag_name}. Make sure to wait for SDK readiness before using this method.")
357-
control_treatment.merge({ label: Engine::Models::Label::NOT_READY })
356+
treatment_data = control_treatment.merge({ :label => Engine::Models::Label::NOT_READY })
357+
impressions_disabled = false
358358
end
359359

360360
record_latency(calling_method, start)
361-
impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, { attributes: attributes, time: nil })
362-
impressions << impression unless impression.nil?
361+
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }), :disabled => impressions_disabled }
362+
impressions_decorator << impression_decorator unless impression_decorator.nil?
363363
rescue StandardError => e
364364
@config.log_found_exception(__method__.to_s, e)
365-
366365
record_exception(calling_method)
366+
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }), :disabled => false }
367+
impressions_decorator << impression_decorator unless impression_decorator.nil?
367368

368-
impression = @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, { attributes: attributes, time: nil })
369-
impressions << impression unless impression.nil?
370-
371-
return parsed_treatment(control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }), multiple), impressions
369+
return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator
372370
end
373371

374-
return parsed_treatment(treatment_data, multiple), impressions
372+
return parsed_treatment(treatment_data, multiple), impressions_decorator
375373
end
376374

377375
def control_treatment
378-
{ treatment: Engine::Models::Treatment::CONTROL }
376+
{ :treatment => Engine::Models::Treatment::CONTROL }
379377
end
380378

381379
def control_treatment_with_config

lib/splitclient-rb/engine/common/impressions_manager.rb

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,16 @@ def initialize(config,
1818
@unique_keys_tracker = unique_keys_tracker
1919
end
2020

21-
def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
22-
impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
23-
21+
def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {})
22+
impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time])
2423
begin
25-
case @config.impressions_mode
26-
when :debug # In DEBUG mode we should calculate the pt only.
27-
impression_data[:pt] = @impression_observer.test_and_set(impression_data)
28-
when :none # In NONE mode we should track the total amount of evaluations and the unique keys.
24+
if @config.impressions_mode == :none || impressions_disabled
2925
@impression_counter.inc(split_name, impression_data[:m])
3026
@unique_keys_tracker.track(split_name, matching_key)
27+
elsif @config.impressions_mode == :debug # In DEBUG mode we should calculate the pt only.
28+
impression_data[:pt] = @impression_observer.test_and_set(impression_data)
3129
else # In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
3230
impression_data[:pt] = @impression_observer.test_and_set(impression_data)
33-
3431
@impression_counter.inc(split_name, impression_data[:m]) unless impression_data[:pt].nil?
3532
end
3633
rescue StandardError => e
@@ -40,24 +37,25 @@ def build_impression(matching_key, bucketing_key, split_name, treatment, params
4037
impression(impression_data, params[:attributes])
4138
end
4239

43-
def track(impressions)
44-
return if impressions.empty?
45-
46-
stats = { dropped: 0, queued: 0, dedupe: 0 }
47-
begin
48-
case @config.impressions_mode
49-
when :none
50-
return
51-
when :debug
52-
track_debug_mode(impressions, stats)
53-
when :optimized
54-
track_optimized_mode(impressions, stats)
40+
def track(impressions_decorator)
41+
return if impressions_decorator.empty?
42+
43+
impressions_decorator.each do |impression_decorator|
44+
impression_router.add_bulk([impression_decorator[:impression]])
45+
stats = { dropped: 0, queued: 0, dedupe: 0 }
46+
begin
47+
next if @config.impressions_mode == :none || impression_decorator[:disabled]
48+
49+
if @config.impressions_mode == :debug
50+
track_debug_mode([impression_decorator[:impression]], stats)
51+
else
52+
track_optimized_mode([impression_decorator[:impression]], stats)
53+
end
54+
rescue StandardError => e
55+
@config.log_found_exception(__method__.to_s, e)
56+
ensure
57+
record_stats(stats)
5558
end
56-
rescue StandardError => e
57-
@config.log_found_exception(__method__.to_s, e)
58-
ensure
59-
record_stats(stats)
60-
impression_router.add_bulk(impressions)
6159
end
6260
end
6361

@@ -126,11 +124,10 @@ def track_debug_mode(impressions, stats)
126124

127125
def track_optimized_mode(impressions, stats)
128126
optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
129-
127+
stats[:dedupe] = impressions.length - optimized_impressions.length
130128
return if optimized_impressions.empty?
131129

132130
stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
133-
stats[:dedupe] = impressions.length - optimized_impressions.length
134131
stats[:queued] = optimized_impressions.length - stats[:dropped]
135132
end
136133
end

lib/splitclient-rb/engine/synchronizer.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def start_periodic_data_recording
4848
events_sender
4949
start_telemetry_sync_task
5050
end
51-
51+
5252
impressions_count_sender
5353
start_unique_keys_tracker_task
5454
end
@@ -175,7 +175,7 @@ def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_secon
175175

176176
# Starts thread which loops constantly and sends impressions to the Split API
177177
def impressions_sender
178-
ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call unless @config.impressions_mode == :none
178+
ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
179179
end
180180

181181
# Starts thread which loops constantly and sends events to the Split API
@@ -185,7 +185,7 @@ def events_sender
185185

186186
# Starts thread which loops constantly and sends impressions count to the Split API
187187
def impressions_count_sender
188-
ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call unless @config.impressions_mode == :debug
188+
ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call
189189
end
190190

191191
def start_telemetry_sync_task
@@ -203,7 +203,7 @@ def sync_result(success, remaining_attempts, segment_names = nil)
203203
def sync_splits_and_segments
204204
@config.logger.debug('Synchronizing feature flags and segments ...') if @config.debug_enabled
205205
splits_result = @split_fetcher.fetch_splits
206-
206+
207207
splits_result[:success] && @segment_fetcher.fetch_segments
208208
end
209209
end

lib/splitclient-rb/helpers/repository_helper.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ def self.update_feature_flag_repository(feature_flag_repository, feature_flags,
1313
next
1414
end
1515

16+
unless feature_flag.key?(:impressionsDisabled)
17+
feature_flag[:impressionsDisabled] = false
18+
if config.debug_enabled
19+
config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false")
20+
end
21+
end
22+
1623
config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled
1724
to_add.push(feature_flag)
1825
end

lib/splitclient-rb/managers/split_manager.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ def build_split_view(name, split)
107107
change_number: split[:changeNumber],
108108
configs: split[:configurations] || {},
109109
sets: split[:sets] || [],
110-
default_treatment: split[:defaultTreatment]
110+
default_treatment: split[:defaultTreatment],
111+
impressions_disabled: split[:impressionsDisabled]
111112
}
112113
end
113114

lib/splitclient-rb/split_factory.rb

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -230,33 +230,22 @@ def build_telemetry_synchronizer(flag_sets, flag_sets_invalid)
230230
end
231231

232232
def build_unique_keys_tracker
233-
if @config.impressions_mode != :none
234-
@unique_keys_tracker = Engine::Impressions::NoopUniqueKeysTracker.new
235-
return
236-
end
237-
238233
bf = Cache::Filter::BloomFilter.new(30_000_000)
239234
filter_adapter = Cache::Filter::FilterAdapter.new(@config, bf)
240235
cache = Concurrent::Hash.new
241236
@unique_keys_tracker = Engine::Impressions::UniqueKeysTracker.new(@config, filter_adapter, @impressions_sender_adapter, cache)
242237
end
243238

244239
def build_impressions_observer
245-
if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized) ||
246-
(@config.cache_adapter == :memory && @config.impressions_mode == :none)
240+
if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized)
247241
@impression_observer = Observers::NoopImpressionObserver.new
248242
else
249243
@impression_observer = Observers::ImpressionObserver.new
250244
end
251245
end
252246

253247
def build_impression_counter
254-
case @config.impressions_mode
255-
when :debug
256-
@impression_counter = Engine::Common::NoopImpressionCounter.new
257-
else
258-
@impression_counter = Engine::Common::ImpressionCounter.new
259-
end
248+
@impression_counter = Engine::Common::ImpressionCounter.new
260249
end
261250

262251
def build_impressions_sender_adapter

lib/splitclient-rb/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SplitIoClient
2-
VERSION = '8.4.0'
2+
VERSION = '8.5.0'
33
end

0 commit comments

Comments
 (0)