splitclient-rb 0.1.3
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.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +152 -0
- data/Rakefile +4 -0
- data/lib/splitclient-cache/local_store.rb +45 -0
- data/lib/splitclient-engine/evaluator/splitter.rb +110 -0
- data/lib/splitclient-engine/impressions/impressions.rb +79 -0
- data/lib/splitclient-engine/matchers/all_keys_matcher.rb +46 -0
- data/lib/splitclient-engine/matchers/combiners.rb +13 -0
- data/lib/splitclient-engine/matchers/combining_matcher.rb +94 -0
- data/lib/splitclient-engine/matchers/negation_matcher.rb +54 -0
- data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +58 -0
- data/lib/splitclient-engine/matchers/whitelist_matcher.rb +55 -0
- data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +122 -0
- data/lib/splitclient-engine/metrics/metrics.rb +158 -0
- data/lib/splitclient-engine/parser/condition.rb +90 -0
- data/lib/splitclient-engine/parser/partition.rb +37 -0
- data/lib/splitclient-engine/parser/segment.rb +84 -0
- data/lib/splitclient-engine/parser/segment_parser.rb +46 -0
- data/lib/splitclient-engine/parser/split.rb +68 -0
- data/lib/splitclient-engine/parser/split_adapter.rb +433 -0
- data/lib/splitclient-engine/parser/split_parser.rb +129 -0
- data/lib/splitclient-engine/partitions/treatments.rb +40 -0
- data/lib/splitclient-rb.rb +22 -0
- data/lib/splitclient-rb/split_client.rb +170 -0
- data/lib/splitclient-rb/split_config.rb +193 -0
- data/lib/splitclient-rb/version.rb +3 -0
- data/splitclient-rb.gemspec +44 -0
- data/tasks/benchmark_is_treatment.rake +37 -0
- data/tasks/concurrent_benchmark_is_treatment.rake +43 -0
- data/tasks/console.rake +4 -0
- data/tasks/rspec.rake +3 -0
- metadata +260 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# class to implement the user defined matcher
|
|
5
|
+
#
|
|
6
|
+
class WhitelistMatcher < NoMethodError
|
|
7
|
+
|
|
8
|
+
# variable that contains the keys of the whitelist
|
|
9
|
+
@whitelist = []
|
|
10
|
+
|
|
11
|
+
def initialize(whitelist)
|
|
12
|
+
unless whitelist.nil?
|
|
13
|
+
@whitelist = whitelist
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# evaluates if the key matches the matcher
|
|
19
|
+
#
|
|
20
|
+
# @param key [string] key value to be matched
|
|
21
|
+
#
|
|
22
|
+
# @return [boolean] evaluation of the key against the whitelist
|
|
23
|
+
def match?(key)
|
|
24
|
+
@whitelist.include?(key)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# evaluates if the given object equals the matcher
|
|
29
|
+
#
|
|
30
|
+
# @param obj [object] object to be evaluated
|
|
31
|
+
#
|
|
32
|
+
# @returns [boolean] true if obj equals the matcher
|
|
33
|
+
def equals?(obj)
|
|
34
|
+
if obj.nil?
|
|
35
|
+
false
|
|
36
|
+
elsif !obj.instance_of?(WhitelistMatcher)
|
|
37
|
+
false
|
|
38
|
+
elsif self.equal?(obj)
|
|
39
|
+
true
|
|
40
|
+
else
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# function to print string value for this matcher
|
|
47
|
+
#
|
|
48
|
+
# @reutrn [string] string value of this matcher
|
|
49
|
+
def to_s
|
|
50
|
+
'in segment ' + @whitelist.to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Tracks latencies pero bucket of time.
|
|
5
|
+
# Each bucket represent a latency greater than the one before
|
|
6
|
+
# and each number within each bucket is a number of calls in the range.
|
|
7
|
+
#
|
|
8
|
+
# (1) 1.00
|
|
9
|
+
# (2) 1.50
|
|
10
|
+
# (3) 2.25
|
|
11
|
+
# (4) 3.38
|
|
12
|
+
# (5) 5.06
|
|
13
|
+
# (6) 7.59
|
|
14
|
+
# (7) 11.39
|
|
15
|
+
# (8) 17.09
|
|
16
|
+
# (9) 25.63
|
|
17
|
+
# (10) 38.44
|
|
18
|
+
# (11) 57.67
|
|
19
|
+
# (12) 86.50
|
|
20
|
+
# (13) 129.75
|
|
21
|
+
# (14) 194.62
|
|
22
|
+
# (15) 291.93
|
|
23
|
+
# (16) 437.89
|
|
24
|
+
# (17) 656.84
|
|
25
|
+
# (18) 985.26
|
|
26
|
+
# (19) 1,477.89
|
|
27
|
+
# (20) 2,216.84
|
|
28
|
+
# (21) 3,325.26
|
|
29
|
+
# (22) 4,987.89
|
|
30
|
+
# (23) 7,481.83
|
|
31
|
+
#
|
|
32
|
+
# Created by fvitale on 2/17/16 based on java implementation by patricioe.
|
|
33
|
+
#
|
|
34
|
+
|
|
35
|
+
class BinarySearchLatencyTracker < NoMethodError
|
|
36
|
+
|
|
37
|
+
BUCKETS = [ 1000, 1500, 2250, 3375, 5063,
|
|
38
|
+
7594, 11391, 17086, 25629, 38443,
|
|
39
|
+
57665, 86498, 129746, 194620, 291929,
|
|
40
|
+
437894, 656841, 985261, 1477892, 2216838,
|
|
41
|
+
3325257, 4987885, 7481828 ].freeze
|
|
42
|
+
|
|
43
|
+
MAX_LATENCY = 7481828
|
|
44
|
+
|
|
45
|
+
attr_accessor :latencies
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
@latencies = Array.new(BUCKETS.length, 0)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# Increment the internal counter for the bucket this latency falls into.
|
|
53
|
+
# @param millis
|
|
54
|
+
#
|
|
55
|
+
def add_latency_millis(millis)
|
|
56
|
+
index = find_bucket_index(millis * 1000)
|
|
57
|
+
@latencies[index] += 1
|
|
58
|
+
@latencies
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Increment the internal counter for the bucket this latency falls into.
|
|
62
|
+
# @param micros
|
|
63
|
+
def add_latency_micros(micros)
|
|
64
|
+
index = find_bucket_index(micros)
|
|
65
|
+
@latencies[index] += 1
|
|
66
|
+
@latencies
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the list of latencies buckets as an array.
|
|
70
|
+
#
|
|
71
|
+
#
|
|
72
|
+
# @return the list of latencies buckets as an array.
|
|
73
|
+
def get_latencies
|
|
74
|
+
@latencies
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_latency(index)
|
|
78
|
+
return @latencies[index]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def clear
|
|
82
|
+
@latencies = Array.new(BUCKETS.length, 0)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Returns the counts in the bucket this latency falls into.
|
|
87
|
+
# The latencies will not be updated.
|
|
88
|
+
# @param latency
|
|
89
|
+
# @return the bucket content for the latency.
|
|
90
|
+
#
|
|
91
|
+
def get_bucket_for_latency_millis(latency)
|
|
92
|
+
return @latencies[find_bucket_index(latency * 1000)]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Returns the counts in the bucket this latency falls into.
|
|
97
|
+
# The latencies will not be updated.
|
|
98
|
+
# @param latency
|
|
99
|
+
# @return the bucket content for the latency.
|
|
100
|
+
#
|
|
101
|
+
def get_bucket_for_latency_micros(latency)
|
|
102
|
+
return @latencies[find_bucket_index(latency)]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def find_bucket_index(micros)
|
|
108
|
+
if (micros > MAX_LATENCY) then
|
|
109
|
+
return BUCKETS.length - 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if (micros < 1500) then
|
|
113
|
+
return 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
index = BUCKETS.find_index(BUCKETS.bsearch {|x| x >= micros })
|
|
117
|
+
|
|
118
|
+
return index
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
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)
|
|
36
|
+
@latencies = []
|
|
37
|
+
@counts = []
|
|
38
|
+
@gauges = []
|
|
39
|
+
@queue_size = queue_size
|
|
40
|
+
@binary_search = SplitIoClient::BinarySearchLatencyTracker.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# creates a new entry in the array for cached counts
|
|
45
|
+
#
|
|
46
|
+
# @param counter [string] name of the counter
|
|
47
|
+
# @delta [int] value of the counter
|
|
48
|
+
#
|
|
49
|
+
# @return void
|
|
50
|
+
def count(counter, delta)
|
|
51
|
+
return if delta <= 0
|
|
52
|
+
|
|
53
|
+
return if (counter.nil? || counter.strip.empty?)
|
|
54
|
+
|
|
55
|
+
counter_hash = @counts.find { |c| c[:name] == counter }
|
|
56
|
+
if counter_hash.nil?
|
|
57
|
+
counter_delta = SumAndCount.new
|
|
58
|
+
counter_delta.add_delta(delta)
|
|
59
|
+
@counts << {name: counter, delta: counter_delta}
|
|
60
|
+
else
|
|
61
|
+
counter_hash[:delta].add_delta(delta)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# creates a new entry in the array for cached time metrics
|
|
67
|
+
#
|
|
68
|
+
# @param operation [string] name of the operation
|
|
69
|
+
# @time_in_ms [number] time in miliseconds
|
|
70
|
+
#
|
|
71
|
+
# @return void
|
|
72
|
+
def time(operation, time_in_ms)
|
|
73
|
+
|
|
74
|
+
if operation.nil? || operation.empty? || time_in_ms < 0
|
|
75
|
+
return;
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
operation_hash = @latencies.find { |l| l[:operation] == operation }
|
|
79
|
+
if operation_hash.nil?
|
|
80
|
+
latencies_for_op = (operation == 'sdk.get_treatment') ? @binary_search.add_latency_millis(time_in_ms) : [time_in_ms]
|
|
81
|
+
@latencies << {operation: operation, latencies: latencies_for_op}
|
|
82
|
+
else
|
|
83
|
+
latencies_for_op = (operation == 'sdk.get_treatment') ? @binary_search.add_latency_millis(time_in_ms) : operation_hash[:latencies].push(time_in_ms)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# creates a new entry in the array for cached gauges
|
|
89
|
+
#
|
|
90
|
+
# @param gauge [string] name of the gauge
|
|
91
|
+
# @value [number] value of the gauge
|
|
92
|
+
#
|
|
93
|
+
# @return void
|
|
94
|
+
def gauge(gauge, value)
|
|
95
|
+
if gauge.nil? || gauge.empty?
|
|
96
|
+
return
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
gauge_hash = @gauges.find { |g| g[:name] == gauge }
|
|
100
|
+
if gauge_hash.nil?
|
|
101
|
+
gauge_value = ValueAndCount.new
|
|
102
|
+
gauge_value.set_value(value)
|
|
103
|
+
@gauges << {name: gauge, value: gauge_value}
|
|
104
|
+
else
|
|
105
|
+
gauge_hash[:value].set_value(value)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# small class to act as DTO for counts
|
|
113
|
+
#
|
|
114
|
+
class SumAndCount
|
|
115
|
+
attr_reader :count
|
|
116
|
+
attr_reader :sum
|
|
117
|
+
|
|
118
|
+
def initialize
|
|
119
|
+
@count = 0
|
|
120
|
+
@sum = 0
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def add_delta(delta)
|
|
124
|
+
@count++
|
|
125
|
+
@sum += delta
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def clear
|
|
129
|
+
@count = 0
|
|
130
|
+
@sum = 0
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# small class to act as DTO for gauges
|
|
136
|
+
#
|
|
137
|
+
class ValueAndCount
|
|
138
|
+
attr_reader :count
|
|
139
|
+
attr_reader :value
|
|
140
|
+
|
|
141
|
+
def initialize
|
|
142
|
+
@count = 0
|
|
143
|
+
@value = 0
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def set_value(value)
|
|
147
|
+
@count += 1
|
|
148
|
+
@value = value
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def clear
|
|
152
|
+
@count = 0
|
|
153
|
+
@value = 0
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# acts as dto for a condition structure
|
|
5
|
+
#
|
|
6
|
+
class Condition < NoMethodError
|
|
7
|
+
|
|
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
|
+
#
|
|
20
|
+
# @return [object] the combiner value for this condition
|
|
21
|
+
def combiner
|
|
22
|
+
@data[:matcherGroup][:combiner]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# @return [object] the matcher value for this condition
|
|
27
|
+
def matcher
|
|
28
|
+
@data[:matcherGroup][:matchers].first[:matcherType]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# @return [object] the matchers array value for this condition
|
|
33
|
+
def matchers
|
|
34
|
+
@data[:matcherGroup][:matchers]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# @return [object] the whitelist for this condition in case it has a whitelist matcher
|
|
39
|
+
def matcher_whitelist
|
|
40
|
+
result = nil
|
|
41
|
+
if self.matcher == 'WHITELIST'
|
|
42
|
+
result = (@data[:matcherGroup][:matchers].first[:whitelistMatcherData])[:whitelist]
|
|
43
|
+
end
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# @return [object] the segment for this condition in case it has a segment matcher
|
|
49
|
+
def matcher_segment
|
|
50
|
+
result = nil
|
|
51
|
+
if self.matcher == 'IN_SEGMENT'
|
|
52
|
+
result = (@data[:matcherGroup][:matchers].first[:userDefinedSegmentMatcherData])[:segmentName]
|
|
53
|
+
end
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# @return [object] the negate value for this condition
|
|
59
|
+
def negate
|
|
60
|
+
@data[:matcherGroup][:matchers].first[:negate]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#
|
|
64
|
+
# @return [object] the array of partitions for this condition
|
|
65
|
+
def partitions
|
|
66
|
+
@partitions
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# converts the partitions hash for this condition into an array of partition objects
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def set_partitions
|
|
74
|
+
partitions_list = []
|
|
75
|
+
@data[:partitions].each do |p|
|
|
76
|
+
partition = SplitIoClient::Partition.new(p)
|
|
77
|
+
partitions_list << partition
|
|
78
|
+
end
|
|
79
|
+
partitions_list
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
#
|
|
83
|
+
# @return [boolean] true if the condition is empty false otherwise
|
|
84
|
+
def is_empty?
|
|
85
|
+
@data.empty? ? true : false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# acts as dto for a partition structure
|
|
5
|
+
#
|
|
6
|
+
class Partition < NoMethodError
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# definition of the condition
|
|
10
|
+
#
|
|
11
|
+
# @returns [object] condition values
|
|
12
|
+
attr_accessor :data
|
|
13
|
+
|
|
14
|
+
def initialize(partition)
|
|
15
|
+
@data = partition
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# @return [object] the treatment value for this partition
|
|
20
|
+
def treatment
|
|
21
|
+
@data[:treatment]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# @return [object] the size value for this partition
|
|
26
|
+
def size
|
|
27
|
+
@data[:size]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# @return [boolean] true if the partition is empty false otherwise
|
|
32
|
+
def is_empty?
|
|
33
|
+
@data.empty? ? true : false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|