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