splitclient-rb 4.5.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES.txt +147 -0
  4. data/Detailed-README.md +571 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +13 -0
  7. data/NEWS +75 -0
  8. data/README.md +43 -0
  9. data/Rakefile +24 -0
  10. data/exe/splitio +96 -0
  11. data/ext/murmurhash/MurmurHash3.java +162 -0
  12. data/lib/murmurhash/base.rb +58 -0
  13. data/lib/murmurhash/murmurhash.jar +0 -0
  14. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  15. data/lib/splitclient-rb.rb +90 -0
  16. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  17. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +133 -0
  18. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +44 -0
  19. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +165 -0
  20. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +30 -0
  21. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +29 -0
  22. data/lib/splitclient-rb/cache/repositories/events_repository.rb +41 -0
  23. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +49 -0
  24. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +78 -0
  25. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +21 -0
  26. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +129 -0
  27. data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +98 -0
  28. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +22 -0
  29. data/lib/splitclient-rb/cache/repositories/repository.rb +23 -0
  30. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +82 -0
  31. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +106 -0
  32. data/lib/splitclient-rb/cache/routers/impression_router.rb +52 -0
  33. data/lib/splitclient-rb/cache/senders/events_sender.rb +47 -0
  34. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +73 -0
  35. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +67 -0
  36. data/lib/splitclient-rb/cache/senders/metrics_sender.rb +49 -0
  37. data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +48 -0
  38. data/lib/splitclient-rb/cache/stores/segment_store.rb +82 -0
  39. data/lib/splitclient-rb/cache/stores/split_store.rb +97 -0
  40. data/lib/splitclient-rb/clients/localhost_split_client.rb +92 -0
  41. data/lib/splitclient-rb/clients/split_client.rb +214 -0
  42. data/lib/splitclient-rb/engine/api/client.rb +74 -0
  43. data/lib/splitclient-rb/engine/api/events.rb +48 -0
  44. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +55 -0
  45. data/lib/splitclient-rb/engine/api/impressions.rb +42 -0
  46. data/lib/splitclient-rb/engine/api/metrics.rb +61 -0
  47. data/lib/splitclient-rb/engine/api/segments.rb +62 -0
  48. data/lib/splitclient-rb/engine/api/splits.rb +60 -0
  49. data/lib/splitclient-rb/engine/evaluator/splitter.rb +123 -0
  50. data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +46 -0
  51. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +56 -0
  52. data/lib/splitclient-rb/engine/matchers/combiners.rb +9 -0
  53. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +86 -0
  54. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +21 -0
  55. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +19 -0
  56. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +30 -0
  57. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +20 -0
  58. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +26 -0
  59. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +27 -0
  60. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +54 -0
  61. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +19 -0
  62. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +53 -0
  63. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +53 -0
  64. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +24 -0
  65. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +60 -0
  66. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +23 -0
  67. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +20 -0
  68. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +26 -0
  69. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +45 -0
  70. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  71. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +128 -0
  72. data/lib/splitclient-rb/engine/metrics/metrics.rb +83 -0
  73. data/lib/splitclient-rb/engine/models/label.rb +8 -0
  74. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  75. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  76. data/lib/splitclient-rb/engine/parser/condition.rb +210 -0
  77. data/lib/splitclient-rb/engine/parser/evaluator.rb +118 -0
  78. data/lib/splitclient-rb/engine/parser/partition.rb +35 -0
  79. data/lib/splitclient-rb/engine/parser/split_adapter.rb +88 -0
  80. data/lib/splitclient-rb/exceptions/impressions_shutdown_exception.rb +4 -0
  81. data/lib/splitclient-rb/exceptions/sdk_blocker_timeout_expired_exception.rb +4 -0
  82. data/lib/splitclient-rb/localhost_split_factory.rb +13 -0
  83. data/lib/splitclient-rb/localhost_utils.rb +36 -0
  84. data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
  85. data/lib/splitclient-rb/managers/split_manager.rb +77 -0
  86. data/lib/splitclient-rb/split_config.rb +391 -0
  87. data/lib/splitclient-rb/split_factory.rb +35 -0
  88. data/lib/splitclient-rb/split_factory_builder.rb +16 -0
  89. data/lib/splitclient-rb/utilitites.rb +41 -0
  90. data/lib/splitclient-rb/version.rb +3 -0
  91. data/splitclient-rb.gemspec +50 -0
  92. data/splitio.yml.example +7 -0
  93. data/tasks/benchmark_get_treatment.rake +43 -0
  94. data/tasks/irb.rake +4 -0
  95. data/tasks/rspec.rake +3 -0
  96. 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