splitclient-rb 4.5.1-java

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 (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,83 @@
1
+ module SplitIoClient
2
+
3
+ #
4
+ # class to handle cached metrics
5
+ #
6
+ class Metrics < NoMethodError
7
+
8
+ @counter
9
+ @delta
10
+
11
+ #
12
+ # cached latencies
13
+ #
14
+ # @return [object] array of latencies
15
+ attr_accessor :latencies
16
+
17
+ #
18
+ # cached counts
19
+ #
20
+ # @return [object] array of counts
21
+ attr_accessor :counts
22
+
23
+ #
24
+ # cached gauges
25
+ #
26
+ # @return [object] array of gauges
27
+ attr_accessor :gauges
28
+
29
+ #
30
+ # quese size for cached arrays
31
+ #
32
+ # @return [int] queue size
33
+ attr_accessor :queue_size
34
+
35
+ def initialize(queue_size, config, repository)
36
+ @queue_size = queue_size
37
+ @binary_search = SplitIoClient::BinarySearchLatencyTracker.new
38
+
39
+ @config = config
40
+
41
+ @repository = repository
42
+ end
43
+
44
+ #
45
+ # creates a new entry in the array for cached counts
46
+ #
47
+ # @param counter [string] name of the counter
48
+ # @delta [int] value of the counter
49
+ #
50
+ # @return void
51
+ def count(counter, delta)
52
+ return if (delta <= 0) || counter.nil? || counter.strip.empty?
53
+
54
+ @repository.add_count(counter, delta)
55
+ end
56
+
57
+ #
58
+ # creates a new entry in the array for cached time metrics
59
+ #
60
+ # @param operation [string] name of the operation
61
+ # @time_in_ms [number] time in miliseconds
62
+ #
63
+ # @return void
64
+ def time(operation, time_in_ms)
65
+ return if operation.nil? || operation.empty? || time_in_ms < 0
66
+
67
+ @repository.add_latency(operation, time_in_ms, @binary_search)
68
+ end
69
+
70
+ #
71
+ # creates a new entry in the array for cached gauges
72
+ #
73
+ # @param gauge [string] name of the gauge
74
+ # @value [number] value of the gauge
75
+ #
76
+ # @return void
77
+ def gauge(gauge, value)
78
+ return if gauge.nil? || gauge.empty?
79
+
80
+ @repository.add_gauge(gauge, value)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,8 @@
1
+ class SplitIoClient::Engine::Models::Label
2
+ ARCHIVED = 'archived'.freeze
3
+ NO_RULE_MATCHED = 'default rule'.freeze
4
+ EXCEPTION = 'exception'.freeze
5
+ KILLED = 'killed'.freeze
6
+ NOT_IN_SPLIT = 'not in split'.freeze
7
+ DEFINITION_NOT_FOUND = 'definition not found'.freeze
8
+ end
@@ -0,0 +1,17 @@
1
+ module SplitIoClient
2
+ module Engine
3
+ module Models
4
+ class Split
5
+ class << self
6
+ def matchable?(data)
7
+ data && data[:status] == 'ACTIVE' && data[:killed] == false
8
+ end
9
+
10
+ def archived?(data)
11
+ data && data[:status] == 'ARCHIVED'
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ class SplitIoClient::Engine::Models::Treatment
2
+ CONTROL = 'control'.freeze
3
+ end
@@ -0,0 +1,210 @@
1
+ module SplitIoClient
2
+ #
3
+ # acts as dto for a condition structure
4
+ #
5
+ class Condition < NoMethodError
6
+ TYPE_ROLLOUT = 'ROLLOUT'.freeze
7
+ TYPE_WHITELIST = 'WHITELIST'.freeze
8
+ #
9
+ # definition of the condition
10
+ #
11
+ # @returns [object] condition values
12
+ attr_accessor :data
13
+
14
+ def initialize(condition)
15
+ @data = condition
16
+ @partitions = set_partitions
17
+ end
18
+
19
+ def create_condition_matcher(matchers)
20
+ CombiningMatcher.new(combiner, matchers) if combiner
21
+ end
22
+
23
+ #
24
+ # @return [object] the combiner value for this condition
25
+ def combiner
26
+ @data[:matcherGroup][:combiner]
27
+ end
28
+
29
+ #
30
+ # @return [object] the matcher value for this condition
31
+ def matcher
32
+ @data[:matcherGroup][:matchers].first[:matcherType]
33
+ end
34
+
35
+ #
36
+ # @return [object] the matchers array value for this condition
37
+ def matchers
38
+ @data[:matcherGroup][:matchers]
39
+ end
40
+
41
+ #
42
+ # @return [string] condition type
43
+ def type
44
+ @data[:conditionType]
45
+ end
46
+
47
+ def negation_matcher(matcher)
48
+ NegationMatcher.new(matcher)
49
+ end
50
+
51
+ def matcher_all_keys(_params)
52
+ @matcher_all_keys ||= AllKeysMatcher.new
53
+ end
54
+
55
+ # returns UserDefinedSegmentMatcher[object]
56
+ def matcher_in_segment(params)
57
+ matcher = params[:matcher]
58
+ segment_name = matcher[:userDefinedSegmentMatcherData] && matcher[:userDefinedSegmentMatcherData][:segmentName]
59
+
60
+ UserDefinedSegmentMatcher.new(params[:segments_repository], segment_name)
61
+ end
62
+
63
+ # returns WhitelistMatcher[object] the whitelist for this condition in case it has a whitelist matcher
64
+ def matcher_whitelist(params)
65
+ result = nil
66
+ matcher = params[:matcher]
67
+ is_user_whitelist = ((matcher[:keySelector]).nil? || (matcher[:keySelector])[:attribute].nil?)
68
+ if is_user_whitelist
69
+ result = (matcher[:whitelistMatcherData])[:whitelist]
70
+ else
71
+ attribute = (matcher[:keySelector])[:attribute]
72
+ white_list = (matcher[:whitelistMatcherData])[:whitelist]
73
+ result = { attribute: attribute, value: white_list }
74
+ end
75
+ WhitelistMatcher.new(result)
76
+ end
77
+
78
+ def matcher_equal_to(params)
79
+ matcher = params[:matcher]
80
+ attribute = (matcher[:keySelector])[:attribute]
81
+ value = (matcher[:unaryNumericMatcherData])[:value]
82
+ data_type = (matcher[:unaryNumericMatcherData])[:dataType]
83
+ EqualToMatcher.new(attribute: attribute, value: value, data_type: data_type)
84
+ end
85
+
86
+ def matcher_greater_than_or_equal_to(params)
87
+ matcher = params[:matcher]
88
+ attribute = (matcher[:keySelector])[:attribute]
89
+ value = (matcher[:unaryNumericMatcherData])[:value]
90
+ data_type = (matcher[:unaryNumericMatcherData])[:dataType]
91
+ GreaterThanOrEqualToMatcher.new(attribute: attribute, value: value, data_type: data_type)
92
+ end
93
+
94
+ def matcher_less_than_or_equal_to(params)
95
+ matcher = params[:matcher]
96
+ attribute = (matcher[:keySelector])[:attribute]
97
+ value = (matcher[:unaryNumericMatcherData])[:value]
98
+ data_type = (matcher[:unaryNumericMatcherData])[:dataType]
99
+ LessThanOrEqualToMatcher.new(attribute: attribute, value: value, data_type: data_type)
100
+ end
101
+
102
+ def matcher_between(params)
103
+ matcher = params[:matcher]
104
+ attribute = (matcher[:keySelector])[:attribute]
105
+ start_value = (matcher[:betweenMatcherData])[:start]
106
+ end_value = (matcher[:betweenMatcherData])[:end]
107
+ data_type = (matcher[:betweenMatcherData])[:dataType]
108
+ BetweenMatcher.new(attribute: attribute, start_value: start_value, end_value: end_value, data_type: data_type)
109
+ end
110
+
111
+ def matcher_equal_to_set(params)
112
+ EqualToSetMatcher.new(
113
+ params[:matcher][:keySelector][:attribute],
114
+ params[:matcher][:whitelistMatcherData][:whitelist]
115
+ )
116
+ end
117
+
118
+ def matcher_contains_any_of_set(params)
119
+ ContainsAnyMatcher.new(
120
+ params[:matcher][:keySelector][:attribute],
121
+ params[:matcher][:whitelistMatcherData][:whitelist]
122
+ )
123
+ end
124
+
125
+ def matcher_contains_all_of_set(params)
126
+ ContainsAllMatcher.new(
127
+ params[:matcher][:keySelector][:attribute],
128
+ params[:matcher][:whitelistMatcherData][:whitelist]
129
+ )
130
+ end
131
+
132
+ def matcher_part_of_set(params)
133
+ PartOfSetMatcher.new(
134
+ params[:matcher][:keySelector][:attribute],
135
+ params[:matcher][:whitelistMatcherData][:whitelist]
136
+ )
137
+ end
138
+
139
+ def matcher_starts_with(params)
140
+ StartsWithMatcher.new(
141
+ params[:matcher][:keySelector][:attribute],
142
+ params[:matcher][:whitelistMatcherData][:whitelist]
143
+ )
144
+ end
145
+
146
+ def matcher_ends_with(params)
147
+ EndsWithMatcher.new(
148
+ params[:matcher][:keySelector][:attribute],
149
+ params[:matcher][:whitelistMatcherData][:whitelist]
150
+ )
151
+ end
152
+
153
+ def matcher_contains_string(params)
154
+ ContainsMatcher.new(
155
+ params[:matcher][:keySelector][:attribute],
156
+ params[:matcher][:whitelistMatcherData][:whitelist]
157
+ )
158
+ end
159
+
160
+ def matcher_in_split_treatment(params)
161
+ DependencyMatcher.new(
162
+ params[:matcher][:dependencyMatcherData][:split],
163
+ params[:matcher][:dependencyMatcherData][:treatments]
164
+ )
165
+ end
166
+
167
+ def matcher_equal_to_boolean(params)
168
+ EqualToBooleanMatcher.new(
169
+ params[:matcher][:keySelector][:attribute],
170
+ params[:matcher][:booleanMatcherData]
171
+ )
172
+ end
173
+
174
+ def matcher_matches_string(params)
175
+ MatchesStringMatcher.new(
176
+ params[:matcher][:keySelector][:attribute],
177
+ params[:matcher][:stringMatcherData]
178
+ )
179
+ end
180
+
181
+ #
182
+ # @return [object] the negate value for this condition
183
+ def negate
184
+ @data[:matcherGroup][:matchers].first[:negate]
185
+ end
186
+
187
+ #
188
+ # @return [object] the array of partitions for this condition
189
+ attr_reader :partitions
190
+
191
+ #
192
+ # converts the partitions hash for this condition into an array of partition objects
193
+ #
194
+ # @return [void]
195
+ def set_partitions
196
+ partitions_list = []
197
+ @data[:partitions].each do |p|
198
+ partition = SplitIoClient::Partition.new(p)
199
+ partitions_list << partition
200
+ end
201
+ partitions_list
202
+ end
203
+
204
+ #
205
+ # @return [boolean] true if the condition is empty false otherwise
206
+ def empty?
207
+ @data.empty?
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,118 @@
1
+ module SplitIoClient
2
+ module Engine
3
+ module Parser
4
+ class Evaluator
5
+ def initialize(segments_repository, splits_repository, multiple = false)
6
+ @splits_repository = splits_repository
7
+ @segments_repository = segments_repository
8
+ @multiple = multiple
9
+ @cache = {}
10
+ end
11
+
12
+ def call(keys, split, attributes = nil)
13
+ # DependencyMatcher here, split is actually a split_name in this case
14
+ cache_result = split.is_a? String
15
+ split = @splits_repository.get_split(split) if cache_result
16
+ digest = Digest::MD5.hexdigest("#{{matching_key: keys[:matching_key]}}#{split}#{attributes}")
17
+
18
+ if Models::Split.archived?(split)
19
+ return treatment_hash(Models::Label::ARCHIVED, Models::Treatment::CONTROL, split[:changeNumber])
20
+ end
21
+
22
+ treatment = if Models::Split.matchable?(split)
23
+ if @multiple && @cache[digest]
24
+ @cache[digest]
25
+ else
26
+ match(split, keys, attributes)
27
+ end
28
+ else
29
+ treatment_hash(Models::Label::KILLED, split[:defaultTreatment], split[:changeNumber])
30
+ end
31
+
32
+ @cache[digest] = treatment if cache_result
33
+
34
+ treatment
35
+ end
36
+
37
+ private
38
+
39
+ def match(split, keys, attributes)
40
+ in_rollout = false
41
+ key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
42
+ legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false
43
+ splitter = Splitter.new
44
+
45
+ split[:conditions].each do |c|
46
+ condition = SplitIoClient::Condition.new(c)
47
+
48
+ next if condition.empty?
49
+
50
+ if !in_rollout && condition.type == SplitIoClient::Condition::TYPE_ROLLOUT
51
+ if split[:trafficAllocation] < 100
52
+ bucket = splitter.bucket(splitter.count_hash(key, split[:trafficAllocationSeed].to_i, legacy_algo))
53
+
54
+ if bucket >= split[:trafficAllocation]
55
+ return treatment_hash(Models::Label::NOT_IN_SPLIT, split[:defaultTreatment], split[:changeNumber])
56
+ end
57
+ end
58
+
59
+ in_rollout = true
60
+ end
61
+
62
+ condition_matched = matcher_type(condition).match?(
63
+ matching_key: keys[:matching_key],
64
+ bucketing_key: keys[:bucketing_key],
65
+ evaluator: self,
66
+ attributes: attributes
67
+ )
68
+
69
+ next unless condition_matched
70
+
71
+ result = splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo])
72
+
73
+ if result.nil?
74
+ return treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber])
75
+ else
76
+ return treatment_hash(c[:label], result, split[:changeNumber])
77
+ end
78
+ end
79
+
80
+ treatment_hash(Models::Label::NO_RULE_MATCHED, split[:defaultTreatment], split[:changeNumber])
81
+ end
82
+
83
+ def matcher_type(condition)
84
+ matchers = []
85
+
86
+ @segments_repository.adapter.pipelined do
87
+ condition.matchers.each do |matcher|
88
+ matchers << if matcher[:negate]
89
+ condition.negation_matcher(matcher_instance(matcher[:matcherType], condition, matcher))
90
+ else
91
+ matcher_instance(matcher[:matcherType], condition, matcher)
92
+ end
93
+ end
94
+ end
95
+
96
+ final_matcher = condition.create_condition_matcher(matchers)
97
+
98
+ if final_matcher.nil?
99
+ @logger.error('Invalid matcher type')
100
+ else
101
+ final_matcher
102
+ end
103
+ end
104
+
105
+ def treatment_hash(label, treatment, change_number = nil)
106
+ { label: label, treatment: treatment, change_number: change_number }
107
+ end
108
+
109
+ def matcher_instance(type, condition, matcher)
110
+ condition.send(
111
+ "matcher_#{type.downcase}",
112
+ matcher: matcher, segments_repository: @segments_repository
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,35 @@
1
+ module SplitIoClient
2
+ #
3
+ # acts as dto for a partition structure
4
+ #
5
+ class Partition < NoMethodError
6
+
7
+ #
8
+ # definition of the condition
9
+ #
10
+ # @returns [object] condition values
11
+ attr_accessor :data
12
+
13
+ def initialize(partition)
14
+ @data = partition
15
+ end
16
+
17
+ #
18
+ # @return [object] the treatment value for this partition
19
+ def treatment
20
+ @data[:treatment]
21
+ end
22
+
23
+ #
24
+ # @return [object] the size value for this partition
25
+ def size
26
+ @data[:size]
27
+ end
28
+
29
+ #
30
+ # @return [boolean] true if the partition is empty false otherwise
31
+ def is_empty?
32
+ @data.empty?
33
+ end
34
+ end
35
+ end