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,30 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Events
5
+ class MemoryRepository < EventsRepository
6
+ EVENTS_SLICE = 100
7
+
8
+ def initialize(adapter, config)
9
+ @adapter = adapter
10
+ @config = config
11
+ end
12
+
13
+ def add(key, traffic_type, event_type, time, value)
14
+ @adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value))
15
+ rescue ThreadError # queue is full
16
+ if @config.debug_enabled
17
+ @config.logger.warn("Dropping events. Current size is #{@config.events_queue_size}. " \
18
+ "Consider increasing events_queue_size")
19
+ end
20
+ @adapter.clear
21
+ end
22
+
23
+ def clear
24
+ @adapter.clear
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Events
5
+ class RedisRepository < EventsRepository
6
+ EVENTS_SLICE = 100
7
+
8
+ def initialize(adapter, config)
9
+ @adapter = adapter
10
+ @config = config
11
+ end
12
+
13
+ def add(key, traffic_type, event_type, time, value)
14
+ @adapter.add_to_queue(
15
+ namespace_key('.events'),
16
+ { m: metadata, e: event(key, traffic_type, event_type, time, value) }.to_json,
17
+ )
18
+ end
19
+
20
+ def clear
21
+ @adapter.get_from_queue(namespace_key('.events'), EVENTS_SLICE).map do |e|
22
+ JSON.parse(e, symbolize_names: true)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ # Repository which forwards events interface to the selected adapter
5
+ class EventsRepository < Repository
6
+ extend Forwardable
7
+ def_delegators :@adapter, :add, :clear
8
+
9
+ def initialize(adapter, config)
10
+ @config = config
11
+ @adapter = case adapter.class.to_s
12
+ when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
13
+ Repositories::Events::MemoryRepository.new(adapter, config)
14
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
15
+ Repositories::Events::RedisRepository.new(adapter, config)
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def metadata
22
+ {
23
+ s: "#{@config.language}-#{@config.version}",
24
+ i: @config.machine_ip,
25
+ n: @config.machine_name
26
+ }
27
+ end
28
+
29
+ def event(key, traffic_type, event_type, time, value)
30
+ {
31
+ key: key,
32
+ trafficTypeName: traffic_type,
33
+ eventTypeId: event_type,
34
+ value: value,
35
+ timestamp: time
36
+ }.reject { |_, v| v.nil? }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Impressions
5
+ class MemoryRepository
6
+ def initialize(adapter, config)
7
+ @adapter = adapter
8
+ @config = config
9
+ end
10
+
11
+ # Store impression data in the selected adapter
12
+ def add(split_name, data)
13
+ @adapter.add_to_queue(feature: split_name, impressions: data)
14
+ rescue ThreadError # queue is full
15
+ if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
16
+ @config.logger.warn("Dropping impressions. Current size is #{@config.impressions_queue_size}. " \
17
+ "Consider increasing impressions_queue_size")
18
+ end
19
+ end
20
+
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
22
+ treatments_labels_change_numbers.each do |split_name, treatment_label_number|
23
+ add(
24
+ split_name,
25
+ 'keyName' => key,
26
+ 'bucketingKey' => bucketing_key,
27
+ 'treatment' => treatment_label_number[:treatment],
28
+ 'label' => @config.labels_enabled ? treatment_label_number[:label] : nil,
29
+ 'changeNumber' => treatment_label_number[:change_number],
30
+ 'time' => time
31
+ )
32
+ end
33
+ end
34
+
35
+ # Get everything from the queue and leave it empty
36
+ def clear
37
+ @adapter.clear.map { |impression| impression.update(ip: @config.machine_ip) }
38
+ end
39
+
40
+ private
41
+
42
+ def random_sampler
43
+ @random_sampler ||= Random.new
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,78 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Impressions
5
+ class RedisRepository < Repository
6
+ IMPRESSIONS_SLICE = 1000
7
+
8
+ def initialize(adapter, config)
9
+ @adapter = adapter
10
+ @config = config
11
+ end
12
+
13
+ # Store impression data in Redis
14
+ def add(split_name, data)
15
+ @adapter.add_to_set(
16
+ impressions_metrics_key("impressions.#{split_name}"),
17
+ data.to_json
18
+ )
19
+ end
20
+
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
22
+ @adapter.redis.pipelined do
23
+ treatments_labels_change_numbers.each_slice(IMPRESSIONS_SLICE) do |treatments_labels_change_numbers_slice|
24
+ treatments_labels_change_numbers_slice.each do |split_name, treatment_label_change_number|
25
+ add(split_name,
26
+ 'keyName' => key,
27
+ 'bucketingKey' => bucketing_key,
28
+ 'treatment' => treatment_label_change_number[:treatment],
29
+ 'label' => @config.labels_enabled ? treatment_label_change_number[:label] : nil,
30
+ 'changeNumber' => treatment_label_change_number[:change_number],
31
+ 'time' => time)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Get random impressions from redis in batches of size @config.impressions_queue_size,
38
+ # delete fetched impressions afterwards
39
+ def clear
40
+ impressions = impression_keys.each_with_object([]) do |key, memo|
41
+ ip = key.split('/')[-2] # 'prefix/sdk_lang/ip/impressions.name' -> ip
42
+ if ip.nil?
43
+ @config.logger.warn("Impressions IP parse error for key: #{key}")
44
+ next
45
+ end
46
+ split_name = key.split('.').last
47
+ members = @adapter.random_set_elements(key, @config.impressions_queue_size)
48
+ members.each do |impression|
49
+ parsed_impression = JSON.parse(impression)
50
+
51
+ memo << {
52
+ feature: split_name.to_sym,
53
+ impressions: parsed_impression,
54
+ ip: ip
55
+ }
56
+ end
57
+
58
+ @adapter.delete_from_set(key, members)
59
+ end
60
+
61
+ impressions
62
+ rescue StandardError => e
63
+ @config.logger.error("Exception while clearing impressions cache: #{e}")
64
+ end
65
+
66
+ private
67
+
68
+ # Get all sets by prefix
69
+ def impression_keys
70
+ @adapter.find_sets_by_prefix("#{@config.redis_namespace}/*/impressions.*")
71
+ rescue StandardError => e
72
+ @config.logger.error("Exception while fetching impression_keys: #{e}")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ # Repository which forwards impressions interface to the selected adapter
5
+ class ImpressionsRepository < Repository
6
+ extend Forwardable
7
+ def_delegators :@adapter, :add, :add_bulk, :clear, :empty?
8
+
9
+ def initialize(adapter, config)
10
+ @config = config
11
+ @adapter = case adapter.class.to_s
12
+ when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
13
+ Repositories::Impressions::MemoryRepository.new(adapter, config)
14
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
15
+ Repositories::Impressions::RedisRepository.new(adapter, config)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,129 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Metrics
5
+ class MemoryRepository
6
+ def initialize(_ = nil, adapter, config)
7
+ @counts = []
8
+ @latencies = []
9
+ @gauges = []
10
+
11
+ @config = config
12
+ end
13
+
14
+ def add_count(counter, delta)
15
+ counter_hash = @counts.find { |c| c[:name] == counter }
16
+ if counter_hash.nil?
17
+ counter_delta = SumAndCount.new
18
+ counter_delta.add_delta(delta)
19
+ @counts << { name: counter, delta: counter_delta }
20
+ else
21
+ counter_hash[:delta].add_delta(delta)
22
+ end
23
+ end
24
+
25
+ def add_latency(operation, time_in_ms, binary_search)
26
+ operation_hash = @latencies.find { |l| l[:operation] == operation }
27
+ if operation_hash.nil?
28
+ latencies_for_op = (operation == 'sdk.get_treatment') ? binary_search.add_latency_millis(time_in_ms) : [time_in_ms]
29
+ @latencies << { operation: operation, latencies: latencies_for_op }
30
+ else
31
+ latencies_for_op = (operation == 'sdk.get_treatment') ? binary_search.add_latency_millis(time_in_ms) : operation_hash[:latencies].push(time_in_ms)
32
+ end
33
+ end
34
+
35
+ def add_gauge(gauge, value)
36
+ gauge_hash = @gauges.find { |g| g[:name] == gauge }
37
+ if gauge_hash.nil?
38
+ gauge_value = ValueAndCount.new
39
+ gauge_value.set_value(value)
40
+ @gauges << { name: gauge, value: gauge_value }
41
+ else
42
+ gauge_hash[:value].set_value(value)
43
+ end
44
+ end
45
+
46
+ def counts
47
+ @counts.each_with_object({}) do |count, memo|
48
+ memo[count[:name]] = count[:delta].sum
49
+ end
50
+ end
51
+
52
+ def latencies
53
+ @latencies.each_with_object({}) do |latency, memo|
54
+ memo[latency[:operation]] = latency[:latencies]
55
+ end
56
+ end
57
+
58
+ def gauges
59
+ # TODO
60
+ end
61
+
62
+ def clear_counts
63
+ @counts = []
64
+ end
65
+
66
+ def clear_latencies
67
+ @latencies = []
68
+ end
69
+
70
+ def clear_gauges
71
+ @gauges = []
72
+ end
73
+
74
+ def clear
75
+ clear_counts
76
+ clear_latencies
77
+ clear_gauges
78
+ end
79
+
80
+ #
81
+ # small class to act as DTO for counts
82
+ #
83
+ class SumAndCount
84
+ attr_reader :count
85
+ attr_reader :sum
86
+
87
+ def initialize
88
+ @count = 0
89
+ @sum = 0
90
+ end
91
+
92
+ def add_delta(delta)
93
+ @count += 1
94
+ @sum += delta
95
+ end
96
+
97
+ def clear
98
+ @count = 0
99
+ @sum = 0
100
+ end
101
+ end
102
+
103
+ #
104
+ # small class to act as DTO for gauges
105
+ #
106
+ class ValueAndCount
107
+ attr_reader :count
108
+ attr_reader :value
109
+
110
+ def initialize
111
+ @count = 0
112
+ @value = 0
113
+ end
114
+
115
+ def set_value(value)
116
+ @count += 1
117
+ @value = value
118
+ end
119
+
120
+ def clear
121
+ @count = 0
122
+ @value = 0
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,98 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Metrics
5
+ class RedisRepository < Repository
6
+ def initialize(adapter = nil, config)
7
+ @config = config
8
+ @adapter = adapter
9
+ end
10
+
11
+ def add_count(counter, delta)
12
+ prefixed_name = impressions_metrics_key("count.#{counter}")
13
+ counts = @adapter.find_strings_by_prefix(prefixed_name)
14
+
15
+ @adapter.inc(prefixed_name, delta)
16
+ end
17
+
18
+ def add_latency(operation, time_in_ms, binary_search)
19
+ prefixed_name = impressions_metrics_key("latency.#{operation}")
20
+ latencies = @adapter.find_strings_by_prefix(prefixed_name)
21
+
22
+ if operation == 'sdk.get_treatment'
23
+ @adapter.inc("#{prefixed_name}.#{binary_search.add_latency_millis(time_in_ms, true)}")
24
+ return
25
+ end
26
+
27
+ @adapter.append_to_string(prefixed_name, "#{time_in_ms},")
28
+ end
29
+
30
+ def add_gauge(gauge, value)
31
+ # TODO
32
+ end
33
+
34
+ def counts
35
+ keys = @adapter.find_strings_by_prefix(impressions_metrics_key("count"))
36
+
37
+ return [] if keys.empty?
38
+
39
+ @adapter.multiple_strings(keys).map do |name, data|
40
+ [name.gsub(impressions_metrics_key('count.'), ''), data]
41
+ end.to_h
42
+ end
43
+
44
+ def latencies
45
+ collected_latencies = {}
46
+ latencies_array = Array.new(BinarySearchLatencyTracker::BUCKETS.length, 0)
47
+ keys = @adapter.find_strings_by_prefix(impressions_metrics_key('latency'))
48
+
49
+ return [] if keys.empty?
50
+
51
+ found_latencies = @adapter.multiple_strings(keys).map do |name, data|
52
+ [name.gsub(impressions_metrics_key('latency.'), ''), data]
53
+ end.to_h
54
+
55
+ found_latencies.each do |key, value|
56
+ if key.start_with?('sdk.get_treatment')
57
+ index = key.gsub('sdk.get_treatment.', '').to_i
58
+ latencies_array[index] = value.to_i
59
+
60
+ next
61
+ end
62
+
63
+ collected_latencies[key] = value.split(',').map(&:to_f)
64
+ end
65
+
66
+ collected_latencies['sdk.get_treatment'] = latencies_array unless latencies_array.reduce(:+) == 0
67
+
68
+ collected_latencies
69
+ end
70
+
71
+ def gauges
72
+ # TODO
73
+ end
74
+
75
+ def clear_counts
76
+ keys = @adapter.find_strings_by_prefix(impressions_metrics_key('count'))
77
+ @adapter.delete(keys)
78
+ end
79
+
80
+ def clear_latencies
81
+ keys = @adapter.find_strings_by_prefix(impressions_metrics_key('latency'))
82
+ @adapter.delete(keys)
83
+ end
84
+
85
+ def clear_gauges
86
+ # TODO
87
+ end
88
+
89
+ def clear
90
+ clear_counts
91
+ clear_latencies
92
+ clear_gauges
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end