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

Sign up to get free protection for your applications and to get access to all the features.
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