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