splitclient-rb 7.1.4.pre.rc7 → 7.1.4.pre.rc8

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +1 -1
  3. data/Rakefile +7 -2
  4. data/ext/murmurhash/3_x64_128.c +117 -0
  5. data/ext/murmurhash/3_x86_32.c +88 -0
  6. data/ext/murmurhash/extconf.rb +5 -0
  7. data/ext/murmurhash/murmurhash.c +255 -0
  8. data/ext/murmurhash/murmurhash.h +94 -0
  9. data/lib/splitclient-rb.rb +6 -1
  10. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  11. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  12. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +4 -18
  13. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +7 -18
  14. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +1 -27
  15. data/lib/splitclient-rb/cache/routers/impression_router.rb +12 -14
  16. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +73 -0
  17. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +11 -11
  18. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +3 -3
  19. data/lib/splitclient-rb/clients/split_client.rb +24 -73
  20. data/lib/splitclient-rb/engine/api/impressions.rb +30 -13
  21. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  22. data/lib/splitclient-rb/engine/common/impressions_manager.rb +87 -0
  23. data/lib/splitclient-rb/engine/evaluator/splitter.rb +1 -5
  24. data/lib/splitclient-rb/engine/parser/evaluator.rb +0 -4
  25. data/lib/splitclient-rb/engine/sync_manager.rb +5 -6
  26. data/lib/splitclient-rb/engine/synchronizer.rb +9 -1
  27. data/lib/splitclient-rb/split_config.rb +31 -1
  28. data/lib/splitclient-rb/split_factory.rb +5 -2
  29. data/lib/splitclient-rb/version.rb +1 -1
  30. data/splitclient-rb.gemspec +8 -1
  31. metadata +14 -17
@@ -4,10 +4,10 @@ module SplitIoClient
4
4
  module Cache
5
5
  module Senders
6
6
  class ImpressionsSender
7
- def initialize(impressions_repository, api_key, config)
7
+ def initialize(impressions_repository, config, impressions_api)
8
8
  @impressions_repository = impressions_repository
9
- @api_key = api_key
10
9
  @config = config
10
+ @impressions_api = impressions_api
11
11
  end
12
12
 
13
13
  def call
@@ -50,7 +50,7 @@ module SplitIoClient
50
50
  end
51
51
 
52
52
  def impressions_api
53
- @impressions_api ||= SplitIoClient::Api::Impressions.new(@api_key, @config)
53
+ @impressions_api
54
54
  end
55
55
  end
56
56
  end
@@ -9,7 +9,7 @@ module SplitIoClient
9
9
  # @param api_key [String] the API key for your split account
10
10
  #
11
11
  # @return [SplitIoClient] split.io client instance
12
- def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config)
12
+ def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config, impressions_manager)
13
13
  @api_key = api_key
14
14
  @metrics = metrics
15
15
  @splits_repository = splits_repository
@@ -20,17 +20,21 @@ module SplitIoClient
20
20
  @sdk_blocker = sdk_blocker
21
21
  @destroyed = false
22
22
  @config = config
23
+ @impressions_manager = impressions_manager
23
24
  end
24
25
 
25
26
  def get_treatment(
26
27
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
27
28
  multiple = false, evaluator = nil
28
29
  )
29
- treatment = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator)
30
+ impressions = []
31
+ result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment', impressions)
32
+ @impressions_manager.track(impressions)
33
+
30
34
  if multiple
31
- treatment.tap { |t| t.delete(:config) }
35
+ result.tap { |t| t.delete(:config) }
32
36
  else
33
- treatment[:treatment]
37
+ result[:treatment]
34
38
  end
35
39
  end
36
40
 
@@ -38,7 +42,11 @@ module SplitIoClient
38
42
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
39
43
  multiple = false, evaluator = nil
40
44
  )
41
- treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config')
45
+ impressions = []
46
+ result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config', impressions)
47
+ @impressions_manager.track(impressions)
48
+
49
+ result
42
50
  end
43
51
 
44
52
  def get_treatments(key, split_names, attributes = {})
@@ -74,53 +82,6 @@ module SplitIoClient
74
82
  @destroyed = true
75
83
  end
76
84
 
77
- def store_impression(split_name, matching_key, bucketing_key, treatment, attributes)
78
- time = (Time.now.to_f * 1000.0).to_i
79
-
80
- @impressions_repository.add(
81
- matching_key,
82
- bucketing_key,
83
- split_name,
84
- treatment,
85
- time
86
- )
87
-
88
- route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
89
-
90
- rescue StandardError => error
91
- @config.log_found_exception(__method__.to_s, error)
92
- end
93
-
94
- def route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
95
- impression_router.add(
96
- split_name: split_name,
97
- matching_key: matching_key,
98
- bucketing_key: bucketing_key,
99
- time: time,
100
- treatment: {
101
- label: treatment[:label],
102
- treatment: treatment[:treatment],
103
- change_number: treatment[:change_number]
104
- },
105
- attributes: attributes
106
- )
107
- end
108
-
109
- def route_impressions(split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
110
- impression_router.add_bulk(
111
- split_names: split_names,
112
- matching_key: matching_key,
113
- bucketing_key: bucketing_key,
114
- time: time,
115
- treatments_labels_change_numbers: treatments_labels_change_numbers,
116
- attributes: attributes
117
- )
118
- end
119
-
120
- def impression_router
121
- @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
122
- end
123
-
124
85
  def track(key, traffic_type_name, event_type, value = nil, properties = nil)
125
86
  return false unless valid_client && @config.split_validator.valid_track_parameters(key, traffic_type_name, event_type, value, properties)
126
87
 
@@ -239,26 +200,20 @@ module SplitIoClient
239
200
 
240
201
  bucketing_key, matching_key = keys_from_key(key)
241
202
  bucketing_key = bucketing_key ? bucketing_key.to_s : nil
242
- matching_key = matching_key ? matching_key.to_s : nil
203
+ matching_key = matching_key ? matching_key.to_s : nil
243
204
 
244
205
  evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true)
245
206
  start = Time.now
207
+ impressions = []
246
208
  treatments_labels_change_numbers =
247
209
  @splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
248
- memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator))
210
+ memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions))
249
211
  end
250
212
  latency = (Time.now - start) * 1000.0
251
213
  # Measure
252
214
  @metrics.time('sdk.' + calling_method, latency)
253
215
 
254
- treatments_for_impressions = get_treatment_for_impressions(treatments_labels_change_numbers)
255
-
256
- time = (Time.now.to_f * 1000.0).to_i
257
- @impressions_repository.add_bulk(
258
- matching_key, bucketing_key, treatments_for_impressions, time
259
- ) unless treatments_for_impressions == {}
260
-
261
- route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_for_impressions, attributes)
216
+ @impressions_manager.track(impressions)
262
217
 
263
218
  split_names_keys = treatments_labels_change_numbers.keys
264
219
  treatments = treatments_labels_change_numbers.values.map do |v|
@@ -284,7 +239,7 @@ module SplitIoClient
284
239
  # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
285
240
  def treatment(
286
241
  key, split_name, attributes = {}, split_data = nil, store_impressions = true,
287
- multiple = false, evaluator = nil, calling_method = 'get_treatment'
242
+ multiple = false, evaluator = nil, calling_method = 'get_treatment', impressions = []
288
243
  )
289
244
  control_treatment = { treatment: Engine::Models::Treatment::CONTROL }
290
245
 
@@ -329,15 +284,18 @@ module SplitIoClient
329
284
  end
330
285
 
331
286
  latency = (Time.now - start) * 1000.0
332
-
333
- store_impression(split_name, matching_key, bucketing_key, treatment_data, attributes) if store_impressions
287
+
288
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil })
289
+ impressions << impression unless impression.nil?
334
290
 
335
291
  # Measure
336
292
  @metrics.time('sdk.' + calling_method, latency) unless multiple
337
293
  rescue StandardError => error
294
+ p error
338
295
  @config.log_found_exception(__method__.to_s, error)
339
296
 
340
- store_impression(split_name, matching_key, bucketing_key, control_treatment, attributes) if store_impressions
297
+ impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, control_treatment, { attributes: attributes, time: nil })
298
+ impressions << impression unless impression.nil?
341
299
 
342
300
  return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }))
343
301
  end
@@ -357,12 +315,5 @@ module SplitIoClient
357
315
  def parsed_attributes(attributes)
358
316
  return attributes || attributes.to_h
359
317
  end
360
-
361
- def get_treatment_for_impressions(treatments_labels_change_numbers)
362
- return treatments_labels_change_numbers.select{|imp|
363
- treatments_labels_change_numbers[imp][:label] != Engine::Models::Label::NOT_FOUND &&
364
- !treatments_labels_change_numbers[imp][:label].nil?
365
- }
366
- end
367
318
  end
368
319
  end
@@ -14,16 +14,31 @@ module SplitIoClient
14
14
  return
15
15
  end
16
16
 
17
- impressions_by_ip(impressions).each do |ip, impressions_ip|
18
- response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions_ip)
19
-
20
- if response.success?
21
- @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
22
- else
23
- @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
24
- ' - Check your API key and base URI')
25
- raise 'Split SDK failed to connect to backend to post impressions'
26
- end
17
+ response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
18
+
19
+ if response.success?
20
+ @config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
21
+ else
22
+ @config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
23
+ ' - Check your API key and base URI')
24
+ raise 'Split SDK failed to connect to backend to post impressions'
25
+ end
26
+ end
27
+
28
+ def post_count(impressions_count)
29
+ if impressions_count.nil? || impressions_count[:pf].empty?
30
+ @config.split_logger.log_if_debug('No impressions count to send')
31
+ return
32
+ end
33
+
34
+ response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
35
+
36
+ if response.success?
37
+ @config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
38
+ else
39
+ @config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
40
+ ' - Check your API key and base URI')
41
+ raise 'Split SDK failed to connect to backend to post impressions'
27
42
  end
28
43
  end
29
44
 
@@ -31,14 +46,16 @@ module SplitIoClient
31
46
  return 0 if impressions.nil?
32
47
 
33
48
  impressions.reduce(0) do |impressions_count, impression|
34
- impressions_count += impression[:keyImpressions].length
49
+ impressions_count += impression[:i].length
35
50
  end
36
51
  end
37
52
 
38
53
  private
39
54
 
40
- def impressions_by_ip(impressions)
41
- impressions.group_by { |impression| impression[:ip] }
55
+ def impressions_headers
56
+ {
57
+ 'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
58
+ }
42
59
  end
43
60
  end
44
61
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ TIME_INTERVAL_MS = 3600 * 1000
9
+
10
+ class ImpressionCounter
11
+ DEFAULT_AMOUNT = 1
12
+
13
+ def initialize
14
+ @cache = Concurrent::Hash.new
15
+ end
16
+
17
+ def inc(split_name, time_frame)
18
+ key = make_key(split_name, time_frame)
19
+
20
+ current_amount = @cache[key]
21
+ @cache[key] = current_amount.nil? ? DEFAULT_AMOUNT : (current_amount + DEFAULT_AMOUNT)
22
+ end
23
+
24
+ def pop_all
25
+ to_return = Concurrent::Hash.new
26
+
27
+ @cache.each do |key, value|
28
+ to_return[key] = value
29
+ end
30
+ @cache.clear
31
+
32
+ to_return
33
+ end
34
+
35
+ def truncate_time_frame(timestamp_ms)
36
+ timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
37
+ end
38
+
39
+ def make_key(split_name, time_frame)
40
+ "#{split_name}::#{truncate_time_frame(time_frame)}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Common
6
+ class ImpressionManager
7
+ def initialize(config, impressions_repository, impression_counter)
8
+ @config = config
9
+ @impressions_repository = impressions_repository
10
+ @impression_counter = impression_counter
11
+ @impression_router = SplitIoClient::ImpressionRouter.new(@config)
12
+ @impression_observer = SplitIoClient::Observers::ImpressionObserver.new
13
+ end
14
+
15
+ # added param time for test
16
+ def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
17
+ impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
18
+
19
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data) unless redis?
20
+
21
+ return impression_optimized(split_name, impression_data, params[:attributes]) if optimized? && !redis?
22
+
23
+ impression(impression_data, params[:attributes])
24
+ rescue StandardError => error
25
+ @config.log_found_exception(__method__.to_s, error)
26
+ end
27
+
28
+ def track(impressions)
29
+ @impressions_repository.add_bulk(impressions)
30
+ @impression_router.add_bulk(impressions)
31
+ rescue StandardError => error
32
+ @config.log_found_exception(__method__.to_s, error)
33
+ end
34
+
35
+ private
36
+
37
+ # added param time for test
38
+ def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil)
39
+ {
40
+ k: matching_key,
41
+ b: bucketing_key,
42
+ f: split_name,
43
+ t: treatment[:treatment],
44
+ r: applied_rule(treatment[:label]),
45
+ c: treatment[:change_number],
46
+ m: time || (Time.now.to_f * 1000.0).to_i,
47
+ pt: nil
48
+ }
49
+ end
50
+
51
+ def metadata
52
+ {
53
+ s: "#{@config.language}-#{@config.version}",
54
+ i: @config.machine_ip,
55
+ n: @config.machine_name
56
+ }
57
+ end
58
+
59
+ def applied_rule(label)
60
+ @config.labels_enabled ? label : nil
61
+ end
62
+
63
+ def optimized?
64
+ @config.impressions_mode == :optimized
65
+ end
66
+
67
+ def impression_optimized(split_name, impression_data, attributes)
68
+ @impression_counter.inc(split_name, impression_data[:m])
69
+
70
+ impression(impression_data, attributes) if should_queue_impression?(impression_data)
71
+ end
72
+
73
+ def should_queue_impression?(impression)
74
+ impression[:pt].nil? || (impression[:pt] < ((Time.now.to_f * 1000.0).to_i - Common::TIME_INTERVAL_MS))
75
+ end
76
+
77
+ def impression(impression_data, attributes)
78
+ { m: metadata, i: impression_data, attributes: attributes }
79
+ end
80
+
81
+ def redis?
82
+ @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,7 +1,3 @@
1
- unless defined?(JRUBY_VERSION)
2
- require 'digest/murmurhash'
3
- end
4
-
5
1
  module SplitIoClient
6
2
  # Misc class in charge of providing hash functions and
7
3
  # determination of treatment based on concept of buckets
@@ -13,7 +9,7 @@ module SplitIoClient
13
9
  when 'java'
14
10
  Proc.new { |key, seed| Java::MurmurHash3.murmurhash3_x86_32(key, seed) }
15
11
  else
16
- Proc.new { |key, seed| Digest::MurmurHash3_x86_32.rawdigest(key, [seed].pack('L')) }
12
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x86_32.rawdigest(key, [seed].pack('L')) }
17
13
  end
18
14
  end
19
15
 
@@ -1,7 +1,3 @@
1
- unless defined?(JRUBY_VERSION)
2
- require 'digest/murmurhash'
3
- end
4
-
5
1
  module SplitIoClient
6
2
  module Engine
7
3
  module Parser
@@ -9,14 +9,13 @@ module SplitIoClient
9
9
  repositories,
10
10
  api_key,
11
11
  config,
12
- sdk_blocker,
13
- metrics
12
+ params
14
13
  )
15
- split_fetcher = SplitFetcher.new(repositories[:splits], api_key, metrics, config, sdk_blocker)
16
- segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, metrics, config, sdk_blocker)
17
- sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher }
14
+ split_fetcher = SplitFetcher.new(repositories[:splits], api_key, params[:metrics], config, params[:sdk_blocker])
15
+ segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, params[:metrics], config, params[:sdk_blocker])
16
+ sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher, imp_counter: params[:impression_counter] }
18
17
 
19
- @synchronizer = Synchronizer.new(repositories, api_key, config, sdk_blocker, sync_params)
18
+ @synchronizer = Synchronizer.new(repositories, api_key, config, params[:sdk_blocker], sync_params)
20
19
  notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
21
20
  manager.on_occupancy { |publisher_available| process_occupancy(publisher_available) }
22
21
  manager.on_push_shutdown { process_push_shutdown }
@@ -23,6 +23,8 @@ module SplitIoClient
23
23
  @sdk_blocker = sdk_blocker
24
24
  @split_fetcher = params[:split_fetcher]
25
25
  @segment_fetcher = params[:segment_fetcher]
26
+ @impressions_api = SplitIoClient::Api::Impressions.new(@api_key, @config)
27
+ @impression_counter = params[:imp_counter]
26
28
  end
27
29
 
28
30
  def sync_all
@@ -35,6 +37,7 @@ module SplitIoClient
35
37
  impressions_sender
36
38
  metrics_sender
37
39
  events_sender
40
+ impressions_count_sender
38
41
  end
39
42
 
40
43
  def start_periodic_fetch
@@ -73,7 +76,7 @@ module SplitIoClient
73
76
 
74
77
  # Starts thread which loops constantly and sends impressions to the Split API
75
78
  def impressions_sender
76
- ImpressionsSender.new(@impressions_repository, @api_key, @config).call
79
+ ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
77
80
  end
78
81
 
79
82
  # Starts thread which loops constantly and sends metrics to the Split API
@@ -85,6 +88,11 @@ module SplitIoClient
85
88
  def events_sender
86
89
  EventsSender.new(@events_repository, @config).call
87
90
  end
91
+
92
+ # Starts thread which loops constantly and sends impressions count to the Split API
93
+ def impressions_count_sender
94
+ ImpressionsCountSender.new(@config, @impression_counter, @impressions_api).call
95
+ end
88
96
  end
89
97
  end
90
98
  end