splitclient-rb 1.0.2.wip → 8.11.1.pre.rc1-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.
- checksums.yaml +5 -5
- 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 +20 -0
- data/.rubocop.yml +76 -0
- data/.simplecov +1 -0
- data/CHANGES.txt +361 -2
- data/CONTRIBUTORS-GUIDE.md +49 -0
- data/Gemfile +2 -0
- data/LICENSE +3 -36
- data/NOTICE.txt +5 -0
- data/README.md +59 -127
- data/Rakefile +27 -3
- data/ext/murmurhash/MurmurHash3.java +302 -0
- data/gemfiles/faraday_after_0.13.gemfile +7 -0
- data/gemfiles/faraday_before_0.13.gemfile +8 -0
- data/lib/murmurhash/base.rb +58 -0
- data/lib/murmurhash/murmurhash.jar +0 -0
- data/lib/murmurhash/murmurhash_mri.rb +3 -0
- data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
- 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 +45 -0
- data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
- data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
- 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 +56 -0
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
- data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
- data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
- data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
- 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 +95 -0
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
- 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 +530 -0
- data/lib/splitclient-rb/constants.rb +16 -0
- data/lib/splitclient-rb/engine/api/client.rb +87 -0
- data/lib/splitclient-rb/engine/api/events.rb +57 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
- data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
- data/lib/splitclient-rb/engine/api/segments.rb +78 -0
- data/lib/splitclient-rb/engine/api/splits.rb +139 -0
- data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
- data/lib/splitclient-rb/engine/auth_api_client.rb +100 -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-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
- 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-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
- data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
- data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
- data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
- data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
- data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
- data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
- 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 +43 -0
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
- data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
- data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
- data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
- 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 +27 -0
- data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
- data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
- data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
- 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 +10 -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 +16 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split.rb +17 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
- 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 +183 -0
- data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
- data/lib/splitclient-rb/exceptions.rb +26 -0
- 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 +121 -0
- data/lib/splitclient-rb/spec.rb +9 -0
- data/lib/splitclient-rb/split_config.rb +591 -47
- data/lib/splitclient-rb/split_factory.rb +284 -0
- data/lib/splitclient-rb/split_factory_builder.rb +7 -0
- data/lib/splitclient-rb/split_factory_registry.rb +63 -0
- data/lib/splitclient-rb/split_logger.rb +23 -0
- data/lib/splitclient-rb/sse/event_source/client.rb +264 -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 +153 -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 +49 -0
- data/lib/splitclient-rb/validators.rb +361 -0
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +177 -25
- data/sonar-project.properties +6 -0
- data/splitclient-rb.gemspec +53 -26
- data/tasks/benchmark_get_treatment.rake +31 -17
- data/tasks/irb.rake +6 -0
- metadata +411 -86
- data/NEWS +0 -9
- data/lib/splitclient-cache/local_store.rb +0 -45
- data/lib/splitclient-engine/impressions/impressions.rb +0 -79
- data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
- data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
- data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
- data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
- data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
- data/lib/splitclient-engine/metrics/metrics.rb +0 -158
- data/lib/splitclient-engine/parser/condition.rb +0 -143
- data/lib/splitclient-engine/parser/segment.rb +0 -84
- data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
- data/lib/splitclient-engine/parser/split.rb +0 -68
- data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
- data/lib/splitclient-engine/parser/split_parser.rb +0 -139
- data/lib/splitclient-engine/partitions/treatments.rb +0 -40
- data/lib/splitclient-rb/split_client.rb +0 -156
- data/lib/splitclient-rb_utilitites.rb +0 -26
- data/tasks/console.rake +0 -4
- data/tasks/rspec.rake +0 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Cache
|
|
3
|
+
module Adapters
|
|
4
|
+
module MemoryAdapters
|
|
5
|
+
# Memory adapter implementation, which stores everything inside queue
|
|
6
|
+
class QueueAdapter
|
|
7
|
+
def initialize(max_size)
|
|
8
|
+
@max_size = max_size
|
|
9
|
+
@queue = Queue.new
|
|
10
|
+
@current_size = Concurrent::AtomicFixnum.new(0)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def clear(_ = nil)
|
|
14
|
+
@queue = Queue.new
|
|
15
|
+
@current_size.value = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Adds data to queue in non-blocking mode
|
|
19
|
+
def add_to_queue(data)
|
|
20
|
+
fail ThreadError if @current_size.value >= @max_size
|
|
21
|
+
|
|
22
|
+
@queue.push(data)
|
|
23
|
+
|
|
24
|
+
@current_size.increment
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
get_batch(@current_size.value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_batch(size)
|
|
32
|
+
items = []
|
|
33
|
+
size.times do
|
|
34
|
+
items << @queue.pop(true)
|
|
35
|
+
@current_size.decrement
|
|
36
|
+
end
|
|
37
|
+
items
|
|
38
|
+
rescue ThreadError
|
|
39
|
+
items
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def length
|
|
43
|
+
@current_size.value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty?
|
|
47
|
+
@queue.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module SplitIoClient
|
|
6
|
+
module Cache
|
|
7
|
+
module Adapters
|
|
8
|
+
# Redis adapter used to provide interface to Redis
|
|
9
|
+
class RedisAdapter
|
|
10
|
+
SCAN_SLICE = 5000
|
|
11
|
+
|
|
12
|
+
attr_reader :redis
|
|
13
|
+
|
|
14
|
+
def initialize(redis_url)
|
|
15
|
+
connection = redis_url.is_a?(Hash) ? redis_url : { url: redis_url }
|
|
16
|
+
|
|
17
|
+
@redis = Redis.new(connection)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def hincrby(key, field, increment)
|
|
21
|
+
@redis.hincrby(key, field, increment)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Map
|
|
25
|
+
def initialize_map(key)
|
|
26
|
+
# No need to initialize hash/map in Redis
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_to_map(key, field, value)
|
|
30
|
+
@redis.hset(key, field, value)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_in_map(key, field)
|
|
34
|
+
@redis.hget(key, field)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete_from_map(key, field)
|
|
38
|
+
@redis.hdel(key, field)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def in_map?(key, field)
|
|
42
|
+
@redis.hexists(key, field)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def map_keys(key)
|
|
46
|
+
@redis.hkeys(key)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def get_map(key)
|
|
50
|
+
@redis.hgetall(key)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# String
|
|
54
|
+
def string(key)
|
|
55
|
+
@redis.get(key)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_string(key, str)
|
|
59
|
+
@redis.set(key, str)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def find_strings_by_prefix(prefix)
|
|
63
|
+
find_strings_by_pattern("#{prefix}*")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def find_strings_by_pattern(pattern)
|
|
67
|
+
memo = { items: [], cursor: 0 }
|
|
68
|
+
|
|
69
|
+
loop do
|
|
70
|
+
memo[:cursor], items = @redis.scan(memo[:cursor], match: "#{pattern}", count: SCAN_SLICE)
|
|
71
|
+
|
|
72
|
+
memo[:items].push(*items)
|
|
73
|
+
|
|
74
|
+
break if memo[:cursor] == '0'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
memo[:items]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def multiple_strings(keys)
|
|
81
|
+
Hash[keys.zip(@redis.mget(keys))]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def append_to_string(key, val)
|
|
85
|
+
@redis.append(key, val)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Bool
|
|
89
|
+
def set_bool(key, val)
|
|
90
|
+
@redis.set(key, val.to_s)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def bool(key)
|
|
94
|
+
@redis.get(key) == 'true'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Set
|
|
98
|
+
alias initialize_set initialize_map
|
|
99
|
+
alias find_sets_by_prefix find_strings_by_prefix
|
|
100
|
+
|
|
101
|
+
def add_to_set(key, val)
|
|
102
|
+
@redis.sadd?(key, val)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def delete_from_set(key, val)
|
|
106
|
+
@redis.srem(key, val)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def get_set(key)
|
|
110
|
+
@redis.smembers(key)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def in_set?(key, val)
|
|
114
|
+
@redis.sismember(key, val)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def get_all_from_set(key)
|
|
118
|
+
@redis.smembers(key)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def union_sets(set_keys)
|
|
122
|
+
return [] if set_keys == []
|
|
123
|
+
|
|
124
|
+
@redis.sunion(set_keys)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def random_set_elements(key, count)
|
|
128
|
+
@redis.srandmember(key, count)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Queue
|
|
132
|
+
def add_to_queue(key, val)
|
|
133
|
+
@redis.rpush(key, val)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# count = 0 will result in lrange(0,-1), fetching all items
|
|
137
|
+
def get_from_queue(key, count)
|
|
138
|
+
items = @redis.lrange(key, 0, count - 1)
|
|
139
|
+
fetched_count = items.size
|
|
140
|
+
items_to_remove = fetched_count == count ? count : fetched_count
|
|
141
|
+
|
|
142
|
+
@redis.ltrim(key, items_to_remove, -1)
|
|
143
|
+
|
|
144
|
+
items
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# General
|
|
148
|
+
def exists?(key)
|
|
149
|
+
@redis.exists?(key)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def delete(key)
|
|
153
|
+
return nil if key == []
|
|
154
|
+
|
|
155
|
+
@redis.del(key)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def inc(key, inc = 1)
|
|
159
|
+
@redis.incrby(key, inc)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def pipelined
|
|
163
|
+
@redis.pipelined do |pipeline|
|
|
164
|
+
yield
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def clear(prefix)
|
|
169
|
+
# noop
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def expire(key, seconds)
|
|
173
|
+
@redis.expire(key, seconds)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Cache
|
|
3
|
+
module Fetchers
|
|
4
|
+
class SegmentFetcher
|
|
5
|
+
attr_reader :segments_repository
|
|
6
|
+
|
|
7
|
+
def initialize(segments_repository, api_key, config, telemetry_runtime_producer)
|
|
8
|
+
@segments_repository = segments_repository
|
|
9
|
+
@api_key = api_key
|
|
10
|
+
@config = config
|
|
11
|
+
@semaphore = Mutex.new
|
|
12
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
17
|
+
fetch_segments
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
segments_thread
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fetch_segments_if_not_exists(names, cache_control_headers = false)
|
|
25
|
+
names.each do |name|
|
|
26
|
+
change_number = @segments_repository.get_change_number(name)
|
|
27
|
+
|
|
28
|
+
if change_number == -1
|
|
29
|
+
fetch_options = { cache_control_headers: cache_control_headers, till: nil }
|
|
30
|
+
fetch_segment(name, fetch_options) if change_number == -1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fetch_segment(name, fetch_options = { cache_control_headers: false, till: nil })
|
|
38
|
+
@semaphore.synchronize do
|
|
39
|
+
segments_api.fetch_segments_by_names([name], fetch_options)
|
|
40
|
+
end
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fetch_segments
|
|
46
|
+
@semaphore.synchronize do
|
|
47
|
+
segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
|
|
48
|
+
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
53
|
+
false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def stop_segments_thread
|
|
57
|
+
SplitIoClient::Helpers::ThreadHelper.stop(:segment_fetcher, @config)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def segments_thread
|
|
63
|
+
@config.threads[:segment_fetcher] = Thread.new do
|
|
64
|
+
@config.logger.info('Starting segments fetcher service') if @config.debug_enabled
|
|
65
|
+
|
|
66
|
+
loop do
|
|
67
|
+
fetch_segments
|
|
68
|
+
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
|
69
|
+
|
|
70
|
+
sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.segments_refresh_rate)
|
|
71
|
+
@config.logger.debug("Segments fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
|
|
72
|
+
sleep(sleep_for)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def segments_api
|
|
78
|
+
@segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @segments_repository, @config, @telemetry_runtime_producer)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Cache
|
|
3
|
+
module Repositories
|
|
4
|
+
module Events
|
|
5
|
+
class MemoryRepository < EventsRepository
|
|
6
|
+
EVENTS_MAX_SIZE_BYTES = 5242880
|
|
7
|
+
|
|
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
|
|
13
|
+
end
|
|
14
|
+
|
|
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
|
|
18
|
+
|
|
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)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
@size = 0
|
|
29
|
+
@adapter.clear
|
|
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
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SplitIoClient
|
|
2
|
+
module Cache
|
|
3
|
+
module Repositories
|
|
4
|
+
module Events
|
|
5
|
+
class RedisRepository < EventsRepository
|
|
6
|
+
|
|
7
|
+
def initialize(config)
|
|
8
|
+
@config = config
|
|
9
|
+
@adapter = @config.events_adapter
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add(key, traffic_type, event_type, time, value, properties, size)
|
|
13
|
+
@adapter.add_to_queue(
|
|
14
|
+
namespace_key('.events'),
|
|
15
|
+
{ m: metadata, e: event(key, traffic_type, event_type, time, value, properties) }.to_json
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def clear
|
|
20
|
+
@adapter.get_from_queue(namespace_key('.events'), 0).map do |e|
|
|
21
|
+
JSON.parse(e, symbolize_names: true)
|
|
22
|
+
end
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
@config.logger.error("Exception while clearing events cache: #{e}")
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def batch
|
|
29
|
+
clear()
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|