splitclient-rb 7.1.4.pre.rc7 → 7.1.4.pre.rc8
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/CHANGES.txt +1 -1
- data/Rakefile +7 -2
- data/ext/murmurhash/3_x64_128.c +117 -0
- data/ext/murmurhash/3_x86_32.c +88 -0
- data/ext/murmurhash/extconf.rb +5 -0
- data/ext/murmurhash/murmurhash.c +255 -0
- data/ext/murmurhash/murmurhash.h +94 -0
- data/lib/splitclient-rb.rb +6 -1
- 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/repositories/impressions/memory_repository.rb +4 -18
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +7 -18
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +1 -27
- data/lib/splitclient-rb/cache/routers/impression_router.rb +12 -14
- data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +73 -0
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +11 -11
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +3 -3
- data/lib/splitclient-rb/clients/split_client.rb +24 -73
- data/lib/splitclient-rb/engine/api/impressions.rb +30 -13
- data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +87 -0
- data/lib/splitclient-rb/engine/evaluator/splitter.rb +1 -5
- data/lib/splitclient-rb/engine/parser/evaluator.rb +0 -4
- data/lib/splitclient-rb/engine/sync_manager.rb +5 -6
- data/lib/splitclient-rb/engine/synchronizer.rb +9 -1
- data/lib/splitclient-rb/split_config.rb +31 -1
- data/lib/splitclient-rb/split_factory.rb +5 -2
- data/lib/splitclient-rb/version.rb +1 -1
- data/splitclient-rb.gemspec +8 -1
- metadata +14 -17
@@ -4,10 +4,10 @@ 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
|
-
@api_key = api_key
|
10
9
|
@config = config
|
10
|
+
@impressions_api = impressions_api
|
11
11
|
end
|
12
12
|
|
13
13
|
def call
|
@@ -50,7 +50,7 @@ module SplitIoClient
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def impressions_api
|
53
|
-
@impressions_api
|
53
|
+
@impressions_api
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -9,7 +9,7 @@ module SplitIoClient
|
|
9
9
|
# @param api_key [String] the API key for your split account
|
10
10
|
#
|
11
11
|
# @return [SplitIoClient] split.io client instance
|
12
|
-
def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config)
|
12
|
+
def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config, impressions_manager)
|
13
13
|
@api_key = api_key
|
14
14
|
@metrics = metrics
|
15
15
|
@splits_repository = splits_repository
|
@@ -20,17 +20,21 @@ module SplitIoClient
|
|
20
20
|
@sdk_blocker = sdk_blocker
|
21
21
|
@destroyed = false
|
22
22
|
@config = config
|
23
|
+
@impressions_manager = impressions_manager
|
23
24
|
end
|
24
25
|
|
25
26
|
def get_treatment(
|
26
27
|
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
27
28
|
multiple = false, evaluator = nil
|
28
29
|
)
|
29
|
-
|
30
|
+
impressions = []
|
31
|
+
result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment', impressions)
|
32
|
+
@impressions_manager.track(impressions)
|
33
|
+
|
30
34
|
if multiple
|
31
|
-
|
35
|
+
result.tap { |t| t.delete(:config) }
|
32
36
|
else
|
33
|
-
|
37
|
+
result[:treatment]
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
@@ -38,7 +42,11 @@ module SplitIoClient
|
|
38
42
|
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
39
43
|
multiple = false, evaluator = nil
|
40
44
|
)
|
41
|
-
|
45
|
+
impressions = []
|
46
|
+
result = treatment(key, split_name, attributes, split_data, store_impressions, multiple, evaluator, 'get_treatment_with_config', impressions)
|
47
|
+
@impressions_manager.track(impressions)
|
48
|
+
|
49
|
+
result
|
42
50
|
end
|
43
51
|
|
44
52
|
def get_treatments(key, split_names, attributes = {})
|
@@ -74,53 +82,6 @@ module SplitIoClient
|
|
74
82
|
@destroyed = true
|
75
83
|
end
|
76
84
|
|
77
|
-
def store_impression(split_name, matching_key, bucketing_key, treatment, attributes)
|
78
|
-
time = (Time.now.to_f * 1000.0).to_i
|
79
|
-
|
80
|
-
@impressions_repository.add(
|
81
|
-
matching_key,
|
82
|
-
bucketing_key,
|
83
|
-
split_name,
|
84
|
-
treatment,
|
85
|
-
time
|
86
|
-
)
|
87
|
-
|
88
|
-
route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
|
89
|
-
|
90
|
-
rescue StandardError => error
|
91
|
-
@config.log_found_exception(__method__.to_s, error)
|
92
|
-
end
|
93
|
-
|
94
|
-
def route_impression(split_name, matching_key, bucketing_key, time, treatment, attributes)
|
95
|
-
impression_router.add(
|
96
|
-
split_name: split_name,
|
97
|
-
matching_key: matching_key,
|
98
|
-
bucketing_key: bucketing_key,
|
99
|
-
time: time,
|
100
|
-
treatment: {
|
101
|
-
label: treatment[:label],
|
102
|
-
treatment: treatment[:treatment],
|
103
|
-
change_number: treatment[:change_number]
|
104
|
-
},
|
105
|
-
attributes: attributes
|
106
|
-
)
|
107
|
-
end
|
108
|
-
|
109
|
-
def route_impressions(split_names, matching_key, bucketing_key, time, treatments_labels_change_numbers, attributes)
|
110
|
-
impression_router.add_bulk(
|
111
|
-
split_names: split_names,
|
112
|
-
matching_key: matching_key,
|
113
|
-
bucketing_key: bucketing_key,
|
114
|
-
time: time,
|
115
|
-
treatments_labels_change_numbers: treatments_labels_change_numbers,
|
116
|
-
attributes: attributes
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
def impression_router
|
121
|
-
@impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
|
122
|
-
end
|
123
|
-
|
124
85
|
def track(key, traffic_type_name, event_type, value = nil, properties = nil)
|
125
86
|
return false unless valid_client && @config.split_validator.valid_track_parameters(key, traffic_type_name, event_type, value, properties)
|
126
87
|
|
@@ -239,26 +200,20 @@ module SplitIoClient
|
|
239
200
|
|
240
201
|
bucketing_key, matching_key = keys_from_key(key)
|
241
202
|
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
|
242
|
-
matching_key = matching_key ? matching_key.to_s : nil
|
203
|
+
matching_key = matching_key ? matching_key.to_s : nil
|
243
204
|
|
244
205
|
evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @config, true)
|
245
206
|
start = Time.now
|
207
|
+
impressions = []
|
246
208
|
treatments_labels_change_numbers =
|
247
209
|
@splits_repository.get_splits(sanitized_split_names).each_with_object({}) do |(name, data), memo|
|
248
|
-
memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator))
|
210
|
+
memo.merge!(name => treatment(key, name, attributes, data, false, true, evaluator, calling_method, impressions))
|
249
211
|
end
|
250
212
|
latency = (Time.now - start) * 1000.0
|
251
213
|
# Measure
|
252
214
|
@metrics.time('sdk.' + calling_method, latency)
|
253
215
|
|
254
|
-
|
255
|
-
|
256
|
-
time = (Time.now.to_f * 1000.0).to_i
|
257
|
-
@impressions_repository.add_bulk(
|
258
|
-
matching_key, bucketing_key, treatments_for_impressions, time
|
259
|
-
) unless treatments_for_impressions == {}
|
260
|
-
|
261
|
-
route_impressions(sanitized_split_names, matching_key, bucketing_key, time, treatments_for_impressions, attributes)
|
216
|
+
@impressions_manager.track(impressions)
|
262
217
|
|
263
218
|
split_names_keys = treatments_labels_change_numbers.keys
|
264
219
|
treatments = treatments_labels_change_numbers.values.map do |v|
|
@@ -284,7 +239,7 @@ module SplitIoClient
|
|
284
239
|
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
|
285
240
|
def treatment(
|
286
241
|
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
|
287
|
-
multiple = false, evaluator = nil, calling_method = 'get_treatment'
|
242
|
+
multiple = false, evaluator = nil, calling_method = 'get_treatment', impressions = []
|
288
243
|
)
|
289
244
|
control_treatment = { treatment: Engine::Models::Treatment::CONTROL }
|
290
245
|
|
@@ -329,15 +284,18 @@ module SplitIoClient
|
|
329
284
|
end
|
330
285
|
|
331
286
|
latency = (Time.now - start) * 1000.0
|
332
|
-
|
333
|
-
|
287
|
+
|
288
|
+
impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, treatment_data, { attributes: attributes, time: nil })
|
289
|
+
impressions << impression unless impression.nil?
|
334
290
|
|
335
291
|
# Measure
|
336
292
|
@metrics.time('sdk.' + calling_method, latency) unless multiple
|
337
293
|
rescue StandardError => error
|
294
|
+
p error
|
338
295
|
@config.log_found_exception(__method__.to_s, error)
|
339
296
|
|
340
|
-
|
297
|
+
impression = @impressions_manager.build_impression(matching_key, bucketing_key, split_name, control_treatment, { attributes: attributes, time: nil })
|
298
|
+
impressions << impression unless impression.nil?
|
341
299
|
|
342
300
|
return parsed_treatment(multiple, control_treatment.merge({ label: Engine::Models::Label::EXCEPTION }))
|
343
301
|
end
|
@@ -357,12 +315,5 @@ module SplitIoClient
|
|
357
315
|
def parsed_attributes(attributes)
|
358
316
|
return attributes || attributes.to_h
|
359
317
|
end
|
360
|
-
|
361
|
-
def get_treatment_for_impressions(treatments_labels_change_numbers)
|
362
|
-
return treatments_labels_change_numbers.select{|imp|
|
363
|
-
treatments_labels_change_numbers[imp][:label] != Engine::Models::Label::NOT_FOUND &&
|
364
|
-
!treatments_labels_change_numbers[imp][:label].nil?
|
365
|
-
}
|
366
|
-
end
|
367
318
|
end
|
368
319
|
end
|
@@ -14,16 +14,31 @@ module SplitIoClient
|
|
14
14
|
return
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
response = post_api("#{@config.events_uri}/testImpressions/bulk", @api_key, impressions, impressions_headers)
|
18
|
+
|
19
|
+
if response.success?
|
20
|
+
@config.split_logger.log_if_debug("Impressions reported: #{total_impressions(impressions)}")
|
21
|
+
else
|
22
|
+
@config.logger.error("Unexpected status code while posting impressions: #{response.status}." \
|
23
|
+
' - Check your API key and base URI')
|
24
|
+
raise 'Split SDK failed to connect to backend to post impressions'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_count(impressions_count)
|
29
|
+
if impressions_count.nil? || impressions_count[:pf].empty?
|
30
|
+
@config.split_logger.log_if_debug('No impressions count to send')
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
response = post_api("#{@config.events_uri}/testImpressions/count", @api_key, impressions_count)
|
35
|
+
|
36
|
+
if response.success?
|
37
|
+
@config.split_logger.log_if_debug("Impressions count sent: #{impressions_count[:pf].length}")
|
38
|
+
else
|
39
|
+
@config.logger.error("Unexpected status code while posting impressions count: #{response.status}." \
|
40
|
+
' - Check your API key and base URI')
|
41
|
+
raise 'Split SDK failed to connect to backend to post impressions'
|
27
42
|
end
|
28
43
|
end
|
29
44
|
|
@@ -31,14 +46,16 @@ module SplitIoClient
|
|
31
46
|
return 0 if impressions.nil?
|
32
47
|
|
33
48
|
impressions.reduce(0) do |impressions_count, impression|
|
34
|
-
impressions_count += impression[:
|
49
|
+
impressions_count += impression[:i].length
|
35
50
|
end
|
36
51
|
end
|
37
52
|
|
38
53
|
private
|
39
54
|
|
40
|
-
def
|
41
|
-
|
55
|
+
def impressions_headers
|
56
|
+
{
|
57
|
+
'SplitSDKImpressionsMode' => @config.impressions_mode.to_s
|
58
|
+
}
|
42
59
|
end
|
43
60
|
end
|
44
61
|
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 truncate_time_frame(timestamp_ms)
|
36
|
+
timestamp_ms - (timestamp_ms % TIME_INTERVAL_MS)
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_key(split_name, time_frame)
|
40
|
+
"#{split_name}::#{truncate_time_frame(time_frame)}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Engine
|
5
|
+
module Common
|
6
|
+
class ImpressionManager
|
7
|
+
def initialize(config, impressions_repository, impression_counter)
|
8
|
+
@config = config
|
9
|
+
@impressions_repository = impressions_repository
|
10
|
+
@impression_counter = impression_counter
|
11
|
+
@impression_router = SplitIoClient::ImpressionRouter.new(@config)
|
12
|
+
@impression_observer = SplitIoClient::Observers::ImpressionObserver.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# added param time for test
|
16
|
+
def build_impression(matching_key, bucketing_key, split_name, treatment, params = {})
|
17
|
+
impression_data = impression_data(matching_key, bucketing_key, split_name, treatment, params[:time])
|
18
|
+
|
19
|
+
impression_data[:pt] = @impression_observer.test_and_set(impression_data) unless redis?
|
20
|
+
|
21
|
+
return impression_optimized(split_name, impression_data, params[:attributes]) if optimized? && !redis?
|
22
|
+
|
23
|
+
impression(impression_data, params[:attributes])
|
24
|
+
rescue StandardError => error
|
25
|
+
@config.log_found_exception(__method__.to_s, error)
|
26
|
+
end
|
27
|
+
|
28
|
+
def track(impressions)
|
29
|
+
@impressions_repository.add_bulk(impressions)
|
30
|
+
@impression_router.add_bulk(impressions)
|
31
|
+
rescue StandardError => error
|
32
|
+
@config.log_found_exception(__method__.to_s, error)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# added param time for test
|
38
|
+
def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil)
|
39
|
+
{
|
40
|
+
k: matching_key,
|
41
|
+
b: bucketing_key,
|
42
|
+
f: split_name,
|
43
|
+
t: treatment[:treatment],
|
44
|
+
r: applied_rule(treatment[:label]),
|
45
|
+
c: treatment[:change_number],
|
46
|
+
m: time || (Time.now.to_f * 1000.0).to_i,
|
47
|
+
pt: nil
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def metadata
|
52
|
+
{
|
53
|
+
s: "#{@config.language}-#{@config.version}",
|
54
|
+
i: @config.machine_ip,
|
55
|
+
n: @config.machine_name
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def applied_rule(label)
|
60
|
+
@config.labels_enabled ? label : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def optimized?
|
64
|
+
@config.impressions_mode == :optimized
|
65
|
+
end
|
66
|
+
|
67
|
+
def impression_optimized(split_name, impression_data, attributes)
|
68
|
+
@impression_counter.inc(split_name, impression_data[:m])
|
69
|
+
|
70
|
+
impression(impression_data, attributes) if should_queue_impression?(impression_data)
|
71
|
+
end
|
72
|
+
|
73
|
+
def should_queue_impression?(impression)
|
74
|
+
impression[:pt].nil? || (impression[:pt] < ((Time.now.to_f * 1000.0).to_i - Common::TIME_INTERVAL_MS))
|
75
|
+
end
|
76
|
+
|
77
|
+
def impression(impression_data, attributes)
|
78
|
+
{ m: metadata, i: impression_data, attributes: attributes }
|
79
|
+
end
|
80
|
+
|
81
|
+
def redis?
|
82
|
+
@config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -1,7 +1,3 @@
|
|
1
|
-
unless defined?(JRUBY_VERSION)
|
2
|
-
require 'digest/murmurhash'
|
3
|
-
end
|
4
|
-
|
5
1
|
module SplitIoClient
|
6
2
|
# Misc class in charge of providing hash functions and
|
7
3
|
# determination of treatment based on concept of buckets
|
@@ -13,7 +9,7 @@ module SplitIoClient
|
|
13
9
|
when 'java'
|
14
10
|
Proc.new { |key, seed| Java::MurmurHash3.murmurhash3_x86_32(key, seed) }
|
15
11
|
else
|
16
|
-
Proc.new { |key, seed| Digest::
|
12
|
+
Proc.new { |key, seed| Digest::MurmurHashMRI3_x86_32.rawdigest(key, [seed].pack('L')) }
|
17
13
|
end
|
18
14
|
end
|
19
15
|
|
@@ -9,14 +9,13 @@ module SplitIoClient
|
|
9
9
|
repositories,
|
10
10
|
api_key,
|
11
11
|
config,
|
12
|
-
|
13
|
-
metrics
|
12
|
+
params
|
14
13
|
)
|
15
|
-
split_fetcher = SplitFetcher.new(repositories[:splits], api_key, metrics, config, sdk_blocker)
|
16
|
-
segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, metrics, config, sdk_blocker)
|
17
|
-
sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher }
|
14
|
+
split_fetcher = SplitFetcher.new(repositories[:splits], api_key, params[:metrics], config, params[:sdk_blocker])
|
15
|
+
segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, params[:metrics], config, params[:sdk_blocker])
|
16
|
+
sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher, imp_counter: params[:impression_counter] }
|
18
17
|
|
19
|
-
@synchronizer = Synchronizer.new(repositories, api_key, config, sdk_blocker, sync_params)
|
18
|
+
@synchronizer = Synchronizer.new(repositories, api_key, config, params[:sdk_blocker], sync_params)
|
20
19
|
notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
|
21
20
|
manager.on_occupancy { |publisher_available| process_occupancy(publisher_available) }
|
22
21
|
manager.on_push_shutdown { process_push_shutdown }
|
@@ -23,6 +23,8 @@ module SplitIoClient
|
|
23
23
|
@sdk_blocker = sdk_blocker
|
24
24
|
@split_fetcher = params[:split_fetcher]
|
25
25
|
@segment_fetcher = params[:segment_fetcher]
|
26
|
+
@impressions_api = SplitIoClient::Api::Impressions.new(@api_key, @config)
|
27
|
+
@impression_counter = params[:imp_counter]
|
26
28
|
end
|
27
29
|
|
28
30
|
def sync_all
|
@@ -35,6 +37,7 @@ module SplitIoClient
|
|
35
37
|
impressions_sender
|
36
38
|
metrics_sender
|
37
39
|
events_sender
|
40
|
+
impressions_count_sender
|
38
41
|
end
|
39
42
|
|
40
43
|
def start_periodic_fetch
|
@@ -73,7 +76,7 @@ module SplitIoClient
|
|
73
76
|
|
74
77
|
# Starts thread which loops constantly and sends impressions to the Split API
|
75
78
|
def impressions_sender
|
76
|
-
ImpressionsSender.new(@impressions_repository, @
|
79
|
+
ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
|
77
80
|
end
|
78
81
|
|
79
82
|
# Starts thread which loops constantly and sends metrics to the Split API
|
@@ -85,6 +88,11 @@ module SplitIoClient
|
|
85
88
|
def events_sender
|
86
89
|
EventsSender.new(@events_repository, @config).call
|
87
90
|
end
|
91
|
+
|
92
|
+
# Starts thread which loops constantly and sends impressions count to the Split API
|
93
|
+
def impressions_count_sender
|
94
|
+
ImpressionsCountSender.new(@config, @impression_counter, @impressions_api).call
|
95
|
+
end
|
88
96
|
end
|
89
97
|
end
|
90
98
|
end
|