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
|
@@ -4,54 +4,37 @@ module SplitIoClient
|
|
|
4
4
|
module Cache
|
|
5
5
|
module Senders
|
|
6
6
|
class EventsSender
|
|
7
|
-
def initialize(events_repository,
|
|
7
|
+
def initialize(events_repository, config)
|
|
8
8
|
@events_repository = events_repository
|
|
9
|
-
@
|
|
9
|
+
@config = config
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def call
|
|
13
|
-
|
|
14
|
-
post_events
|
|
15
|
-
else
|
|
16
|
-
events_thread
|
|
17
|
-
|
|
18
|
-
if defined?(PhusionPassenger)
|
|
19
|
-
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
|
20
|
-
events_thread if forked
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
12
|
+
def call
|
|
13
|
+
events_thread
|
|
24
14
|
end
|
|
25
15
|
|
|
26
16
|
private
|
|
27
17
|
|
|
28
18
|
def events_thread
|
|
29
|
-
|
|
19
|
+
@config.threads[:events_sender] = Thread.new do
|
|
30
20
|
begin
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
@config.logger.info('Starting events service')
|
|
22
|
+
|
|
33
23
|
loop do
|
|
34
|
-
post_events
|
|
24
|
+
post_events
|
|
35
25
|
|
|
36
|
-
sleep(SplitIoClient::Utilities.randomize_interval(
|
|
26
|
+
sleep(SplitIoClient::Utilities.randomize_interval(@config.events_push_rate))
|
|
37
27
|
end
|
|
38
28
|
rescue SplitIoClient::SDKShutdownException
|
|
39
29
|
post_events
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
@config.logger.info('Posting events due to shutdown')
|
|
42
32
|
end
|
|
43
33
|
end
|
|
44
34
|
end
|
|
45
35
|
|
|
46
|
-
def post_events
|
|
47
|
-
|
|
48
|
-
events_api.post(events)
|
|
49
|
-
rescue StandardError => error
|
|
50
|
-
SplitIoClient.configuration.log_found_exception(__method__.to_s, error)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def events_api
|
|
54
|
-
@events_api ||= SplitIoClient::Api::Events.new(@api_key)
|
|
36
|
+
def post_events
|
|
37
|
+
@events_repository.post_events
|
|
55
38
|
end
|
|
56
39
|
end
|
|
57
40
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Senders
|
|
6
|
+
class MemoryImpressionsSender < ImpressionsSenderAdapter
|
|
7
|
+
def initialize(config, telemetry_api, impressions_api)
|
|
8
|
+
@config = config
|
|
9
|
+
@telemetry_api = telemetry_api
|
|
10
|
+
@impressions_api = impressions_api
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def record_uniques_key(uniques)
|
|
14
|
+
uniques_keys = uniques_formatter(uniques)
|
|
15
|
+
|
|
16
|
+
@telemetry_api.record_unique_keys(uniques_keys) unless uniques_keys.nil?
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def record_impressions_count(impressions_count)
|
|
22
|
+
counts = impressions_count_formatter(impressions_count)
|
|
23
|
+
|
|
24
|
+
@impressions_api.post_count(counts) unless counts.nil?
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def uniques_formatter(uniques)
|
|
32
|
+
return if uniques.nil? || uniques.empty?
|
|
33
|
+
|
|
34
|
+
to_return = { keys: [] }
|
|
35
|
+
uniques.each do |key, value|
|
|
36
|
+
to_return[:keys] << {
|
|
37
|
+
f: key,
|
|
38
|
+
ks: value.to_a
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
to_return
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def impressions_count_formatter(counts)
|
|
49
|
+
return if counts.nil? || counts.empty?
|
|
50
|
+
|
|
51
|
+
formated_counts = {pf: []}
|
|
52
|
+
|
|
53
|
+
counts.each do |key, value|
|
|
54
|
+
key_splited = key.split('::')
|
|
55
|
+
|
|
56
|
+
formated_counts[:pf] << {
|
|
57
|
+
f: key_splited[0].to_s, # feature name
|
|
58
|
+
m: key_splited[1].to_i, # time frame
|
|
59
|
+
rc: value # count
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
formated_counts
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Senders
|
|
6
|
+
class RedisImpressionsSender < ImpressionsSenderAdapter
|
|
7
|
+
EXPIRE_SECONDS = 3600
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
@adapter = @config.impressions_adapter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def record_uniques_key(uniques)
|
|
15
|
+
return if uniques.nil? || uniques.empty?
|
|
16
|
+
|
|
17
|
+
size = @adapter.add_to_queue(unique_keys_key, uniques_formatter(uniques))
|
|
18
|
+
|
|
19
|
+
@adapter.expire(unique_keys_key, EXPIRE_SECONDS) if uniques.length == size
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def record_impressions_count(impressions_count)
|
|
25
|
+
return if impressions_count.nil? || impressions_count.empty?
|
|
26
|
+
|
|
27
|
+
result = @adapter.redis.pipelined do |pipeline|
|
|
28
|
+
impressions_count.each do |key, value|
|
|
29
|
+
pipeline.hincrby(impressions_count_key, key, value)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
expire_impressions_count_key(impressions_count, result)
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def expire_impressions_count_key(impressions_count, pipeline_result)
|
|
41
|
+
total_count = impressions_count.sum { |_, value| value }
|
|
42
|
+
hlen = pipeline_result.last
|
|
43
|
+
|
|
44
|
+
@adapter.expire(impressions_count_key, EXPIRE_SECONDS) if impressions_count.size == hlen && (pipeline_result.sum - hlen) == total_count
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def uniques_formatter(uniques)
|
|
48
|
+
to_return = []
|
|
49
|
+
uniques.each do |key, value|
|
|
50
|
+
to_return << {
|
|
51
|
+
f: key,
|
|
52
|
+
ks: value.to_a
|
|
53
|
+
}.to_json
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
to_return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def impressions_count_key
|
|
60
|
+
"#{@config.redis_namespace}.impressions.count"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unique_keys_key
|
|
64
|
+
"#{@config.redis_namespace}.uniquekeys"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Senders
|
|
6
|
+
class ImpressionsCountSender
|
|
7
|
+
def initialize(config, impression_counter, impressions_sender_adapter)
|
|
8
|
+
@config = config
|
|
9
|
+
@impression_counter = impression_counter
|
|
10
|
+
@impressions_sender_adapter = impressions_sender_adapter
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
impressions_count_thread
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def impressions_count_thread
|
|
20
|
+
@config.threads[:impressions_count_sender] = Thread.new do
|
|
21
|
+
begin
|
|
22
|
+
@config.logger.info('Starting impressions count service')
|
|
23
|
+
loop do
|
|
24
|
+
sleep(@config.counter_refresh_rate)
|
|
25
|
+
post_impressions_count
|
|
26
|
+
end
|
|
27
|
+
rescue SplitIoClient::SDKShutdownException
|
|
28
|
+
post_impressions_count
|
|
29
|
+
|
|
30
|
+
@config.logger.info('Posting impressions count due to shutdown')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def post_impressions_count
|
|
36
|
+
@impressions_sender_adapter.record_impressions_count(@impression_counter.pop_all)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -17,12 +17,10 @@ module SplitIoClient
|
|
|
17
17
|
|
|
18
18
|
formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
|
|
19
19
|
feature_impressions = feature_impressions(filtered_impressions, feature)
|
|
20
|
-
ip = feature_impressions.first[:m][:i]
|
|
21
20
|
current_impressions = current_impressions(feature_impressions)
|
|
22
21
|
memo << {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ip: ip
|
|
22
|
+
f: feature.to_sym,
|
|
23
|
+
i: current_impressions
|
|
26
24
|
}
|
|
27
25
|
end
|
|
28
26
|
|
|
@@ -39,14 +37,28 @@ module SplitIoClient
|
|
|
39
37
|
|
|
40
38
|
def current_impressions(feature_impressions)
|
|
41
39
|
feature_impressions.map do |impression|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
if impression[:i][:properties].nil?
|
|
41
|
+
impression = {
|
|
42
|
+
k: impression[:i][:k],
|
|
43
|
+
t: impression[:i][:t],
|
|
44
|
+
m: impression[:i][:m],
|
|
45
|
+
b: impression[:i][:b],
|
|
46
|
+
r: impression[:i][:r],
|
|
47
|
+
c: impression[:i][:c],
|
|
48
|
+
pt: impression[:i][:pt]
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
impression = {
|
|
52
|
+
k: impression[:i][:k],
|
|
53
|
+
t: impression[:i][:t],
|
|
54
|
+
m: impression[:i][:m],
|
|
55
|
+
b: impression[:i][:b],
|
|
56
|
+
r: impression[:i][:r],
|
|
57
|
+
c: impression[:i][:c],
|
|
58
|
+
pt: impression[:i][:pt],
|
|
59
|
+
properties: impression[:i][:properties].to_json.to_s
|
|
60
|
+
}
|
|
61
|
+
end
|
|
50
62
|
end
|
|
51
63
|
end
|
|
52
64
|
|
|
@@ -73,7 +85,9 @@ module SplitIoClient
|
|
|
73
85
|
"#{impression[:i][:k]}:" \
|
|
74
86
|
"#{impression[:i][:b]}:" \
|
|
75
87
|
"#{impression[:i][:c]}:" \
|
|
76
|
-
"#{impression[:i][:t]}"
|
|
88
|
+
"#{impression[:i][:t]}:" \
|
|
89
|
+
"#{impression[:i][:pt]}" \
|
|
90
|
+
"#{impression[:i][:properties]}" \
|
|
77
91
|
end
|
|
78
92
|
end
|
|
79
93
|
end
|
|
@@ -4,46 +4,32 @@ module SplitIoClient
|
|
|
4
4
|
module Cache
|
|
5
5
|
module Senders
|
|
6
6
|
class ImpressionsSender
|
|
7
|
-
def initialize(impressions_repository,
|
|
7
|
+
def initialize(impressions_repository, config, impressions_api)
|
|
8
8
|
@impressions_repository = impressions_repository
|
|
9
|
-
@
|
|
9
|
+
@config = config
|
|
10
|
+
@impressions_api = impressions_api
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def call
|
|
13
|
-
|
|
14
|
-
SplitIoClient.configuration.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
|
|
14
|
+
impressions_thread
|
|
29
15
|
end
|
|
30
16
|
|
|
31
17
|
private
|
|
32
18
|
|
|
33
19
|
def impressions_thread
|
|
34
|
-
|
|
20
|
+
@config.threads[:impressions_sender] = Thread.new do
|
|
35
21
|
begin
|
|
36
|
-
|
|
22
|
+
@config.logger.info('Starting impressions service')
|
|
37
23
|
|
|
38
24
|
loop do
|
|
39
25
|
post_impressions(false)
|
|
40
26
|
|
|
41
|
-
sleep(SplitIoClient::Utilities.randomize_interval(
|
|
27
|
+
sleep(SplitIoClient::Utilities.randomize_interval(@config.impressions_refresh_rate))
|
|
42
28
|
end
|
|
43
29
|
rescue SplitIoClient::SDKShutdownException
|
|
44
30
|
post_impressions
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
@config.logger.info('Posting impressions due to shutdown')
|
|
47
33
|
end
|
|
48
34
|
end
|
|
49
35
|
end
|
|
@@ -53,12 +39,12 @@ module SplitIoClient
|
|
|
53
39
|
.call(fetch_all_impressions)
|
|
54
40
|
|
|
55
41
|
impressions_api.post(formatted_impressions)
|
|
56
|
-
rescue StandardError =>
|
|
57
|
-
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
58
44
|
end
|
|
59
45
|
|
|
60
46
|
def impressions_api
|
|
61
|
-
@impressions_api
|
|
47
|
+
@impressions_api
|
|
62
48
|
end
|
|
63
49
|
end
|
|
64
50
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Senders
|
|
6
|
+
class ImpressionsSenderAdapter
|
|
7
|
+
extend Forwardable
|
|
8
|
+
def_delegators :@sender, :record_uniques_key, :record_impressions_count
|
|
9
|
+
|
|
10
|
+
def initialize(config, telemetry_api, impressions_api)
|
|
11
|
+
@sender = case config.telemetry_adapter.class.to_s
|
|
12
|
+
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
|
13
|
+
Cache::Senders::RedisImpressionsSender.new(config)
|
|
14
|
+
else
|
|
15
|
+
Cache::Senders::MemoryImpressionsSender.new(config, telemetry_api, impressions_api)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Senders
|
|
6
|
+
class LocalhostRepoCleaner
|
|
7
|
+
def initialize(impressions_repository, events_repository, config)
|
|
8
|
+
@impressions_repository = impressions_repository
|
|
9
|
+
@events_repository = events_repository
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
15
|
+
clear_repositories
|
|
16
|
+
else
|
|
17
|
+
cleaner_thread
|
|
18
|
+
|
|
19
|
+
if defined?(PhusionPassenger)
|
|
20
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
|
21
|
+
cleaner_thread if forked
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def cleaner_thread
|
|
30
|
+
@config.threads[:repo_cleaner] = Thread.new do
|
|
31
|
+
@config.logger.info('Starting repositories cleanup service')
|
|
32
|
+
loop do
|
|
33
|
+
clear_repositories
|
|
34
|
+
|
|
35
|
+
sleep(SplitIoClient::Utilities.randomize_interval(@config.features_refresh_rate))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clear_repositories
|
|
41
|
+
@impressions_repository.clear
|
|
42
|
+
@events_repository.clear
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Stores
|
|
6
|
+
class LocalhostSplitBuilder
|
|
7
|
+
class << self
|
|
8
|
+
def build_splits(splits)
|
|
9
|
+
splits.map do |feature, treatments|
|
|
10
|
+
build_split(feature, treatments)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def build_split(feature, treatments)
|
|
17
|
+
{
|
|
18
|
+
name: feature,
|
|
19
|
+
status: 'ACTIVE',
|
|
20
|
+
killed: false,
|
|
21
|
+
trafficAllocation: 100,
|
|
22
|
+
seed: 2_089_907_429,
|
|
23
|
+
defaultTreatment: 'control_treatment',
|
|
24
|
+
configurations: build_configurations(treatments),
|
|
25
|
+
conditions: build_conditions(treatments),
|
|
26
|
+
prerequisites: []
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build_configurations(treatments)
|
|
31
|
+
treatments.reduce({}) do |hash, treatment|
|
|
32
|
+
hash.merge(treatment[:treatment].to_sym => treatment[:config])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_conditions(treatments)
|
|
37
|
+
conditions = treatments.map do |treatment|
|
|
38
|
+
if treatment[:keys]
|
|
39
|
+
build_whitelist_treatment(treatment[:treatment], Array(treatment[:keys]))
|
|
40
|
+
else
|
|
41
|
+
build_rollout_treatment
|
|
42
|
+
end
|
|
43
|
+
.merge(partitions: build_partitions(treatment[:treatment], treatments))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
conditions.sort_by { |condition| condition[:conditionType] }.reverse!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_whitelist_treatment(treatment_name, whitelist_keys)
|
|
50
|
+
{
|
|
51
|
+
conditionType: 'WHITELIST',
|
|
52
|
+
matcherGroup: {
|
|
53
|
+
combiner: 'AND',
|
|
54
|
+
matchers: [{
|
|
55
|
+
keySelector: nil,
|
|
56
|
+
matcherType: 'WHITELIST',
|
|
57
|
+
negate: false,
|
|
58
|
+
whitelistMatcherData: {
|
|
59
|
+
whitelist: whitelist_keys
|
|
60
|
+
}
|
|
61
|
+
}]
|
|
62
|
+
},
|
|
63
|
+
label: "whitelisted #{treatment_name}"
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_rollout_treatment
|
|
68
|
+
{
|
|
69
|
+
conditionType: 'ROLLOUT',
|
|
70
|
+
matcherGroup: {
|
|
71
|
+
combiner: 'AND',
|
|
72
|
+
matchers: [
|
|
73
|
+
{
|
|
74
|
+
matcherType: 'ALL_KEYS',
|
|
75
|
+
negate: false
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
label: 'default rule'
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_partitions(current_treatment_name, treatments)
|
|
84
|
+
treatments.map do |treatment|
|
|
85
|
+
{
|
|
86
|
+
treatment: treatment[:treatment],
|
|
87
|
+
size: treatment[:treatment] == current_treatment_name ? 100 : 0
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Cache
|
|
5
|
+
module Stores
|
|
6
|
+
class LocalhostSplitStore
|
|
7
|
+
require 'yaml'
|
|
8
|
+
attr_reader :splits_repository
|
|
9
|
+
|
|
10
|
+
def initialize(splits_repository, config, status_manager = nil)
|
|
11
|
+
@splits_repository = splits_repository
|
|
12
|
+
@config = config
|
|
13
|
+
@status_manager = status_manager
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
18
|
+
store_splits
|
|
19
|
+
else
|
|
20
|
+
splits_thread
|
|
21
|
+
|
|
22
|
+
if defined?(PhusionPassenger)
|
|
23
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
|
24
|
+
splits_thread if forked
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def splits_thread
|
|
33
|
+
@config.threads[:split_store] = Thread.new do
|
|
34
|
+
@config.logger.info('Starting feature flags fetcher service')
|
|
35
|
+
loop do
|
|
36
|
+
store_splits
|
|
37
|
+
|
|
38
|
+
sleep(StoreUtils.random_interval(@config.features_refresh_rate))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def store_splits
|
|
44
|
+
load_features.each do |split|
|
|
45
|
+
store_split(split)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@status_manager.ready! if @status_manager
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
@config.logger.error('Error while parsing the split file. ' \
|
|
51
|
+
'Check that the input file matches the expected format')
|
|
52
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def store_split(split)
|
|
56
|
+
@config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled
|
|
57
|
+
|
|
58
|
+
@splits_repository.update([split], [], -1)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def load_features
|
|
62
|
+
yaml_extensions = ['.yml', '.yaml']
|
|
63
|
+
if yaml_extensions.include? File.extname(@config.split_file)
|
|
64
|
+
parse_yaml_features
|
|
65
|
+
else
|
|
66
|
+
@config.logger.warn('Localhost mode: .split mocks ' \
|
|
67
|
+
'will be deprecated soon in favor of YAML files, which provide more ' \
|
|
68
|
+
'targeting power. Take a look in our documentation.')
|
|
69
|
+
|
|
70
|
+
parse_plain_text_features
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_plain_text_features
|
|
75
|
+
splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
|
|
76
|
+
feature, treatment = line.strip.split(' ')
|
|
77
|
+
|
|
78
|
+
next if line.start_with?('#') || line.strip.empty?
|
|
79
|
+
|
|
80
|
+
memo[feature] = [{ treatment: treatment }]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
LocalhostSplitBuilder.build_splits(splits)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_yaml_features
|
|
87
|
+
splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
|
|
88
|
+
feat_symbolized_keys = symbolize_feat_keys(feature)
|
|
89
|
+
|
|
90
|
+
feat_name = feature.keys.first
|
|
91
|
+
|
|
92
|
+
if memo[feat_name]
|
|
93
|
+
memo[feat_name] << feat_symbolized_keys
|
|
94
|
+
else
|
|
95
|
+
memo[feat_name] = [feat_symbolized_keys]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
LocalhostSplitBuilder.build_splits(splits)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def symbolize_feat_keys(yaml_feature)
|
|
103
|
+
yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
|
|
104
|
+
memo[k.to_sym] = k == 'config' ? v.to_json : v
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|