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,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