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,22 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ # Repository which forwards impressions interface to the selected adapter
5
+ class MetricsRepository < Repository
6
+ extend Forwardable
7
+ def_delegators :@adapter, :add_count, :add_latency, :add_gauge, :counts, :latencies, :gauges,
8
+ :clear_counts, :clear_latencies, :clear_gauges, :clear
9
+
10
+ def initialize(adapter, config)
11
+ @config = config
12
+ @adapter = case adapter.class.to_s
13
+ when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
14
+ Repositories::Metrics::MemoryRepository.new(adapter, config)
15
+ when 'SplitIoClient::Cache::Adapters::RedisAdapter'
16
+ Repositories::Metrics::RedisRepository.new(adapter, config)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ class Repository
4
+ def set_string(key, str)
5
+ @adapter.set_string(namespace_key(key), str)
6
+ end
7
+
8
+ def string(key)
9
+ @adapter.string(namespace_key(key))
10
+ end
11
+
12
+ protected
13
+
14
+ def namespace_key(key = '')
15
+ "#{@config.redis_namespace}#{key}"
16
+ end
17
+
18
+ def impressions_metrics_key(key)
19
+ namespace_key("/#{@config.language}-#{@config.version}/#{@config.machine_ip}/#{key}")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,82 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ class SegmentsRepository < Repository
5
+ KEYS_SLICE = 3000
6
+
7
+ attr_reader :adapter
8
+
9
+ def initialize(adapter, config)
10
+ @adapter = adapter
11
+ @config = config
12
+
13
+ @adapter.set_bool(namespace_key('.ready'), false)
14
+ end
15
+
16
+ # Receives segment data, adds and removes segements from the store
17
+ def add_to_segment(segment)
18
+ name = segment[:name]
19
+
20
+ @adapter.initialize_set(segment_data(name)) unless @adapter.exists?(segment_data(name))
21
+
22
+ add_keys(name, segment[:added])
23
+ remove_keys(name, segment[:removed])
24
+ end
25
+
26
+ def get_segment_keys(name)
27
+ @adapter.get_set(segment_data(name))
28
+ end
29
+
30
+ def in_segment?(name, key)
31
+ @adapter.in_set?(segment_data(name), key)
32
+ end
33
+
34
+ def used_segment_names
35
+ @adapter.get_set(namespace_key('.segments.registered'))
36
+ end
37
+
38
+ def set_change_number(name, last_change)
39
+ @adapter.set_string(namespace_key(".segment.#{name}.till"), last_change)
40
+ end
41
+
42
+ def get_change_number(name)
43
+ @adapter.string(namespace_key(".segment.#{name}.till")) || -1
44
+ end
45
+
46
+ def ready?
47
+ @adapter.string(namespace_key('.segments.ready')).to_i != -1
48
+ end
49
+
50
+ def not_ready!
51
+ @adapter.set_string(namespace_key('.segments.ready'), -1)
52
+ end
53
+
54
+ def ready!
55
+ @adapter.set_string(namespace_key('.segments.ready'), Time.now.utc.to_i)
56
+ end
57
+
58
+ def clear
59
+ @adapter.clear(namespace_key)
60
+ end
61
+
62
+ private
63
+
64
+ def segment_data(name)
65
+ namespace_key(".segment.#{name}")
66
+ end
67
+
68
+ def add_keys(name, keys)
69
+ keys.each_slice(KEYS_SLICE) do |keys_slice|
70
+ @adapter.add_to_set(segment_data(name), keys_slice)
71
+ end
72
+ end
73
+
74
+ def remove_keys(name, keys)
75
+ keys.each_slice(KEYS_SLICE) do |keys_slice|
76
+ @adapter.delete_from_set(segment_data(name), keys_slice)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,106 @@
1
+ require 'concurrent'
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Repositories
6
+ class SplitsRepository < Repository
7
+ SPLITS_SLICE = 10
8
+
9
+ attr_reader :adapter
10
+
11
+ def initialize(adapter, config)
12
+ @adapter = adapter
13
+ @config = config
14
+
15
+ @adapter.set_string(namespace_key('.splits.till'), '-1')
16
+ @adapter.initialize_map(namespace_key('.segments.registered'))
17
+ end
18
+
19
+ def add_split(split)
20
+ return unless split[:name]
21
+
22
+ @adapter.set_string(namespace_key(".split.#{split[:name]}"), split.to_json)
23
+ end
24
+
25
+ def remove_split(name)
26
+ @adapter.delete(namespace_key(".split.#{name}"))
27
+ end
28
+
29
+ def get_splits(names, slice = SPLITS_SLICE)
30
+ splits = {}
31
+
32
+ names.each_slice(slice) do |splits_slice|
33
+ splits.merge!(
34
+ @adapter
35
+ .multiple_strings(splits_slice.map { |name| namespace_key(".split.#{name}") })
36
+ .map { |name, data| [name.gsub(namespace_key('.split.'), ''), data] }.to_h
37
+ )
38
+ end
39
+
40
+ splits.map do |name, data|
41
+ parsed_data = data ? JSON.parse(data, symbolize_names: true) : nil
42
+ [name.to_sym, parsed_data]
43
+ end.to_h
44
+ end
45
+
46
+ def get_split(name)
47
+ split = @adapter.string(namespace_key(".split.#{name}"))
48
+
49
+ JSON.parse(split, symbolize_names: true) if split
50
+ end
51
+
52
+ def splits
53
+ splits_hash = {}
54
+
55
+ split_names.each do |name|
56
+ splits_hash[name] = get_split(name)
57
+ end
58
+
59
+ splits_hash
60
+ end
61
+
62
+ # Return an array of Split Names excluding control keys like splits.till
63
+ def split_names
64
+ @adapter.find_strings_by_prefix(namespace_key('.split.'))
65
+ .map { |split| split.gsub(namespace_key('.split.'), '') }
66
+ end
67
+
68
+ def set_change_number(since)
69
+ @adapter.set_string(namespace_key('.splits.till'), since)
70
+ end
71
+
72
+ def get_change_number
73
+ @adapter.string(namespace_key('.splits.till'))
74
+ end
75
+
76
+ def set_segment_names(names)
77
+ return if names.nil? || names.empty?
78
+
79
+ names.each do |name|
80
+ @adapter.add_to_set(namespace_key('.segments.registered'), name)
81
+ end
82
+ end
83
+
84
+ def exists?(name)
85
+ @adapter.exists?(namespace_key(".split.#{name}"))
86
+ end
87
+
88
+ def ready?
89
+ @adapter.string(namespace_key('.splits.ready')).to_i != -1
90
+ end
91
+
92
+ def not_ready!
93
+ @adapter.set_string(namespace_key('.splits.ready'), -1)
94
+ end
95
+
96
+ def ready!
97
+ @adapter.set_string(namespace_key('.splits.ready'), Time.now.utc.to_i)
98
+ end
99
+
100
+ def clear
101
+ @adapter.clear(namespace_key)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,52 @@
1
+ module SplitIoClient
2
+ class ImpressionRouter
3
+ attr_reader :router_thread
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ @listener = config.impression_listener
8
+ @queue = Queue.new
9
+ router_thread
10
+
11
+ if defined?(PhusionPassenger)
12
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
13
+ router_thread if forked
14
+ end
15
+ end
16
+ end
17
+
18
+ def add(impression)
19
+ @queue.push(impression)
20
+ end
21
+
22
+ def add_bulk(impressions)
23
+ impressions[:split_names].each do |split_name|
24
+ @queue.push(
25
+ split_name: split_name.to_s,
26
+ matching_key: impressions[:matching_key],
27
+ bucketing_key: impressions[:bucketing_key],
28
+ treatment: {
29
+ label: impressions[:treatments_labels_change_numbers][split_name.to_sym][:label],
30
+ treatment: impressions[:treatments_labels_change_numbers][split_name.to_sym][:treatment],
31
+ change_number: impressions[:treatments_labels_change_numbers][split_name.to_sym][:change_number]
32
+ },
33
+ attributes: impressions[:attributes]
34
+ )
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def router_thread
41
+ @config.threads[:impression_router] = Thread.new do
42
+ loop do
43
+ begin
44
+ @listener.log(@queue.pop)
45
+ rescue StandardError => error
46
+ @config.log_found_exception(__method__.to_s, error)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Senders
4
+ class EventsSender
5
+ def initialize(events_repository, config, api_key)
6
+ @events_repository = events_repository
7
+ @config = config
8
+ @api_key = api_key
9
+ end
10
+
11
+ def call
12
+ if ENV['SPLITCLIENT_ENV'] == 'test'
13
+ post_events
14
+ else
15
+ events_thread
16
+
17
+ if defined?(PhusionPassenger)
18
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
19
+ events_thread if forked
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def events_thread
28
+ @config.threads[:events_sender] = Thread.new do
29
+ @config.logger.info('Starting events service')
30
+
31
+ loop do
32
+ post_events
33
+
34
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.events_push_rate))
35
+ end
36
+ end
37
+ end
38
+
39
+ def post_events
40
+ SplitIoClient::Api::Events.new(@api_key, @config, @events_repository.clear).post
41
+ rescue StandardError => error
42
+ @config.log_found_exception(__method__.to_s, error)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Senders
4
+ class ImpressionsFormatter
5
+ def initialize(impressions_repository)
6
+ @impressions_repository = impressions_repository
7
+ end
8
+
9
+ def call(raw_impressions)
10
+ impressions = raw_impressions ? raw_impressions : @impressions_repository.clear
11
+ formatted_impressions = []
12
+ filtered_impressions = filter_impressions(impressions)
13
+
14
+ return [] if impressions.empty? || filtered_impressions.empty?
15
+
16
+ formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
17
+ ip = nil
18
+ current_impressions =
19
+ filtered_impressions
20
+ .select { |impression| impression[:feature] == feature }
21
+ .map do |impression|
22
+ ip = impression[:ip]
23
+ {
24
+ keyName: impression[:impressions]['keyName'] || impression[:impressions]['key_name'],
25
+ treatment: impression[:impressions]['treatment'],
26
+ time: impression[:impressions]['time'],
27
+ bucketingKey: impression[:impressions]['bucketingKey'] || impression[:impressions]['bucketing_key'],
28
+ label: impression[:impressions]['label'],
29
+ changeNumber: impression[:impressions]['changeNumber'] || impression[:impressions]['change_number'],
30
+ }
31
+ end
32
+
33
+ memo << {
34
+ testName: feature.to_sym,
35
+ keyImpressions: current_impressions,
36
+ ip: ip
37
+ }
38
+ end
39
+
40
+ formatted_impressions
41
+ end
42
+
43
+ private
44
+
45
+ def unique_features(impressions)
46
+ impressions.map { |impression| impression[:feature] }.uniq
47
+ end
48
+
49
+ # Filter seen impressions by impression_hash
50
+ def filter_impressions(unfiltered_impressions)
51
+ impressions_seen = []
52
+
53
+ unfiltered_impressions.each_with_object([]) do |impression, impressions|
54
+ impression_hash = impression_hash(impression)
55
+
56
+ next if impressions_seen.include?(impression_hash)
57
+
58
+ impressions_seen << impression_hash
59
+ impressions << impression
60
+ end
61
+ end
62
+
63
+ def impression_hash(impression)
64
+ "#{impression[:feature]}:" \
65
+ "#{impression[:impressions]['keyName']}:" \
66
+ "#{impression[:impressions]['bucketingKey']}:" \
67
+ "#{impression[:impressions]['changeNumber']}:" \
68
+ "#{impression[:impressions]['treatment']}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,67 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Senders
4
+ class ImpressionsSender
5
+ def initialize(impressions_repository, config, api_key)
6
+ @impressions_repository = impressions_repository
7
+ @config = config
8
+ @api_key = api_key
9
+ end
10
+
11
+ def call
12
+ # Disable impressions if @config.impressions_queue_size == -1
13
+ if @config.impressions_queue_size < 0
14
+ @config.logger.info('Disabling impressions service by config')
15
+ return
16
+ end
17
+
18
+ if ENV['SPLITCLIENT_ENV'] == 'test'
19
+ post_impressions
20
+ else
21
+ impressions_thread
22
+
23
+ if defined?(PhusionPassenger)
24
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
25
+ impressions_thread if forked
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def impressions_thread
34
+ @config.threads[:impressions_sender] = Thread.new do
35
+ begin
36
+ @config.logger.info('Starting impressions service')
37
+
38
+ loop do
39
+ post_impressions
40
+
41
+ sleep(SplitIoClient::Utilities.randomize_interval(@config.impressions_refresh_rate))
42
+ end
43
+ rescue SplitIoClient::ImpressionShutdownException
44
+ post_impressions
45
+
46
+ @impressions_repository.clear
47
+ end
48
+ end
49
+ end
50
+
51
+ def post_impressions
52
+ impressions_client.post
53
+ rescue StandardError => error
54
+ @config.log_found_exception(__method__.to_s, error)
55
+ end
56
+
57
+ def formatted_impressions(raw_impressions = nil)
58
+ ImpressionsFormatter.new(@impressions_repository).call(raw_impressions)
59
+ end
60
+
61
+ def impressions_client
62
+ SplitIoClient::Api::Impressions.new(@api_key, @config, formatted_impressions)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end