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,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module SplitIoClient
|
|
7
|
+
module FaradayMiddleware
|
|
8
|
+
class Gzip < Faraday::Middleware
|
|
9
|
+
ACCEPT_ENCODING = 'Accept-Encoding'
|
|
10
|
+
CONTENT_ENCODING = 'Content-Encoding'
|
|
11
|
+
CONTENT_LENGTH = 'Content-Length'
|
|
12
|
+
SUPPORTED_ENCODINGS = 'gzip,deflate'
|
|
13
|
+
RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
env[:request_headers][ACCEPT_ENCODING] ||= SUPPORTED_ENCODINGS
|
|
17
|
+
@app.call(env).on_complete do |response_env|
|
|
18
|
+
case response_env[:response_headers][CONTENT_ENCODING]
|
|
19
|
+
when 'gzip'
|
|
20
|
+
reset_body(response_env, &method(:uncompress_gzip))
|
|
21
|
+
when 'deflate'
|
|
22
|
+
reset_body(response_env, &method(:inflate))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset_body(env)
|
|
28
|
+
env[:body] = yield(env[:body])
|
|
29
|
+
env[:response_headers].delete(CONTENT_ENCODING)
|
|
30
|
+
env[:response_headers][CONTENT_LENGTH] = env[:body].length
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def uncompress_gzip(body)
|
|
34
|
+
io = StringIO.new(body)
|
|
35
|
+
gzip_reader = if RUBY_ENCODING
|
|
36
|
+
Zlib::GzipReader.new(io, encoding: 'ASCII-8BIT')
|
|
37
|
+
else
|
|
38
|
+
Zlib::GzipReader.new(io)
|
|
39
|
+
end
|
|
40
|
+
gzip_reader.read
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inflate(body)
|
|
44
|
+
# Inflate as a DEFLATE (RFC 1950+RFC 1951) stream
|
|
45
|
+
Zlib::Inflate.inflate(body)
|
|
46
|
+
rescue Zlib::DataError
|
|
47
|
+
# Fall back to inflating as a "raw" deflate stream which
|
|
48
|
+
# Microsoft servers return
|
|
49
|
+
inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
|
50
|
+
begin
|
|
51
|
+
inflate.inflate(body)
|
|
52
|
+
ensure
|
|
53
|
+
inflate.close
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Api
|
|
5
|
+
class Impressions < Client
|
|
6
|
+
def initialize(api_key, config, telemetry_runtime_producer)
|
|
7
|
+
super(config)
|
|
8
|
+
@api_key = api_key
|
|
9
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def post(impressions)
|
|
13
|
+
if impressions.empty?
|
|
14
|
+
@config.split_logger.log_if_debug('No impressions to report')
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
start = Time.now
|
|
19
|
+
|
|
20
|
+
response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
|
|
21
|
+
|
|
22
|
+
if response.success?
|
|
23
|
+
@config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
|
|
24
|
+
|
|
25
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
26
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, bucket)
|
|
27
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
28
|
+
else
|
|
29
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSIONS_SYNC, response.status)
|
|
30
|
+
|
|
31
|
+
@config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
|
|
32
|
+
' - Check your API key and base URI')
|
|
33
|
+
raise 'Split SDK failed to connect to backend to post impressions'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def post_count(impressions_count)
|
|
38
|
+
if impressions_count.nil? || impressions_count[:pf].empty?
|
|
39
|
+
@config.split_logger.log_if_debug('No impressions count to send')
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
start = Time.now
|
|
44
|
+
|
|
45
|
+
response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
|
|
46
|
+
|
|
47
|
+
if response.success?
|
|
48
|
+
@config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
|
|
49
|
+
|
|
50
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
51
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, bucket)
|
|
52
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
53
|
+
else
|
|
54
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::IMPRESSION_COUNT_SYNC, response.status)
|
|
55
|
+
|
|
56
|
+
@config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
|
|
57
|
+
' - Check your API key and base URI')
|
|
58
|
+
raise 'Split SDK failed to connect to backend to post impressions'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def total_impressions(impressions)
|
|
63
|
+
return 0 if impressions.nil?
|
|
64
|
+
|
|
65
|
+
impressions.reduce(0) do |impressions_count, impression|
|
|
66
|
+
impressions_count += impression[:i].length
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def impressions_headers
|
|
73
|
+
{
|
|
74
|
+
'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Api
|
|
5
|
+
# Retrieves segment changes from the Split Backend
|
|
6
|
+
class Segments < Client
|
|
7
|
+
def initialize(api_key, segments_repository, config, telemetry_runtime_producer)
|
|
8
|
+
super(config)
|
|
9
|
+
@api_key = api_key
|
|
10
|
+
@segments_repository = segments_repository
|
|
11
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fetch_segments_by_names(names, fetch_options = { cache_control_headers: false, till: nil })
|
|
15
|
+
return if names.nil? || names.empty?
|
|
16
|
+
|
|
17
|
+
names.each do |name|
|
|
18
|
+
since = @segments_repository.get_change_number(name)
|
|
19
|
+
|
|
20
|
+
loop do
|
|
21
|
+
segment = fetch_segment_changes(name, since, fetch_options)
|
|
22
|
+
@segments_repository.add_to_segment(segment)
|
|
23
|
+
|
|
24
|
+
@config.split_logger.log_if_debug("Segment #{name} fetched before: #{since}, \
|
|
25
|
+
till: #{@segments_repository.get_change_number(name)}")
|
|
26
|
+
|
|
27
|
+
break if since.to_i >= @segments_repository.get_change_number(name).to_i
|
|
28
|
+
|
|
29
|
+
since = @segments_repository.get_change_number(name)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil })
|
|
37
|
+
start = Time.now
|
|
38
|
+
|
|
39
|
+
params = { since: since }
|
|
40
|
+
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
|
41
|
+
response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, params, fetch_options[:cache_control_headers])
|
|
42
|
+
|
|
43
|
+
if response.success?
|
|
44
|
+
segment = JSON.parse(response.body, symbolize_names: true)
|
|
45
|
+
@segments_repository.set_change_number(name, segment[:till])
|
|
46
|
+
|
|
47
|
+
@config.split_logger.log_if_debug("\'#{segment[:name]}\' segment retrieved.")
|
|
48
|
+
unless segment[:added].empty?
|
|
49
|
+
@config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:added].size} added keys")
|
|
50
|
+
end
|
|
51
|
+
unless segment[:removed].empty?
|
|
52
|
+
@config.split_logger.log_if_debug("\'#{segment[:name]}\' #{segment[:removed].size} removed keys")
|
|
53
|
+
end
|
|
54
|
+
@config.split_logger.log_if_transport("Segment changes response: #{segment.to_s}")
|
|
55
|
+
|
|
56
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
57
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SEGMENT_SYNC, bucket)
|
|
58
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SEGMENT_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
59
|
+
|
|
60
|
+
segment
|
|
61
|
+
elsif response.status == 403
|
|
62
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
|
|
63
|
+
|
|
64
|
+
@config.logger.error('Factory Instantiation: You passed a browser type api_key, ' \
|
|
65
|
+
'please grab an api key from the Split console that is of type sdk')
|
|
66
|
+
@config.valid_mode = false
|
|
67
|
+
else
|
|
68
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SEGMENT_SYNC, response.status)
|
|
69
|
+
|
|
70
|
+
@config.logger.error("Unexpected status code while fetching segments: #{response.status}." \
|
|
71
|
+
"Since #{since} - Check your API key and base URI")
|
|
72
|
+
|
|
73
|
+
raise 'Split SDK failed to connect to backend to fetch segments'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Api
|
|
5
|
+
# Retrieves split definitions from the Split Backend
|
|
6
|
+
class Splits < Client
|
|
7
|
+
|
|
8
|
+
PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
|
|
9
|
+
SPEC_1_1 = "1.1"
|
|
10
|
+
|
|
11
|
+
def initialize(api_key, config, telemetry_runtime_producer)
|
|
12
|
+
super(config)
|
|
13
|
+
@api_key = api_key
|
|
14
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
15
|
+
@flag_sets_filter = @config.flag_sets_filter
|
|
16
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
17
|
+
@last_proxy_check_timestamp = 0
|
|
18
|
+
@clear_storage = false
|
|
19
|
+
@old_spec_since = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
|
|
23
|
+
start = Time.now
|
|
24
|
+
|
|
25
|
+
if check_last_proxy_check_timestamp
|
|
26
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
27
|
+
@config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled
|
|
28
|
+
@old_spec_since = since
|
|
29
|
+
since = -1
|
|
30
|
+
since_rbs = -1
|
|
31
|
+
fetch_options = { cache_control_headers: false, till: nil, sets: nil}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if @spec_version == Splits::SPEC_1_1
|
|
35
|
+
since = @old_spec_since unless @old_spec_since.nil?
|
|
36
|
+
params = { s: @spec_version, since: since }
|
|
37
|
+
@old_spec_since = nil
|
|
38
|
+
else
|
|
39
|
+
params = { s: @spec_version, since: since, rbSince: since_rbs }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
|
|
43
|
+
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
|
44
|
+
@config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled
|
|
45
|
+
response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
|
|
46
|
+
if response.status == 414
|
|
47
|
+
@config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
|
|
48
|
+
raise ApiException.new response.body, 414
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
52
|
+
@config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
|
|
53
|
+
@spec_version = Splits::SPEC_1_1
|
|
54
|
+
@last_proxy_check_timestamp = Time.now
|
|
55
|
+
return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
|
|
56
|
+
sets: fetch_options[:sets]})
|
|
57
|
+
end
|
|
58
|
+
if response.success?
|
|
59
|
+
result = JSON.parse(response.body, symbolize_names: true)
|
|
60
|
+
|
|
61
|
+
return process_result(result, since, since_rbs, start)
|
|
62
|
+
end
|
|
63
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
|
|
64
|
+
|
|
65
|
+
@config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
|
|
66
|
+
'Check your API key and base URI')
|
|
67
|
+
|
|
68
|
+
raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def clear_storage
|
|
72
|
+
@clear_storage
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def process_result(result, since, since_rbs, start)
|
|
78
|
+
if @spec_version == Splits::SPEC_1_1
|
|
79
|
+
result = convert_to_new_spec(result)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
|
|
83
|
+
result = objects_with_segment_names(result)
|
|
84
|
+
|
|
85
|
+
if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
86
|
+
@clear_storage = @last_proxy_check_timestamp != 0
|
|
87
|
+
@last_proxy_check_timestamp = 0
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless result[:ff][:d].empty?
|
|
91
|
+
@config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
|
|
92
|
+
end
|
|
93
|
+
@config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
|
|
94
|
+
|
|
95
|
+
unless result[:rbs][:d].empty?
|
|
96
|
+
@config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
|
|
97
|
+
end
|
|
98
|
+
@config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
|
|
99
|
+
|
|
100
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
101
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
|
|
102
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
103
|
+
|
|
104
|
+
result
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def check_rbs_data(rbs_data)
|
|
108
|
+
rbs_data.each do |rb_segment|
|
|
109
|
+
rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
|
|
110
|
+
rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
|
|
111
|
+
rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
|
|
112
|
+
end
|
|
113
|
+
rbs_data
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def objects_with_segment_names(parsed_objects)
|
|
117
|
+
parsed_objects[:segment_names] = Set.new
|
|
118
|
+
parsed_objects[:segment_names] =
|
|
119
|
+
parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
|
|
120
|
+
splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
|
|
121
|
+
end.flatten
|
|
122
|
+
|
|
123
|
+
parsed_objects[:rbs][:d].each do |rule_based_segment|
|
|
124
|
+
parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
parsed_objects
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def check_last_proxy_check_timestamp
|
|
131
|
+
@spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def convert_to_new_spec(body)
|
|
135
|
+
{:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Api
|
|
5
|
+
class TelemetryApi < Client
|
|
6
|
+
def initialize(config, api_key, telemetry_runtime_producer)
|
|
7
|
+
super(config)
|
|
8
|
+
@api_key = api_key
|
|
9
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def record_init(config_init)
|
|
13
|
+
post_telemetry("#{@config.telemetry_service_url}/metrics/config", config_init, 'init')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def record_stats(stats)
|
|
17
|
+
post_telemetry("#{@config.telemetry_service_url}/metrics/usage", stats, 'stats')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def record_unique_keys(uniques)
|
|
21
|
+
return if uniques[:keys].empty?
|
|
22
|
+
|
|
23
|
+
post_telemetry("#{@config.telemetry_service_url}/keys/ss", uniques, 'unique_keys')
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def post_telemetry(url, obj, method)
|
|
31
|
+
start = Time.now
|
|
32
|
+
response = post_api(url, @api_key, obj)
|
|
33
|
+
|
|
34
|
+
if response.success?
|
|
35
|
+
@config.split_logger.log_if_debug("Telemetry post succeeded: record #{method}.")
|
|
36
|
+
|
|
37
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
38
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TELEMETRY_SYNC, bucket)
|
|
39
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TELEMETRY_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
40
|
+
else
|
|
41
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TELEMETRY_SYNC, response.status)
|
|
42
|
+
@config.logger.error("Unexpected status code while posting telemetry #{method}: #{response.status}.")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'jwt'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
|
|
6
|
+
module SplitIoClient
|
|
7
|
+
module Engine
|
|
8
|
+
class AuthApiClient
|
|
9
|
+
def initialize(config, telemetry_runtime_producer)
|
|
10
|
+
@config = config
|
|
11
|
+
@api_client = SplitIoClient::Api::Client.new(@config)
|
|
12
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def authenticate(api_key)
|
|
16
|
+
start = Time.now
|
|
17
|
+
response = @api_client.get_api("#{@config.auth_service_url}?s=#{SplitIoClient::Spec::FeatureFlags::SPEC_VERSION}", api_key)
|
|
18
|
+
|
|
19
|
+
return process_success(response, start) if response.success?
|
|
20
|
+
|
|
21
|
+
return process_error(response) if response.status >= 400 && response.status < 500
|
|
22
|
+
|
|
23
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i)
|
|
24
|
+
if @config.debug_enabled
|
|
25
|
+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
|
|
26
|
+
end
|
|
27
|
+
{ push_enabled: false, retry: true }
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
@config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled
|
|
30
|
+
{ push_enabled: false, retry: false }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def expiration(token_decoded)
|
|
36
|
+
exp = token_decoded[0]['exp']
|
|
37
|
+
issued_at = token_decoded[0]['iat']
|
|
38
|
+
|
|
39
|
+
exp - issued_at - SplitIoClient::Constants::EXPIRATION_RATE
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def channels(token_decoded)
|
|
43
|
+
capability = token_decoded[0]['x-ably-capability']
|
|
44
|
+
channels_hash = JSON.parse(capability)
|
|
45
|
+
channels_string = channels_hash.keys.join(',')
|
|
46
|
+
channels_string = control_channels(channels_string)
|
|
47
|
+
@config.logger.debug("Channels #{channels_string}") if @config.debug_enabled
|
|
48
|
+
CGI.escape(channels_string)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def decode_token(token)
|
|
52
|
+
JWT.decode token, nil, false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def process_error(response)
|
|
56
|
+
if @config.debug_enabled
|
|
57
|
+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
|
|
58
|
+
end
|
|
59
|
+
@telemetry_runtime_producer.record_auth_rejections if response.status == 401
|
|
60
|
+
|
|
61
|
+
{ push_enabled: false, retry: false }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def process_success(response, start)
|
|
65
|
+
@config.logger.debug("Success connection to: #{@config.auth_service_url}") if @config.debug_enabled
|
|
66
|
+
record_telemetry(start)
|
|
67
|
+
|
|
68
|
+
body_json = JSON.parse(response.body, symbolize_names: true)
|
|
69
|
+
push_enabled = body_json[:pushEnabled]
|
|
70
|
+
token = body_json[:token]
|
|
71
|
+
|
|
72
|
+
if push_enabled
|
|
73
|
+
decoded_token = decode_token(token)
|
|
74
|
+
channels = channels(decoded_token)
|
|
75
|
+
exp = expiration(decoded_token)
|
|
76
|
+
|
|
77
|
+
@telemetry_runtime_producer.record_token_refreshes
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
{ push_enabled: push_enabled, token: token, channels: channels, exp: exp, retry: true }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def control_channels(channels_string)
|
|
84
|
+
prefix = SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX
|
|
85
|
+
control_pri = SplitIoClient::Constants::CONTROL_PRI
|
|
86
|
+
control_sec = SplitIoClient::Constants::CONTROL_SEC
|
|
87
|
+
channels_string = channels_string.gsub(control_pri, "#{prefix}#{control_pri}")
|
|
88
|
+
|
|
89
|
+
channels_string.gsub(control_sec, "#{prefix}#{control_sec}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def record_telemetry(start)
|
|
93
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
94
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::TOKEN_SYNC, bucket)
|
|
95
|
+
timestamp = (Time.now.to_f * 1000.0).to_i
|
|
96
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::TOKEN_SYNC, timestamp)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Engine
|
|
5
|
+
BACKOFF_MAX_ALLOWED = 1800
|
|
6
|
+
class BackOff
|
|
7
|
+
def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED)
|
|
8
|
+
@attempt = attempt
|
|
9
|
+
@back_off_base = back_off_base
|
|
10
|
+
@max_allowed = max_allowed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def interval
|
|
14
|
+
interval = 0
|
|
15
|
+
interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
|
|
16
|
+
@attempt += 1
|
|
17
|
+
|
|
18
|
+
interval >= @max_allowed ? @max_allowed : interval
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reset
|
|
22
|
+
@attempt = 0
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
module SplitIoClient
|
|
6
|
+
module Engine
|
|
7
|
+
module Common
|
|
8
|
+
TIME_INTERVAL_MS = 3600 * 1000
|
|
9
|
+
|
|
10
|
+
class ImpressionCounter
|
|
11
|
+
DEFAULT_AMOUNT = 1
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@cache = Concurrent::Hash.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inc(split_name, time_frame)
|
|
18
|
+
key = make_key(split_name, time_frame)
|
|
19
|
+
|
|
20
|
+
current_amount = @cache[key]
|
|
21
|
+
@cache[key] = current_amount.nil? ? DEFAULT_AMOUNT : (current_amount + DEFAULT_AMOUNT)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def pop_all
|
|
25
|
+
to_return = Concurrent::Hash.new
|
|
26
|
+
|
|
27
|
+
@cache.each do |key, value|
|
|
28
|
+
to_return[key] = value
|
|
29
|
+
end
|
|
30
|
+
@cache.clear
|
|
31
|
+
|
|
32
|
+
to_return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def make_key(split_name, time_frame)
|
|
36
|
+
"#{split_name}::#{ImpressionCounter.truncate_time_frame(time_frame)}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.truncate_time_frame(timestamp_ms)
|
|
40
|
+
timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|