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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +202 -0
  5. data/README.md +152 -0
  6. data/Rakefile +4 -0
  7. data/lib/splitclient-cache/local_store.rb +45 -0
  8. data/lib/splitclient-engine/evaluator/splitter.rb +110 -0
  9. data/lib/splitclient-engine/impressions/impressions.rb +79 -0
  10. data/lib/splitclient-engine/matchers/all_keys_matcher.rb +46 -0
  11. data/lib/splitclient-engine/matchers/combiners.rb +13 -0
  12. data/lib/splitclient-engine/matchers/combining_matcher.rb +94 -0
  13. data/lib/splitclient-engine/matchers/negation_matcher.rb +54 -0
  14. data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +58 -0
  15. data/lib/splitclient-engine/matchers/whitelist_matcher.rb +55 -0
  16. data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +122 -0
  17. data/lib/splitclient-engine/metrics/metrics.rb +158 -0
  18. data/lib/splitclient-engine/parser/condition.rb +90 -0
  19. data/lib/splitclient-engine/parser/partition.rb +37 -0
  20. data/lib/splitclient-engine/parser/segment.rb +84 -0
  21. data/lib/splitclient-engine/parser/segment_parser.rb +46 -0
  22. data/lib/splitclient-engine/parser/split.rb +68 -0
  23. data/lib/splitclient-engine/parser/split_adapter.rb +433 -0
  24. data/lib/splitclient-engine/parser/split_parser.rb +129 -0
  25. data/lib/splitclient-engine/partitions/treatments.rb +40 -0
  26. data/lib/splitclient-rb.rb +22 -0
  27. data/lib/splitclient-rb/split_client.rb +170 -0
  28. data/lib/splitclient-rb/split_config.rb +193 -0
  29. data/lib/splitclient-rb/version.rb +3 -0
  30. data/splitclient-rb.gemspec +44 -0
  31. data/tasks/benchmark_is_treatment.rake +37 -0
  32. data/tasks/concurrent_benchmark_is_treatment.rake +43 -0
  33. data/tasks/console.rake +4 -0
  34. data/tasks/rspec.rake +3 -0
  35. 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