splitclient-rb 4.5.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/CHANGES.txt +147 -0
- data/Detailed-README.md +571 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/NEWS +75 -0
- data/README.md +43 -0
- data/Rakefile +24 -0
- data/exe/splitio +96 -0
- data/ext/murmurhash/MurmurHash3.java +162 -0
- data/lib/murmurhash/base.rb +58 -0
- data/lib/murmurhash/murmurhash.jar +0 -0
- data/lib/murmurhash/murmurhash_mri.rb +3 -0
- data/lib/splitclient-rb.rb +90 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +133 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +44 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +165 -0
- data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +30 -0
- data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +29 -0
- data/lib/splitclient-rb/cache/repositories/events_repository.rb +41 -0
- data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +49 -0
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +78 -0
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +21 -0
- data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +129 -0
- data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +98 -0
- data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +22 -0
- data/lib/splitclient-rb/cache/repositories/repository.rb +23 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +82 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +106 -0
- data/lib/splitclient-rb/cache/routers/impression_router.rb +52 -0
- data/lib/splitclient-rb/cache/senders/events_sender.rb +47 -0
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +73 -0
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +67 -0
- data/lib/splitclient-rb/cache/senders/metrics_sender.rb +49 -0
- data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +48 -0
- data/lib/splitclient-rb/cache/stores/segment_store.rb +82 -0
- data/lib/splitclient-rb/cache/stores/split_store.rb +97 -0
- data/lib/splitclient-rb/clients/localhost_split_client.rb +92 -0
- data/lib/splitclient-rb/clients/split_client.rb +214 -0
- data/lib/splitclient-rb/engine/api/client.rb +74 -0
- data/lib/splitclient-rb/engine/api/events.rb +48 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +55 -0
- data/lib/splitclient-rb/engine/api/impressions.rb +42 -0
- data/lib/splitclient-rb/engine/api/metrics.rb +61 -0
- data/lib/splitclient-rb/engine/api/segments.rb +62 -0
- data/lib/splitclient-rb/engine/api/splits.rb +60 -0
- data/lib/splitclient-rb/engine/evaluator/splitter.rb +123 -0
- data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +46 -0
- data/lib/splitclient-rb/engine/matchers/between_matcher.rb +56 -0
- data/lib/splitclient-rb/engine/matchers/combiners.rb +9 -0
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +86 -0
- data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +21 -0
- data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +19 -0
- data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +30 -0
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +20 -0
- data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +26 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +27 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +54 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +19 -0
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +53 -0
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +53 -0
- data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +24 -0
- data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +60 -0
- data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +23 -0
- data/lib/splitclient-rb/engine/matchers/set_matcher.rb +20 -0
- data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +26 -0
- data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +45 -0
- data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
- data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +128 -0
- data/lib/splitclient-rb/engine/metrics/metrics.rb +83 -0
- data/lib/splitclient-rb/engine/models/label.rb +8 -0
- data/lib/splitclient-rb/engine/models/split.rb +17 -0
- data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +210 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +118 -0
- data/lib/splitclient-rb/engine/parser/partition.rb +35 -0
- data/lib/splitclient-rb/engine/parser/split_adapter.rb +88 -0
- data/lib/splitclient-rb/exceptions/impressions_shutdown_exception.rb +4 -0
- data/lib/splitclient-rb/exceptions/sdk_blocker_timeout_expired_exception.rb +4 -0
- data/lib/splitclient-rb/localhost_split_factory.rb +13 -0
- data/lib/splitclient-rb/localhost_utils.rb +36 -0
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
- data/lib/splitclient-rb/managers/split_manager.rb +77 -0
- data/lib/splitclient-rb/split_config.rb +391 -0
- data/lib/splitclient-rb/split_factory.rb +35 -0
- data/lib/splitclient-rb/split_factory_builder.rb +16 -0
- data/lib/splitclient-rb/utilitites.rb +41 -0
- data/lib/splitclient-rb/version.rb +3 -0
- data/splitclient-rb.gemspec +50 -0
- data/splitio.yml.example +7 -0
- data/tasks/benchmark_get_treatment.rake +43 -0
- data/tasks/irb.rake +4 -0
- data/tasks/rspec.rake +3 -0
- metadata +321 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Senders
|
4
|
+
class MetricsSender
|
5
|
+
def initialize(metrics_repository, config, api_key)
|
6
|
+
@metrics_repository = metrics_repository
|
7
|
+
@config = config
|
8
|
+
@api_key = api_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
return if ENV['SPLITCLIENT_ENV'] == 'test'
|
13
|
+
|
14
|
+
metrics_thread
|
15
|
+
|
16
|
+
if defined?(PhusionPassenger)
|
17
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
18
|
+
metrics_thread if forked
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def metrics_thread
|
26
|
+
@config.threads[:metrics_sender] = Thread.new do
|
27
|
+
@config.logger.info('Starting metrics service')
|
28
|
+
|
29
|
+
loop do
|
30
|
+
post_metrics
|
31
|
+
|
32
|
+
sleep(SplitIoClient::Utilities.randomize_interval(@config.metrics_refresh_rate))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_metrics
|
38
|
+
metrics_client.post
|
39
|
+
rescue StandardError => error
|
40
|
+
@config.log_found_exception(__method__.to_s, error)
|
41
|
+
end
|
42
|
+
|
43
|
+
def metrics_client
|
44
|
+
SplitIoClient::Api::Metrics.new(@api_key, @config, @metrics_repository)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module SplitIoClient
|
5
|
+
module Cache
|
6
|
+
module Stores
|
7
|
+
class SDKBlocker
|
8
|
+
attr_reader :splits_repository
|
9
|
+
attr_writer :splits_thread, :segments_thread
|
10
|
+
|
11
|
+
def initialize(config, splits_repository, segments_repository)
|
12
|
+
@config = config
|
13
|
+
@splits_repository = splits_repository
|
14
|
+
@segments_repository = segments_repository
|
15
|
+
|
16
|
+
@splits_repository.not_ready!
|
17
|
+
@segments_repository.not_ready!
|
18
|
+
end
|
19
|
+
|
20
|
+
def splits_ready!
|
21
|
+
@splits_repository.ready!
|
22
|
+
end
|
23
|
+
|
24
|
+
def segments_ready!
|
25
|
+
@segments_repository.ready!
|
26
|
+
end
|
27
|
+
|
28
|
+
def block
|
29
|
+
begin
|
30
|
+
Timeout::timeout(@config.block_until_ready) do
|
31
|
+
sleep 0.1 until ready?
|
32
|
+
end
|
33
|
+
rescue Timeout::Error
|
34
|
+
fail SDKBlockerTimeoutExpiredException, 'SDK start up timeout expired'
|
35
|
+
end
|
36
|
+
|
37
|
+
@config.logger.info('SplitIO SDK is ready')
|
38
|
+
@splits_thread.run
|
39
|
+
@segments_thread.run
|
40
|
+
end
|
41
|
+
|
42
|
+
def ready?
|
43
|
+
@splits_repository.ready? && @segments_repository.ready?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Stores
|
4
|
+
class SegmentStore
|
5
|
+
attr_reader :segments_repository
|
6
|
+
|
7
|
+
def initialize(segments_repository, config, api_key, metrics, sdk_blocker = nil)
|
8
|
+
@segments_repository = segments_repository
|
9
|
+
@config = config
|
10
|
+
@api_key = api_key
|
11
|
+
@metrics = metrics
|
12
|
+
@sdk_blocker = sdk_blocker
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
17
|
+
store_segments
|
18
|
+
else
|
19
|
+
segments_thread
|
20
|
+
|
21
|
+
if defined?(PhusionPassenger)
|
22
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
23
|
+
segments_thread if forked
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def segments_thread
|
32
|
+
@config.threads[:segment_store] = @sdk_blocker.segments_thread = Thread.new do
|
33
|
+
@config.logger.info('Starting segments fetcher service')
|
34
|
+
@config.block_until_ready > 0 ? blocked_store : unblocked_store
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def blocked_store
|
39
|
+
loop do
|
40
|
+
next unless @sdk_blocker.splits_repository.ready?
|
41
|
+
|
42
|
+
store_segments
|
43
|
+
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
44
|
+
|
45
|
+
unless @sdk_blocker.ready?
|
46
|
+
@sdk_blocker.segments_ready!
|
47
|
+
@config.logger.info('segments are ready')
|
48
|
+
end
|
49
|
+
|
50
|
+
sleep_for = random_interval(@config.segments_refresh_rate)
|
51
|
+
@config.logger.debug("Segments store is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
|
52
|
+
sleep(sleep_for)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def unblocked_store
|
57
|
+
loop do
|
58
|
+
store_segments
|
59
|
+
|
60
|
+
sleep(random_interval(@config.segments_refresh_rate))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def store_segments
|
65
|
+
segments_api.store_segments_by_names(@segments_repository.used_segment_names)
|
66
|
+
rescue StandardError => error
|
67
|
+
@config.log_found_exception(__method__.to_s, error)
|
68
|
+
end
|
69
|
+
|
70
|
+
def random_interval(interval)
|
71
|
+
random_factor = Random.new.rand(50..100) / 100.0
|
72
|
+
|
73
|
+
interval * random_factor
|
74
|
+
end
|
75
|
+
|
76
|
+
def segments_api
|
77
|
+
SplitIoClient::Api::Segments.new(@api_key, @config, @metrics, @segments_repository)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Stores
|
4
|
+
class SplitStore
|
5
|
+
attr_reader :splits_repository
|
6
|
+
|
7
|
+
def initialize(splits_repository, config, api_key, metrics, sdk_blocker = nil)
|
8
|
+
@splits_repository = splits_repository
|
9
|
+
@config = config
|
10
|
+
@api_key = api_key
|
11
|
+
@metrics = metrics
|
12
|
+
@sdk_blocker = sdk_blocker
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
17
|
+
store_splits
|
18
|
+
else
|
19
|
+
splits_thread
|
20
|
+
|
21
|
+
if defined?(PhusionPassenger)
|
22
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
23
|
+
splits_thread if forked
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def splits_thread
|
32
|
+
@config.threads[:split_store] = @sdk_blocker.splits_thread = Thread.new do
|
33
|
+
@config.logger.info('Starting splits fetcher service')
|
34
|
+
loop do
|
35
|
+
store_splits
|
36
|
+
|
37
|
+
sleep(random_interval(@config.features_refresh_rate))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def store_splits
|
43
|
+
data = splits_since(@splits_repository.get_change_number)
|
44
|
+
|
45
|
+
data[:splits] && data[:splits].each do |split|
|
46
|
+
add_split_unless_archived(split)
|
47
|
+
end
|
48
|
+
|
49
|
+
@splits_repository.set_segment_names(data[:segment_names])
|
50
|
+
@splits_repository.set_change_number(data[:till])
|
51
|
+
|
52
|
+
@config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
|
53
|
+
|
54
|
+
if @config.block_until_ready > 0 && !@sdk_blocker.ready?
|
55
|
+
@sdk_blocker.splits_ready!
|
56
|
+
@config.logger.info('splits are ready')
|
57
|
+
end
|
58
|
+
|
59
|
+
rescue StandardError => error
|
60
|
+
@config.log_found_exception(__method__.to_s, error)
|
61
|
+
end
|
62
|
+
|
63
|
+
def random_interval(interval)
|
64
|
+
random_factor = Random.new.rand(50..100) / 100.0
|
65
|
+
|
66
|
+
interval * random_factor
|
67
|
+
end
|
68
|
+
|
69
|
+
def splits_since(since)
|
70
|
+
SplitIoClient::Api::Splits.new(@api_key, @config, @metrics).since(since)
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_split_unless_archived(split)
|
74
|
+
if Engine::Models::Split.archived?(split)
|
75
|
+
@config.logger.debug("Seeing archived split #{split[:name]}") if @config.debug_enabled
|
76
|
+
|
77
|
+
remove_archived_split(split)
|
78
|
+
else
|
79
|
+
store_split(split)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def remove_archived_split(split)
|
84
|
+
@config.logger.debug("removing split from store(#{split})") if @config.debug_enabled
|
85
|
+
|
86
|
+
@splits_repository.remove_split(split[:name])
|
87
|
+
end
|
88
|
+
|
89
|
+
def store_split(split)
|
90
|
+
@config.logger.debug("storing split (#{split[:name]})") if @config.debug_enabled
|
91
|
+
|
92
|
+
@splits_repository.add_split(split)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
class LocalhostSplitClient
|
3
|
+
include SplitIoClient::LocalhostUtils
|
4
|
+
|
5
|
+
#
|
6
|
+
# variables to if the sdk is being used in localhost mode and store the list of features
|
7
|
+
attr_reader :localhost_mode
|
8
|
+
attr_reader :localhost_mode_features
|
9
|
+
|
10
|
+
#
|
11
|
+
# Creates a new split client instance that reads from the given splits file
|
12
|
+
#
|
13
|
+
# @param splits_file [File] file that contains some splits
|
14
|
+
#
|
15
|
+
# @return [LocalhostSplitIoClient] split.io localhost client instance
|
16
|
+
def initialize(splits_file, reload_rate = nil)
|
17
|
+
@localhost_mode = true
|
18
|
+
@localhost_mode_features = []
|
19
|
+
load_localhost_mode_features(splits_file, reload_rate)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# method that returns the sdk gem version
|
24
|
+
#
|
25
|
+
# @return [string] version value for this sdk
|
26
|
+
def self.sdk_version
|
27
|
+
'ruby-'+SplitIoClient::VERSION
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_treatments(key, split_names, attributes = nil)
|
31
|
+
split_names.each_with_object({}) do |name, memo|
|
32
|
+
puts "name #{name} memo #{memo}"
|
33
|
+
memo.merge!(name => get_treatment(key, name, attributes))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# obtains the treatment for a given feature
|
39
|
+
#
|
40
|
+
# @param id [string] user id
|
41
|
+
# @param feature [string] name of the feature that is being validated
|
42
|
+
#
|
43
|
+
# @return [Treatment] treatment constant value
|
44
|
+
def get_treatment(id, feature, attributes = nil)
|
45
|
+
unless id
|
46
|
+
@config.logger.warn('id was null for feature: ' + feature)
|
47
|
+
return SplitIoClient::Engine::Models::Treatment::CONTROL
|
48
|
+
end
|
49
|
+
|
50
|
+
unless feature
|
51
|
+
@config.logger.warn('feature was null for id: ' + id)
|
52
|
+
return SplitIoClient::Engine::Models::Treatment::CONTROL
|
53
|
+
end
|
54
|
+
|
55
|
+
result = get_localhost_treatment(feature)
|
56
|
+
end
|
57
|
+
|
58
|
+
def track
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
#
|
64
|
+
# auxiliary method to get the treatments avoding exceptions
|
65
|
+
#
|
66
|
+
# @param id [string] user id
|
67
|
+
# @param feature [string] name of the feature that is being validated
|
68
|
+
#
|
69
|
+
# @return [Treatment] tretment constant value
|
70
|
+
def get_treatment_without_exception_handling(id, feature, attributes = nil)
|
71
|
+
get_treatment(id, feature, attributes)
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# method to check if the sdk is running in localhost mode based on api key
|
76
|
+
#
|
77
|
+
# @return [boolean] True if is in localhost mode, false otherwise
|
78
|
+
def is_localhost_mode?
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# method to check the treatment for the given feature in localhost mode
|
84
|
+
#
|
85
|
+
# @return [boolean] true if the feature is available in localhost mode, false otherwise
|
86
|
+
def get_localhost_treatment(feature)
|
87
|
+
treatment = @localhost_mode_features.select { |h| h[:feature] == feature }.last || {}
|
88
|
+
|
89
|
+
treatment[:treatment] || SplitIoClient::Engine::Models::Treatment::CONTROL
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
class SplitClient
|
3
|
+
#
|
4
|
+
# Creates a new split client instance that connects to split.io API.
|
5
|
+
#
|
6
|
+
# @param api_key [String] the API key for your split account
|
7
|
+
#
|
8
|
+
# @return [SplitIoClient] split.io client instance
|
9
|
+
def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository)
|
10
|
+
@config = config
|
11
|
+
|
12
|
+
@splits_repository = splits_repository
|
13
|
+
@segments_repository = segments_repository
|
14
|
+
@impressions_repository = impressions_repository
|
15
|
+
@metrics_repository = metrics_repository
|
16
|
+
@events_repository = events_repository
|
17
|
+
|
18
|
+
@adapter = adapter
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_treatments(key, split_names, attributes = nil)
|
22
|
+
bucketing_key, matching_key = keys_from_key(key)
|
23
|
+
evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, true)
|
24
|
+
|
25
|
+
treatments_labels_change_numbers =
|
26
|
+
@splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
|
27
|
+
memo.merge!(name => get_treatment(key, name, attributes, data, false, true, evaluator))
|
28
|
+
end
|
29
|
+
|
30
|
+
if @config.impressions_queue_size > 0
|
31
|
+
time = (Time.now.to_f * 1000.0).to_i
|
32
|
+
@impressions_repository.add_bulk(
|
33
|
+
matching_key, bucketing_key, treatments_labels_change_numbers, time
|
34
|
+
)
|
35
|
+
|
36
|
+
route_impressions(split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
|
37
|
+
end
|
38
|
+
|
39
|
+
split_names = treatments_labels_change_numbers.keys
|
40
|
+
treatments = treatments_labels_change_numbers.values.map { |v| v[:treatment] }
|
41
|
+
|
42
|
+
Hash[split_names.zip(treatments)]
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# obtains the treatment for a given feature
|
47
|
+
#
|
48
|
+
# @param key [String/Hash] user id or hash with matching_key/bucketing_key
|
49
|
+
# @param split_name [String/Array] name of the feature that is being validated or array of them
|
50
|
+
# @param attributes [Hash] attributes to pass to the treatment class
|
51
|
+
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
|
52
|
+
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
|
53
|
+
# @param multiple [Hash] internal flag to signal if method is called by get_treatments
|
54
|
+
# @param evaluator [Evaluator] Evaluator class instance, used to cache treatments
|
55
|
+
#
|
56
|
+
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
|
57
|
+
def get_treatment(
|
58
|
+
key, split_name, attributes = nil, split_data = nil, store_impressions = true,
|
59
|
+
multiple = false, evaluator = nil
|
60
|
+
)
|
61
|
+
bucketing_key, matching_key = keys_from_key(key)
|
62
|
+
treatment_data = { label: Engine::Models::Label::DEFINITION_NOT_FOUND, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL }
|
63
|
+
evaluator ||= Engine::Parser::Evaluator.new(@segments_repository, @splits_repository)
|
64
|
+
|
65
|
+
if matching_key.nil?
|
66
|
+
@config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
|
67
|
+
return parsed_treatment(multiple, treatment_data)
|
68
|
+
end
|
69
|
+
|
70
|
+
if split_name.nil?
|
71
|
+
@config.logger.warn('split_name was null for key: ' + key)
|
72
|
+
return parsed_treatment(multiple, treatment_data)
|
73
|
+
end
|
74
|
+
|
75
|
+
start = Time.now
|
76
|
+
|
77
|
+
begin
|
78
|
+
split = multiple ? split_data : @splits_repository.get_split(split_name)
|
79
|
+
|
80
|
+
if split.nil?
|
81
|
+
@config.logger.debug("split_name: #{split_name} does not exist. Returning CONTROL")
|
82
|
+
return parsed_treatment(multiple, treatment_data)
|
83
|
+
else
|
84
|
+
treatment_data =
|
85
|
+
evaluator.call(
|
86
|
+
{ bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
|
87
|
+
)
|
88
|
+
end
|
89
|
+
rescue StandardError => error
|
90
|
+
@config.log_found_exception(__method__.to_s, error)
|
91
|
+
|
92
|
+
store_impression(
|
93
|
+
split_name, matching_key, bucketing_key,
|
94
|
+
{
|
95
|
+
treatment: SplitIoClient::Engine::Models::Treatment::CONTROL,
|
96
|
+
label: SplitIoClient::Engine::Models::Label::EXCEPTION
|
97
|
+
},
|
98
|
+
store_impressions, attributes
|
99
|
+
)
|
100
|
+
|
101
|
+
return parsed_treatment(multiple, treatment_data)
|
102
|
+
end
|
103
|
+
|
104
|
+
begin
|
105
|
+
latency = (Time.now - start) * 1000.0
|
106
|
+
# Disable impressions if @config.impressions_queue_size == -1
|
107
|
+
split && store_impression(split_name, matching_key, bucketing_key, treatment_data, store_impressions, attributes)
|
108
|
+
|
109
|
+
# Measure
|
110
|
+
@adapter.metrics.time('sdk.get_treatment', latency)
|
111
|
+
rescue StandardError => error
|
112
|
+
@config.log_found_exception(__method__.to_s, error)
|
113
|
+
|
114
|
+
store_impression(
|
115
|
+
split_name, matching_key, bucketing_key,
|
116
|
+
{
|
117
|
+
treatment: SplitIoClient::Engine::Models::Treatment::CONTROL,
|
118
|
+
label: SplitIoClient::Engine::Models::Label::EXCEPTION
|
119
|
+
},
|
120
|
+
store_impressions, attributes
|
121
|
+
)
|
122
|
+
|
123
|
+
return parsed_treatment(multiple, treatment_data)
|
124
|
+
end
|
125
|
+
|
126
|
+
parsed_treatment(multiple, treatment_data)
|
127
|
+
end
|
128
|
+
|
129
|
+
def destroy
|
130
|
+
@config.logger.info('Split client shutdown started...') if @config.debug_enabled
|
131
|
+
|
132
|
+
@config.threads[:impressions_sender].raise(SplitIoClient::ImpressionShutdownException)
|
133
|
+
@config.threads.reject { |k, _| k == :impressions_sender }.each do |name, thread|
|
134
|
+
Thread.kill(thread)
|
135
|
+
end
|
136
|
+
|
137
|
+
@metrics_repository.clear
|
138
|
+
@splits_repository.clear
|
139
|
+
@segments_repository.clear
|
140
|
+
@events_repository.clear
|
141
|
+
|
142
|
+
@config.logger.info('Split client shutdown complete') if @config.debug_enabled
|
143
|
+
end
|
144
|
+
|
145
|
+
def store_impression(split_name, matching_key, bucketing_key, treatment, store_impressions, attributes)
|
146
|
+
time = (Time.now.to_f * 1000.0).to_i
|
147
|
+
route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes) if @config.impression_listener && store_impressions
|
148
|
+
|
149
|
+
return if @config.impressions_queue_size <= 0 || !store_impressions
|
150
|
+
|
151
|
+
@impressions_repository.add(split_name,
|
152
|
+
'keyName' => matching_key,
|
153
|
+
'bucketingKey' => bucketing_key,
|
154
|
+
'treatment' => treatment[:treatment],
|
155
|
+
'label' => @config.labels_enabled ? treatment[:label] : nil,
|
156
|
+
'time' => time,
|
157
|
+
'changeNumber' => treatment[:change_number]
|
158
|
+
)
|
159
|
+
rescue StandardError => error
|
160
|
+
@config.log_found_exception(__method__.to_s, error)
|
161
|
+
end
|
162
|
+
|
163
|
+
def route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
|
164
|
+
impression_router.add(
|
165
|
+
split_name: split_name,
|
166
|
+
matching_key: matching_key,
|
167
|
+
bucketing_key: bucketing_key,
|
168
|
+
time: time,
|
169
|
+
treatment: treatment,
|
170
|
+
attributes: attributes
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def route_impressions(split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
|
175
|
+
impression_router.add_bulk(
|
176
|
+
split_names: split_names,
|
177
|
+
matching_key: matching_key,
|
178
|
+
bucketing_key: bucketing_key,
|
179
|
+
time: time,
|
180
|
+
treatments_labels_change_numbers: treatments_labels_change_numbers,
|
181
|
+
attributes: attributes
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def impression_router
|
186
|
+
@impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
|
187
|
+
end
|
188
|
+
|
189
|
+
def track(key, traffic_type, event_type, value = nil)
|
190
|
+
@events_repository.add(key, traffic_type, event_type, (Time.now.to_f * 1000).to_i, value)
|
191
|
+
end
|
192
|
+
|
193
|
+
def keys_from_key(key)
|
194
|
+
case key.class.to_s
|
195
|
+
when 'Hash'
|
196
|
+
key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k.to_s }
|
197
|
+
else
|
198
|
+
[nil, key].map { |k| k.nil? ? nil : k.to_s }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def parsed_treatment(multiple, treatment_data)
|
203
|
+
if multiple
|
204
|
+
{
|
205
|
+
treatment: treatment_data[:treatment],
|
206
|
+
label: treatment_data[:label],
|
207
|
+
change_number: treatment_data[:change_number]
|
208
|
+
}
|
209
|
+
else
|
210
|
+
treatment_data[:treatment]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|