splitclient-rb 6.3.0 → 8.11.0
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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/pull_request_template.md +9 -0
- data/.github/workflows/ci.yml +90 -0
- data/.github/workflows/update-license-year.yml +45 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +46 -3
- data/CHANGES.txt +158 -11
- data/CONTRIBUTORS-GUIDE.md +49 -0
- data/LICENSE +169 -13
- data/NOTICE.txt +5 -0
- data/README.md +67 -27
- data/Rakefile +1 -8
- data/ext/murmurhash/3_x64_128.c +117 -0
- data/ext/murmurhash/murmurhash.c +5 -1
- data/lib/murmurhash/murmurhash.jar +0 -0
- data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +3 -3
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +7 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +12 -4
- data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
- data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
- data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
- data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
- data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
- data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
- data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
- data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +26 -14
- data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +9 -14
- data/lib/splitclient-rb/cache/repositories/events_repository.rb +31 -10
- data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
- data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +22 -23
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +15 -22
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +6 -31
- data/lib/splitclient-rb/cache/repositories/repository.rb +6 -5
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +46 -6
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +232 -43
- data/lib/splitclient-rb/cache/routers/impression_router.rb +24 -22
- data/lib/splitclient-rb/cache/senders/events_sender.rb +12 -29
- data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
- data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
- data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +27 -13
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +11 -25
- data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
- data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
- data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
- data/lib/splitclient-rb/clients/split_client.rb +385 -138
- data/lib/splitclient-rb/constants.rb +16 -0
- data/lib/splitclient-rb/engine/api/client.rb +38 -43
- data/lib/splitclient-rb/engine/api/events.rb +19 -11
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
- data/lib/splitclient-rb/engine/api/impressions.rb +49 -14
- data/lib/splitclient-rb/engine/api/segments.rb +31 -24
- data/lib/splitclient-rb/engine/api/splits.rb +108 -33
- data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
- data/lib/splitclient-rb/engine/auth_api_client.rb +96 -0
- data/lib/splitclient-rb/engine/back_off.rb +26 -0
- data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
- data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
- data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
- data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
- data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
- data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
- data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
- data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
- data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
- data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
- data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +1 -1
- data/lib/splitclient-rb/engine/matchers/between_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +10 -8
- data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +2 -6
- data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +1 -5
- data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +6 -5
- data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +5 -4
- data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +6 -4
- data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +1 -5
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +6 -4
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +6 -4
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/matcher.rb +22 -0
- data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +2 -6
- data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
- data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
- data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
- data/lib/splitclient-rb/engine/matchers/set_matcher.rb +2 -1
- data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +4 -3
- data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +3 -65
- data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
- data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
- data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
- data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
- data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
- data/lib/splitclient-rb/engine/models/label.rb +3 -0
- data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +81 -20
- data/lib/splitclient-rb/engine/parser/evaluator.rb +40 -51
- data/lib/splitclient-rb/engine/push_manager.rb +66 -0
- data/lib/splitclient-rb/engine/status_manager.rb +39 -0
- data/lib/splitclient-rb/engine/sync_manager.rb +180 -0
- data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
- data/lib/splitclient-rb/exceptions.rb +20 -1
- data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
- data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
- data/lib/splitclient-rb/helpers/util.rb +26 -0
- data/lib/splitclient-rb/managers/split_manager.rb +58 -20
- data/lib/splitclient-rb/spec.rb +9 -0
- data/lib/splitclient-rb/split_config.rb +336 -54
- data/lib/splitclient-rb/split_factory.rb +219 -33
- data/lib/splitclient-rb/split_factory_builder.rb +1 -22
- data/lib/splitclient-rb/split_factory_registry.rb +63 -0
- data/lib/splitclient-rb/split_logger.rb +9 -10
- data/lib/splitclient-rb/sse/event_source/client.rb +263 -0
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
- data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
- data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
- data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
- data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +149 -0
- data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
- data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
- data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
- data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
- data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
- data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
- data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
- data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
- data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
- data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
- data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
- data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
- data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
- data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
- data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
- data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
- data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
- data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
- data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
- data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
- data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
- data/lib/splitclient-rb/utilitites.rb +8 -0
- data/lib/splitclient-rb/validators.rb +142 -38
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +101 -16
- data/sonar-project.properties +6 -0
- data/splitclient-rb.gemspec +28 -23
- metadata +262 -82
- data/.travis.yml +0 -11
- data/Appraisals +0 -10
- data/Detailed-README.md +0 -588
- data/NEWS +0 -141
- data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +0 -127
- data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +0 -96
- data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +0 -21
- data/lib/splitclient-rb/cache/senders/metrics_sender.rb +0 -56
- data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +0 -46
- data/lib/splitclient-rb/cache/stores/segment_store.rb +0 -81
- data/lib/splitclient-rb/cache/stores/split_store.rb +0 -102
- data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -183
- data/lib/splitclient-rb/engine/api/faraday_adapter/patched_net_http_persistent.rb +0 -46
- data/lib/splitclient-rb/engine/api/metrics.rb +0 -60
- data/lib/splitclient-rb/engine/metrics/metrics.rb +0 -80
- data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -81
- data/lib/splitclient-rb/localhost_split_factory.rb +0 -13
- data/lib/splitclient-rb/localhost_utils.rb +0 -59
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Cache
|
|
3
|
+
module Fetchers
|
|
4
|
+
class SplitFetcher
|
|
5
|
+
attr_reader :splits_repository, :rule_based_segments_repository
|
|
6
|
+
|
|
7
|
+
def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer)
|
|
8
|
+
@splits_repository = splits_repository
|
|
9
|
+
@rule_based_segments_repository = rule_based_segments_repository
|
|
10
|
+
@api_key = api_key
|
|
11
|
+
@config = config
|
|
12
|
+
@semaphore = Mutex.new
|
|
13
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
18
|
+
fetch_splits
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
splits_thread
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
|
|
26
|
+
@semaphore.synchronize do
|
|
27
|
+
data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options)
|
|
28
|
+
SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage)
|
|
29
|
+
SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config)
|
|
30
|
+
@splits_repository.set_segment_names(data[:segment_names])
|
|
31
|
+
@rule_based_segments_repository.set_segment_names(data[:segment_names])
|
|
32
|
+
@config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
|
|
33
|
+
|
|
34
|
+
{ segment_names: data[:segment_names], success: true }
|
|
35
|
+
end
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
38
|
+
{ segment_names: [], success: false }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stop_splits_thread
|
|
42
|
+
SplitIoClient::Helpers::ThreadHelper.stop(:split_fetcher, @config)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def splits_thread
|
|
48
|
+
@config.threads[:split_fetcher] = Thread.new do
|
|
49
|
+
@config.logger.info('Starting feature flags fetcher service') if @config.debug_enabled
|
|
50
|
+
loop do
|
|
51
|
+
fetch_splits
|
|
52
|
+
|
|
53
|
+
sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.features_refresh_rate)
|
|
54
|
+
@config.logger.debug("Feature flags fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
|
|
55
|
+
sleep(sleep_for)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil })
|
|
61
|
+
splits_api.since(since, since_rbs, fetch_options)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def splits_api
|
|
65
|
+
@splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @config, @telemetry_runtime_producer)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bitarray'
|
|
4
|
+
|
|
5
|
+
module SplitIoClient
|
|
6
|
+
module Cache
|
|
7
|
+
module Filter
|
|
8
|
+
class BloomFilter
|
|
9
|
+
def initialize(capacity, false_positive_probability = 0.001)
|
|
10
|
+
@capacity = capacity.round
|
|
11
|
+
@m = best_m(capacity, false_positive_probability)
|
|
12
|
+
reset_filter
|
|
13
|
+
@k = best_k(capacity)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add(string)
|
|
17
|
+
return false if contains?(string)
|
|
18
|
+
|
|
19
|
+
positions = hashes(string)
|
|
20
|
+
positions.each { |position| @ba[position] = 1 }
|
|
21
|
+
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def contains?(string)
|
|
26
|
+
!hashes(string).any? { |ea| @ba[ea] == 0 }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clear
|
|
30
|
+
@ba = nil
|
|
31
|
+
reset_filter
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def reset_filter
|
|
37
|
+
@ba = BitArray.new(@m.round)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# m is the required number of bits in the array
|
|
41
|
+
def best_m(capacity, false_positive_probability)
|
|
42
|
+
-(capacity * Math.log(false_positive_probability)) / (Math.log(2) ** 2)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# k is the number of hash functions that minimizes the probability of false positives
|
|
46
|
+
def best_k(capacity)
|
|
47
|
+
(Math.log(2) * (@ba.size / capacity)).round
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def hashes(data)
|
|
51
|
+
m = @ba.size
|
|
52
|
+
h = Digest::MD5.hexdigest(data.to_s).to_i(16)
|
|
53
|
+
x = h % m
|
|
54
|
+
h /= m
|
|
55
|
+
y = h % m
|
|
56
|
+
h /= m
|
|
57
|
+
z = h % m
|
|
58
|
+
[x] + 1.upto(@k - 1).collect do |i|
|
|
59
|
+
x = (x + y) % m
|
|
60
|
+
y = (y + z) % m
|
|
61
|
+
x
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Filter
|
|
6
|
+
class FilterAdapter
|
|
7
|
+
def initialize(config, filter)
|
|
8
|
+
@config = config
|
|
9
|
+
@filter = filter
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add(feature_name, key)
|
|
13
|
+
@filter.add("#{feature_name}#{key}")
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def contains?(feature_name, key)
|
|
19
|
+
@filter.contains?("#{feature_name}#{key}")
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear
|
|
25
|
+
@filter.clear
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module SplitIoClient
|
|
6
|
+
module Cache
|
|
7
|
+
module Filter
|
|
8
|
+
class FlagSetsFilter
|
|
9
|
+
def initialize(flag_sets = [])
|
|
10
|
+
@flag_sets = Set.new(flag_sets)
|
|
11
|
+
@should_filter = @flag_sets.any?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def should_filter?
|
|
15
|
+
@should_filter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def flag_set_exist?(flag_set)
|
|
19
|
+
return true unless @should_filter
|
|
20
|
+
|
|
21
|
+
if not flag_set.is_a?(String) or flag_set.empty?
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@flag_sets.intersection([flag_set]).any?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def intersect?(flag_sets)
|
|
29
|
+
return true unless @should_filter
|
|
30
|
+
|
|
31
|
+
if not flag_sets.is_a?(Array) or flag_sets.empty?
|
|
32
|
+
return false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@flag_sets.intersection(Set.new(flag_sets)).any?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Hashers
|
|
3
|
+
class ImpressionHasher
|
|
4
|
+
def initialize
|
|
5
|
+
@murmur_hash_128_64 = case RUBY_PLATFORM
|
|
6
|
+
when 'java'
|
|
7
|
+
Proc.new { |key, seed| Java::MurmurHash3.hash128x64(key, seed) }
|
|
8
|
+
else
|
|
9
|
+
Proc.new { |key, seed| Digest::MurmurHashMRI3_x64_128.rawdigest(key, [seed].pack('L')) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process(impression)
|
|
14
|
+
impression_data = "#{unknown_if_null(impression[:k])}"
|
|
15
|
+
impression_data << ":#{unknown_if_null(impression[:f])}"
|
|
16
|
+
impression_data << ":#{unknown_if_null(impression[:t])}"
|
|
17
|
+
impression_data << ":#{unknown_if_null(impression[:r])}"
|
|
18
|
+
impression_data << ":#{zero_if_null(impression[:c])}"
|
|
19
|
+
|
|
20
|
+
@murmur_hash_128_64.call(impression_data, 0)[0];
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def unknown_if_null(value)
|
|
26
|
+
value == nil ? "UNKNOWN" : value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def zero_if_null(value)
|
|
30
|
+
value == nil ? 0 : value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Observers
|
|
3
|
+
class ImpressionObserver
|
|
4
|
+
LAST_SEEN_CACHE_SIZE = 500000
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@cache = LruRedux::TTL::ThreadSafeCache.new(LAST_SEEN_CACHE_SIZE)
|
|
8
|
+
@impression_hasher = Hashers::ImpressionHasher.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_and_set(impression)
|
|
12
|
+
return if impression.nil?
|
|
13
|
+
|
|
14
|
+
hash = @impression_hasher.process(impression)
|
|
15
|
+
previous = @cache[hash]
|
|
16
|
+
@cache[hash] = impression[:m]
|
|
17
|
+
|
|
18
|
+
previous.nil? ? nil : [previous, impression[:m]].min
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -3,29 +3,41 @@ module SplitIoClient
|
|
|
3
3
|
module Repositories
|
|
4
4
|
module Events
|
|
5
5
|
class MemoryRepository < EventsRepository
|
|
6
|
-
|
|
6
|
+
EVENTS_MAX_SIZE_BYTES = 5242880
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
8
|
+
def initialize(config, telemetry_runtime_producer)
|
|
9
|
+
@config = config
|
|
10
|
+
@adapter = @config.events_adapter
|
|
11
|
+
@size = 0
|
|
12
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
10
13
|
end
|
|
11
14
|
|
|
12
|
-
def add(key, traffic_type, event_type, time, value)
|
|
13
|
-
@adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value))
|
|
14
|
-
|
|
15
|
-
if SplitIoClient.configuration.debug_enabled
|
|
16
|
-
SplitIoClient.configuration.logger.warn("Dropping events. Current size is #{SplitIoClient.configuration.events_queue_size}. " \
|
|
17
|
-
"Consider increasing events_queue_size")
|
|
18
|
-
end
|
|
19
|
-
@adapter.clear
|
|
20
|
-
end
|
|
15
|
+
def add(key, traffic_type, event_type, time, value, properties, event_size)
|
|
16
|
+
@adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value, properties))
|
|
17
|
+
@size += event_size
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
post_events if @size >= EVENTS_MAX_SIZE_BYTES || @adapter.length == @config.events_queue_size
|
|
20
|
+
|
|
21
|
+
@telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_QUEUED, 1)
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
24
|
+
@telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_DROPPED, 1)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def clear
|
|
28
|
+
@size = 0
|
|
27
29
|
@adapter.clear
|
|
28
30
|
end
|
|
31
|
+
|
|
32
|
+
def empty?
|
|
33
|
+
@adapter.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def batch
|
|
37
|
+
return [] if @config.events_queue_size.zero?
|
|
38
|
+
|
|
39
|
+
@adapter.get_batch(@config.events_queue_size)
|
|
40
|
+
end
|
|
29
41
|
end
|
|
30
42
|
end
|
|
31
43
|
end
|
|
@@ -3,36 +3,31 @@ module SplitIoClient
|
|
|
3
3
|
module Repositories
|
|
4
4
|
module Events
|
|
5
5
|
class RedisRepository < EventsRepository
|
|
6
|
-
EVENTS_SLICE = 100
|
|
7
6
|
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
7
|
+
def initialize(config)
|
|
8
|
+
@config = config
|
|
9
|
+
@adapter = @config.events_adapter
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def add(key, traffic_type, event_type, time, value)
|
|
12
|
+
def add(key, traffic_type, event_type, time, value, properties, size)
|
|
13
13
|
@adapter.add_to_queue(
|
|
14
14
|
namespace_key('.events'),
|
|
15
|
-
{ m: metadata, e: event(key, traffic_type, event_type, time, value) }.to_json
|
|
15
|
+
{ m: metadata, e: event(key, traffic_type, event_type, time, value, properties) }.to_json
|
|
16
16
|
)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
@adapter.get_from_queue(namespace_key('.events'),
|
|
19
|
+
def clear
|
|
20
|
+
@adapter.get_from_queue(namespace_key('.events'), 0).map do |e|
|
|
21
21
|
JSON.parse(e, symbolize_names: true)
|
|
22
22
|
end
|
|
23
23
|
rescue StandardError => e
|
|
24
|
-
|
|
24
|
+
@config.logger.error("Exception while clearing events cache: #{e}")
|
|
25
25
|
[]
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def batch
|
|
29
|
-
|
|
29
|
+
clear()
|
|
30
30
|
end
|
|
31
|
-
|
|
32
|
-
def clear
|
|
33
|
-
get_events
|
|
34
|
-
end
|
|
35
|
-
|
|
36
31
|
end
|
|
37
32
|
end
|
|
38
33
|
end
|
|
@@ -4,36 +4,57 @@ module SplitIoClient
|
|
|
4
4
|
# Repository which forwards events interface to the selected adapter
|
|
5
5
|
class EventsRepository < Repository
|
|
6
6
|
extend Forwardable
|
|
7
|
-
def_delegators :@
|
|
7
|
+
def_delegators :@repository, :add, :clear, :batch
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
10
|
-
|
|
9
|
+
def initialize(config, api_key, telemetry_runtime_producer)
|
|
10
|
+
super(config)
|
|
11
|
+
@repository = case @config.events_adapter.class.to_s
|
|
11
12
|
when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
|
|
12
|
-
Repositories::Events::MemoryRepository.new(
|
|
13
|
+
Repositories::Events::MemoryRepository.new(@config, telemetry_runtime_producer)
|
|
13
14
|
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
|
14
|
-
Repositories::Events::RedisRepository.new(
|
|
15
|
+
Repositories::Events::RedisRepository.new(@config)
|
|
15
16
|
end
|
|
17
|
+
|
|
18
|
+
@api_key = api_key
|
|
19
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def post_events
|
|
23
|
+
events_api.post(self.clear)
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
16
26
|
end
|
|
17
27
|
|
|
28
|
+
def empty?
|
|
29
|
+
@repository.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
18
32
|
protected
|
|
19
33
|
|
|
20
34
|
def metadata
|
|
21
35
|
{
|
|
22
|
-
s: "#{
|
|
23
|
-
i:
|
|
24
|
-
n:
|
|
36
|
+
s: "#{@config.language}-#{@config.version}",
|
|
37
|
+
i: @config.machine_ip,
|
|
38
|
+
n: @config.machine_name
|
|
25
39
|
}
|
|
26
40
|
end
|
|
27
41
|
|
|
28
|
-
def event(key, traffic_type, event_type, time, value)
|
|
42
|
+
def event(key, traffic_type, event_type, time, value, properties)
|
|
29
43
|
{
|
|
30
44
|
key: key,
|
|
31
45
|
trafficTypeName: traffic_type,
|
|
32
46
|
eventTypeId: event_type,
|
|
33
47
|
value: value,
|
|
34
|
-
timestamp: time
|
|
48
|
+
timestamp: time,
|
|
49
|
+
properties: properties
|
|
35
50
|
}.reject { |_, v| v.nil? }
|
|
36
51
|
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def events_api
|
|
56
|
+
@events_api ||= SplitIoClient::Api::Events.new(@api_key, @config, @telemetry_runtime_producer)
|
|
57
|
+
end
|
|
37
58
|
end
|
|
38
59
|
end
|
|
39
60
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'concurrent'
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Repositories
|
|
6
|
+
class MemoryFlagSetsRepository
|
|
7
|
+
def initialize(flag_sets = [])
|
|
8
|
+
@sets_feature_flag_map = {}
|
|
9
|
+
flag_sets.each{ |flag_set| @sets_feature_flag_map[flag_set] = Set[] }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def flag_set_exist?(flag_set)
|
|
13
|
+
@sets_feature_flag_map.key?(flag_set)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_flag_sets(flag_sets)
|
|
17
|
+
to_return = Array.new
|
|
18
|
+
flag_sets.each { |flag_set| to_return.concat(@sets_feature_flag_map[flag_set].to_a)}
|
|
19
|
+
to_return.uniq
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_flag_set(flag_set)
|
|
23
|
+
@sets_feature_flag_map[flag_set] = Set[] if !flag_set_exist?(flag_set)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove_flag_set(flag_set)
|
|
27
|
+
@sets_feature_flag_map.delete(flag_set) if flag_set_exist?(flag_set)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_feature_flag_to_flag_set(flag_set, feature_flag)
|
|
31
|
+
@sets_feature_flag_map[flag_set].add(feature_flag) if flag_set_exist?(flag_set)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def remove_feature_flag_from_flag_set(flag_set, feature_flag)
|
|
35
|
+
@sets_feature_flag_map[flag_set].delete(feature_flag) if flag_set_exist?(flag_set)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'concurrent'
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Repositories
|
|
6
|
+
class RedisFlagSetsRepository < Repository
|
|
7
|
+
|
|
8
|
+
def initialize(config)
|
|
9
|
+
super(config)
|
|
10
|
+
@adapter = SplitIoClient::Cache::Adapters::RedisAdapter.new(@config.redis_url)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def flag_set_exist?(flag_set)
|
|
14
|
+
@adapter.exists?(namespace_key(".flagSet.#{flag_set}"))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_flag_sets(flag_sets)
|
|
18
|
+
result = @adapter.redis.pipelined do |pipeline|
|
|
19
|
+
flag_sets.each do |flag_set|
|
|
20
|
+
pipeline.smembers(namespace_key(".flagSet.#{flag_set}"))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
to_return = Array.new
|
|
24
|
+
result.each do |flag_set|
|
|
25
|
+
flag_set.each { |feature_flag_name| to_return.push(feature_flag_name.to_s)}
|
|
26
|
+
end
|
|
27
|
+
to_return.uniq
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_flag_set(flag_set)
|
|
31
|
+
# not implemented
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def remove_flag_set(flag_set)
|
|
35
|
+
# not implemented
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_feature_flag_to_flag_set(flag_set, feature_flag)
|
|
39
|
+
# not implemented
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def remove_feature_flag_from_flag_set(flag_set, feature_flag)
|
|
43
|
+
# not implemented
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -5,46 +5,45 @@ module SplitIoClient
|
|
|
5
5
|
module Repositories
|
|
6
6
|
module Impressions
|
|
7
7
|
class MemoryRepository < ImpressionsRepository
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
8
|
+
def initialize(config)
|
|
9
|
+
@config = config
|
|
10
|
+
@adapter = @config.impressions_adapter
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
)
|
|
13
|
+
def add_bulk(impressions)
|
|
14
|
+
return 0 if impressions.nil?
|
|
15
|
+
|
|
16
|
+
count = 0
|
|
17
|
+
impressions.each do |impression|
|
|
18
|
+
@adapter.add_to_queue(impression)
|
|
19
|
+
count += 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
0
|
|
24
23
|
rescue ThreadError # queue is full
|
|
25
24
|
if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
|
|
26
|
-
|
|
27
|
-
#{
|
|
25
|
+
@config.logger.warn("Dropping impressions. Current size is \
|
|
26
|
+
#{@config.impressions_queue_size}. " \
|
|
28
27
|
'Consider increasing impressions_queue_size')
|
|
29
28
|
end
|
|
30
|
-
end
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
treatments.each do |split_name, treatment|
|
|
34
|
-
add(key, bucketing_key, split_name, treatment, time)
|
|
35
|
-
end
|
|
30
|
+
impressions.length - count
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def batch
|
|
39
|
-
return [] if
|
|
34
|
+
return [] if @config.impressions_bulk_size.zero?
|
|
40
35
|
|
|
41
|
-
@adapter.get_batch(
|
|
36
|
+
@adapter.get_batch(@config.impressions_bulk_size)
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
def clear
|
|
45
40
|
@adapter.clear
|
|
46
41
|
end
|
|
47
42
|
|
|
43
|
+
def empty?
|
|
44
|
+
@adapter.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
48
47
|
private
|
|
49
48
|
|
|
50
49
|
def random_sampler
|
|
@@ -7,32 +7,25 @@ module SplitIoClient
|
|
|
7
7
|
class RedisRepository < ImpressionsRepository
|
|
8
8
|
EXPIRE_SECONDS = 3600
|
|
9
9
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@adapter = @config.impressions_adapter
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def add_bulk(matching_key, bucketing_key, treatments, time)
|
|
19
|
-
impressions = treatments.map do |split_name, treatment|
|
|
20
|
-
{
|
|
21
|
-
m: metadata,
|
|
22
|
-
i: impression_data(
|
|
23
|
-
matching_key,
|
|
24
|
-
bucketing_key,
|
|
25
|
-
split_name,
|
|
26
|
-
treatment,
|
|
27
|
-
time
|
|
28
|
-
)
|
|
29
|
-
}.to_json
|
|
15
|
+
def add_bulk(impressions)
|
|
16
|
+
impressions_json = impressions.map do |impression|
|
|
17
|
+
impression[:i][:properties] = impression[:i][:properties].to_json.to_s unless impression[:i][:properties].nil?
|
|
18
|
+
impression.to_json
|
|
30
19
|
end
|
|
31
20
|
|
|
32
|
-
impressions_list_size = @adapter.add_to_queue(key,
|
|
21
|
+
impressions_list_size = @adapter.add_to_queue(key, impressions_json)
|
|
33
22
|
|
|
34
23
|
# Synchronizer might not be running
|
|
35
|
-
@adapter.expire(key, EXPIRE_SECONDS) if
|
|
24
|
+
@adapter.expire(key, EXPIRE_SECONDS) if impressions_json.size == impressions_list_size
|
|
25
|
+
0
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
@config.logger.error("Exception while add_bulk: #{e}")
|
|
28
|
+
0
|
|
36
29
|
end
|
|
37
30
|
|
|
38
31
|
def get_impressions(number_of_impressions = 0)
|
|
@@ -42,12 +35,12 @@ module SplitIoClient
|
|
|
42
35
|
impression
|
|
43
36
|
end
|
|
44
37
|
rescue StandardError => e
|
|
45
|
-
|
|
38
|
+
@config.logger.error("Exception while clearing impressions cache: #{e}")
|
|
46
39
|
[]
|
|
47
40
|
end
|
|
48
41
|
|
|
49
42
|
def batch
|
|
50
|
-
get_impressions(
|
|
43
|
+
get_impressions(@config.impressions_bulk_size)
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
def clear
|