splitclient-rb 3.1.0.pre.rc7 → 3.1.0.pre.rc8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -8
- data/lib/cache/adapters/memory_adapter.rb +3 -92
- data/lib/cache/adapters/memory_adapters/map_adapter.rb +121 -0
- data/lib/cache/adapters/memory_adapters/sized_queue_adapter.rb +38 -0
- data/lib/cache/adapters/redis_adapter.rb +18 -0
- data/lib/cache/repositories/impressions/memory_repository.rb +35 -0
- data/lib/cache/repositories/impressions/redis_repository.rb +47 -0
- data/lib/cache/repositories/impressions_repository.rb +20 -0
- data/lib/cache/repositories/segments_repository.rb +1 -0
- data/lib/engine/parser/split_adapter.rb +78 -44
- data/lib/splitclient-rb.rb +5 -1
- data/lib/splitclient-rb/split_config.rb +21 -3
- data/lib/splitclient-rb/split_factory.rb +10 -5
- data/lib/splitclient-rb/version.rb +1 -1
- metadata +7 -3
- data/lib/engine/impressions/impressions.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1309484de3281c553d149dedc90df953df886f6
|
4
|
+
data.tar.gz: 3f4cc3a188da4d9c3167465b4d3e9886bc92320f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d28027d4c955132b5064c331fb2d7a50104a8e04b5d7abc86bd102731324050572b021b02a4d5e384d2101e6c37351f158c50660706d26125d8c8619b45cb5c
|
7
|
+
data.tar.gz: fe822d1c1f3395e0f8c48fed3dbee881dd8d7a9b15964bc5340d300c0cf2494fdba7406b6fe79c8f409955cc8fc02793374e26026cdd3ee34795c8aecb3a4f42
|
data/README.md
CHANGED
@@ -104,7 +104,7 @@ The following values can be customized:
|
|
104
104
|
|
105
105
|
**impressions_refresh_rate** : The SDK sends information on who got what treatment at what time back to Split servers to power analytics. This parameter controls how often this data is sent to Split servers in seconds
|
106
106
|
|
107
|
-
**impressions_queue_size** : The size of the impressions queue
|
107
|
+
**impressions_queue_size** : The size of the impressions queue in case of `cache_adapter == :memory` and the size impressions batch to be fetched from Redis in case of `cache_adapter == :redis`. Use `-1` to disable impressions.
|
108
108
|
|
109
109
|
*default value* = `60`
|
110
110
|
|
@@ -130,7 +130,7 @@ The following values can be customized:
|
|
130
130
|
|
131
131
|
#### Cache adapter
|
132
132
|
|
133
|
-
The SDK needs some container to store
|
133
|
+
The SDK needs some container to store data, i.e. splits/segments/impressions. By default it will store everything in the application's memory, but you can also use Redis.
|
134
134
|
|
135
135
|
To use Redis, you have to include `redis-rb` in your app's Gemfile.
|
136
136
|
|
@@ -163,7 +163,7 @@ options = {
|
|
163
163
|
logger: Logger.new('logfile.log'),
|
164
164
|
block_until_ready: 5,
|
165
165
|
cache_adapter: :redis,
|
166
|
-
mode: :
|
166
|
+
mode: :standalone,
|
167
167
|
redis_url: 'redis://127.0.0.1:6379/0'
|
168
168
|
}
|
169
169
|
begin
|
@@ -172,6 +172,10 @@ rescue SplitIoClient::SDKBlockerTimeoutExpiredException
|
|
172
172
|
# Some arbitrary actions
|
173
173
|
end
|
174
174
|
```
|
175
|
+
|
176
|
+
#### IMPORTANT
|
177
|
+
For now, SDK does not support both `producer` mode and `block_until_ready`. You must either run SDK in `standalone` mode, or do not use `block_until_ready` option.
|
178
|
+
|
175
179
|
This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic.
|
176
180
|
|
177
181
|
### Execution
|
@@ -230,28 +234,28 @@ And you should get something like this:
|
|
230
234
|
|
231
235
|
```ruby
|
232
236
|
[
|
233
|
-
{
|
237
|
+
{
|
234
238
|
name: 'some_feature',
|
235
239
|
traffic_type_name: nil,
|
236
240
|
killed: false,
|
237
241
|
treatments: nil,
|
238
242
|
change_number: 1469134003507
|
239
243
|
},
|
240
|
-
{
|
244
|
+
{
|
241
245
|
name: 'another_feature',
|
242
246
|
traffic_type_name: nil,
|
243
247
|
killed: false,
|
244
248
|
treatments: nil,
|
245
249
|
change_number: 1469134003414
|
246
250
|
},
|
247
|
-
{
|
251
|
+
{
|
248
252
|
name: 'even_more_features',
|
249
253
|
traffic_type_name: nil,
|
250
254
|
killed: false,
|
251
255
|
treatments: nil,
|
252
256
|
change_number: 1469133991063
|
253
257
|
},
|
254
|
-
{
|
258
|
+
{
|
255
259
|
name: 'yet_another_feature',
|
256
260
|
traffic_type_name: nil,
|
257
261
|
killed: false,
|
@@ -297,7 +301,7 @@ SDK can be ran in `producer` mode both in the scope of the application (e.g. as
|
|
297
301
|
bundle binstubs splitclient-rb
|
298
302
|
```
|
299
303
|
|
300
|
-
- Run the executable provided by the SDK:
|
304
|
+
- Run the executable provided by the SDK:
|
301
305
|
```ruby
|
302
306
|
bundle exec bin/splitio -c ~/path/to/config/file.yml
|
303
307
|
```
|
@@ -3,98 +3,9 @@ require 'concurrent'
|
|
3
3
|
module SplitIoClient
|
4
4
|
module Cache
|
5
5
|
module Adapters
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
# Map
|
12
|
-
def initialize_map(key)
|
13
|
-
@map[key] = Concurrent::Map.new
|
14
|
-
end
|
15
|
-
|
16
|
-
def add_to_map(key, field, value)
|
17
|
-
@map[key].put(field, value)
|
18
|
-
end
|
19
|
-
|
20
|
-
def find_in_map(key, field)
|
21
|
-
return nil if @map[key].nil?
|
22
|
-
|
23
|
-
@map[key].get(field)
|
24
|
-
end
|
25
|
-
|
26
|
-
def delete_from_map(key, fields)
|
27
|
-
if fields.is_a? Array
|
28
|
-
fields.each { |field| @map[key].delete(field) }
|
29
|
-
else
|
30
|
-
@map[key].delete(field)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def in_map?(key, field)
|
35
|
-
return false if @map[key].nil?
|
36
|
-
|
37
|
-
@map[key].key?(field)
|
38
|
-
end
|
39
|
-
|
40
|
-
def map_keys(key)
|
41
|
-
@map[key].keys
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_map(key)
|
45
|
-
@map[key]
|
46
|
-
end
|
47
|
-
|
48
|
-
# String
|
49
|
-
def string(key)
|
50
|
-
@map[key]
|
51
|
-
end
|
52
|
-
|
53
|
-
def set_string(key, str)
|
54
|
-
@map[key] = str
|
55
|
-
end
|
56
|
-
|
57
|
-
def find_strings_by_prefix(prefix)
|
58
|
-
@map.keys.select { |str| str.start_with? prefix }
|
59
|
-
end
|
60
|
-
|
61
|
-
def multiple_strings(keys)
|
62
|
-
keys.each_with_object({}) do |key, memo|
|
63
|
-
memo[key] = string(key)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Bool
|
68
|
-
def set_bool(key, val)
|
69
|
-
@map[key] = val
|
70
|
-
end
|
71
|
-
|
72
|
-
def bool(key)
|
73
|
-
@map[key]
|
74
|
-
end
|
75
|
-
|
76
|
-
# Set
|
77
|
-
alias_method :initialize_set, :initialize_map
|
78
|
-
alias_method :get_set, :map_keys
|
79
|
-
alias_method :delete_from_set, :delete_from_map
|
80
|
-
alias_method :in_set?, :in_map?
|
81
|
-
|
82
|
-
def add_to_set(key, values)
|
83
|
-
if values.is_a? Array
|
84
|
-
values.each { |value| add_to_map(key, value, 1) }
|
85
|
-
else
|
86
|
-
add_to_map(key, values, 1)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# General
|
91
|
-
def exists?(key)
|
92
|
-
!@map[key].nil?
|
93
|
-
end
|
94
|
-
|
95
|
-
def delete(key)
|
96
|
-
@map.delete(key)
|
97
|
-
end
|
6
|
+
# Memory adapter can have different implementations, this class is used as a delegator to
|
7
|
+
# this implementations
|
8
|
+
class MemoryAdapter < SimpleDelegator
|
98
9
|
end
|
99
10
|
end
|
100
11
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module Cache
|
5
|
+
module Adapters
|
6
|
+
module MemoryAdapters
|
7
|
+
# Memory adapter implementation, which stores everything inside thread-safe Map
|
8
|
+
class MapAdapter
|
9
|
+
def initialize(_ = nil)
|
10
|
+
@map = Concurrent::Map.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Map
|
14
|
+
def initialize_map(key)
|
15
|
+
@map[key] = Concurrent::Map.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_to_map(key, field, value)
|
19
|
+
initialize_map(key) unless @map[key]
|
20
|
+
|
21
|
+
@map[key].put(field, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_in_map(key, field)
|
25
|
+
return nil if @map[key].nil?
|
26
|
+
|
27
|
+
@map[key].get(field)
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_from_map(key, fields)
|
31
|
+
if fields.is_a? Array
|
32
|
+
fields.each { |field| @map[key].delete(field) }
|
33
|
+
else
|
34
|
+
@map[key].delete(field)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def in_map?(key, field)
|
39
|
+
return false if @map[key].nil?
|
40
|
+
|
41
|
+
@map[key].key?(field)
|
42
|
+
end
|
43
|
+
|
44
|
+
def map_keys(key)
|
45
|
+
@map[key].keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_map(key)
|
49
|
+
@map[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
# String
|
53
|
+
def string(key)
|
54
|
+
@map[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_string(key, str)
|
58
|
+
@map[key] = str
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_strings_by_prefix(prefix)
|
62
|
+
@map.keys.select { |str| str.start_with? prefix }
|
63
|
+
end
|
64
|
+
|
65
|
+
def multiple_strings(keys)
|
66
|
+
keys.each_with_object({}) do |key, memo|
|
67
|
+
memo[key] = string(key)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Bool
|
72
|
+
def set_bool(key, val)
|
73
|
+
@map[key] = val
|
74
|
+
end
|
75
|
+
|
76
|
+
def bool(key)
|
77
|
+
@map[key]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Set
|
81
|
+
alias_method :initialize_set, :initialize_map
|
82
|
+
alias_method :get_set, :map_keys
|
83
|
+
alias_method :delete_from_set, :delete_from_map
|
84
|
+
alias_method :in_set?, :in_map?
|
85
|
+
alias_method :find_sets_by_prefix, :find_strings_by_prefix
|
86
|
+
|
87
|
+
def add_to_set(key, values)
|
88
|
+
if values.is_a? Array
|
89
|
+
values.each { |value| add_to_map(key, value, 1) }
|
90
|
+
else
|
91
|
+
add_to_map(key, values, 1)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_all_from_set(key)
|
96
|
+
@map[key].keys
|
97
|
+
end
|
98
|
+
|
99
|
+
def union_sets(set_keys)
|
100
|
+
set_keys.each_with_object([]) do |key, memo|
|
101
|
+
memo << get_set(key)
|
102
|
+
end.flatten
|
103
|
+
end
|
104
|
+
|
105
|
+
# General
|
106
|
+
def exists?(key)
|
107
|
+
!@map[key].nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete(key)
|
111
|
+
if key.is_a? Array
|
112
|
+
key.each { |k| @map.delete(k) }
|
113
|
+
else
|
114
|
+
@map.delete(key)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Adapters
|
4
|
+
module MemoryAdapters
|
5
|
+
# Memory adapter implementation, which stores everything inside sized queue
|
6
|
+
class SizedQueueAdapter
|
7
|
+
def initialize(size)
|
8
|
+
@size = size
|
9
|
+
@queue = SizedQueue.new(queue_size)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Adds data to queue in non-blocking mode
|
13
|
+
def add_to_queue(data)
|
14
|
+
# IMPORTANT: this requires Ruby >= 2.2, consider changing implementation
|
15
|
+
@queue.push(data, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get all items from the queue
|
19
|
+
def clear
|
20
|
+
items = []
|
21
|
+
|
22
|
+
loop { items << @queue.pop(true) }
|
23
|
+
rescue ThreadError
|
24
|
+
# Last queue item reached
|
25
|
+
items
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Return 1 to prevent an exception
|
31
|
+
def queue_size
|
32
|
+
@size <= 0 ? 1 : @size
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
module SplitIoClient
|
5
5
|
module Cache
|
6
6
|
module Adapters
|
7
|
+
# Redis adapter used to provide interface to Redis
|
7
8
|
class RedisAdapter
|
8
9
|
def initialize(redis_url)
|
9
10
|
connection = redis_url.is_a?(Hash) ? redis_url : { url: redis_url }
|
@@ -68,6 +69,7 @@ module SplitIoClient
|
|
68
69
|
|
69
70
|
# Set
|
70
71
|
alias_method :initialize_set, :initialize_map
|
72
|
+
alias_method :find_sets_by_prefix, :find_strings_by_prefix
|
71
73
|
|
72
74
|
def add_to_set(key, val)
|
73
75
|
@redis.sadd(key, val)
|
@@ -85,12 +87,28 @@ module SplitIoClient
|
|
85
87
|
@redis.sismember(key, val)
|
86
88
|
end
|
87
89
|
|
90
|
+
def get_all_from_set(key)
|
91
|
+
@redis.smembers(key)
|
92
|
+
end
|
93
|
+
|
94
|
+
def union_sets(set_keys)
|
95
|
+
return [] if set_keys == []
|
96
|
+
|
97
|
+
@redis.sunion(set_keys)
|
98
|
+
end
|
99
|
+
|
100
|
+
def random_set_elements(key, count)
|
101
|
+
@redis.srandmember(key, count)
|
102
|
+
end
|
103
|
+
|
88
104
|
# General
|
89
105
|
def exists?(key)
|
90
106
|
@redis.exists(key)
|
91
107
|
end
|
92
108
|
|
93
109
|
def delete(key)
|
110
|
+
return nil if key == []
|
111
|
+
|
94
112
|
@redis.del(key)
|
95
113
|
end
|
96
114
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Repositories
|
4
|
+
module Impressions
|
5
|
+
class MemoryRepository
|
6
|
+
def initialize(adapter, config)
|
7
|
+
@adapter = adapter
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
# Store impression data in the selected adapter
|
12
|
+
def add(split_name, data)
|
13
|
+
@adapter.add_to_queue(feature: split_name, impressions: data)
|
14
|
+
rescue ThreadError # queue is full
|
15
|
+
if random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time
|
16
|
+
@config.logger.warn("Dropping impressions. Current size is #{@config.impressions_queue_size}. " \
|
17
|
+
"Consider increasing impressions_queue_size")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get everything from the queue and leave it empty
|
22
|
+
def clear
|
23
|
+
@adapter.clear
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def random_sampler
|
29
|
+
@random_sampler ||= Random.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Repositories
|
4
|
+
module Impressions
|
5
|
+
class RedisRepository < Repository
|
6
|
+
def initialize(adapter, config)
|
7
|
+
@adapter = adapter
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
# Store impression data in Redis
|
12
|
+
def add(split_name, data)
|
13
|
+
@adapter.add_to_set(
|
14
|
+
namespace_key("impressions.#{split_name}"), data.merge(split_name: split_name).to_json
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get random impressions from redis in batches of size @config.impressions_queue_size,
|
19
|
+
# delete fetched impressions afterwards
|
20
|
+
def clear
|
21
|
+
impressions = impression_keys.each_with_object([]) do |key, memo|
|
22
|
+
@adapter.random_set_elements(key, @config.impressions_queue_size).each do |impression|
|
23
|
+
parsed_impression = JSON.parse(impression)
|
24
|
+
|
25
|
+
memo << {
|
26
|
+
feature: parsed_impression['split_name'],
|
27
|
+
impressions: parsed_impression.reject { |k, _| k == 'split_name' }
|
28
|
+
}
|
29
|
+
|
30
|
+
@adapter.delete_from_set(key, impression)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
impressions
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Get all sets by prefix
|
40
|
+
def impression_keys
|
41
|
+
@adapter.find_sets_by_prefix(namespace_key('impressions.'))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Cache
|
3
|
+
module Repositories
|
4
|
+
# Repository which forwards impressions interface to the selected adapter
|
5
|
+
class ImpressionsRepository < Repository
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@adapter, :add, :clear, :empty?
|
8
|
+
|
9
|
+
def initialize(adapter, config)
|
10
|
+
@adapter = case adapter.class.to_s
|
11
|
+
when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
|
12
|
+
Repositories::Impressions::MemoryRepository.new(adapter, config)
|
13
|
+
when 'SplitIoClient::Cache::Adapters::RedisAdapter'
|
14
|
+
Repositories::Impressions::RedisRepository.new(adapter, config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -13,10 +13,6 @@ module SplitIoClient
|
|
13
13
|
# also, uses safe threads to execute fetches and post give the time execution values from the config
|
14
14
|
#
|
15
15
|
class SplitAdapter < NoMethodError
|
16
|
-
#
|
17
|
-
# handler for impressions
|
18
|
-
attr_reader :impressions
|
19
|
-
|
20
16
|
#
|
21
17
|
# handler for metrics
|
22
18
|
attr_reader :metrics
|
@@ -31,7 +27,7 @@ module SplitIoClient
|
|
31
27
|
|
32
28
|
attr_reader :impressions_producer
|
33
29
|
|
34
|
-
attr_reader :splits_repository, :segments_repository
|
30
|
+
attr_reader :splits_repository, :segments_repository, :impressions_repository
|
35
31
|
|
36
32
|
#
|
37
33
|
# Creates a new split api adapter instance that consumes split api endpoints
|
@@ -39,14 +35,14 @@ module SplitIoClient
|
|
39
35
|
# @param api_key [String] the API key for your split account
|
40
36
|
#
|
41
37
|
# @return [SplitIoClient] split.io client instance
|
42
|
-
def initialize(api_key, config, splits_repository, segments_repository, sdk_blocker)
|
38
|
+
def initialize(api_key, config, splits_repository, segments_repository, impressions_repository, sdk_blocker)
|
43
39
|
@api_key = api_key
|
44
40
|
@config = config
|
45
|
-
@impressions = Impressions.new(@config)
|
46
41
|
@metrics = Metrics.new(100)
|
47
42
|
|
48
43
|
@splits_repository = splits_repository
|
49
44
|
@segments_repository = segments_repository
|
45
|
+
@impressions_repository = impressions_repository
|
50
46
|
|
51
47
|
@sdk_blocker = sdk_blocker
|
52
48
|
|
@@ -67,10 +63,10 @@ module SplitIoClient
|
|
67
63
|
impressions_sender
|
68
64
|
when :consumer
|
69
65
|
metrics_sender
|
70
|
-
impressions_sender
|
71
66
|
when :producer
|
72
67
|
split_store
|
73
68
|
segment_store
|
69
|
+
impressions_sender
|
74
70
|
|
75
71
|
sleep unless ENV['SPLITCLIENT_ENV'] == 'test'
|
76
72
|
end
|
@@ -107,7 +103,13 @@ module SplitIoClient
|
|
107
103
|
req.body = param.to_json
|
108
104
|
req.options.timeout = @config.read_timeout
|
109
105
|
req.options.open_timeout = @config.connection_timeout
|
110
|
-
|
106
|
+
|
107
|
+
if @config.transport_debug_enabled
|
108
|
+
@config.logger.debug("POST #{@config.events_uri + path} #{req.body}")
|
109
|
+
elsif @config.debug_enabled
|
110
|
+
@config.logger.debug("POST #{@config.events_uri + path}")
|
111
|
+
end
|
112
|
+
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
@@ -145,6 +147,14 @@ module SplitIoClient
|
|
145
147
|
end
|
146
148
|
|
147
149
|
def impressions_sender
|
150
|
+
# Disable impressions if @config.impressions_queue_size == -1
|
151
|
+
if @config.impressions_queue_size < 0
|
152
|
+
@config.logger.info("Disabling impressions service by config.")
|
153
|
+
return
|
154
|
+
end
|
155
|
+
|
156
|
+
@config.logger.info("Starting impressions service...")
|
157
|
+
|
148
158
|
Thread.new do
|
149
159
|
loop do
|
150
160
|
begin
|
@@ -157,6 +167,7 @@ module SplitIoClient
|
|
157
167
|
end
|
158
168
|
end
|
159
169
|
end
|
170
|
+
@config.logger.info("Started impressions service")
|
160
171
|
end
|
161
172
|
|
162
173
|
#
|
@@ -165,46 +176,69 @@ module SplitIoClient
|
|
165
176
|
#
|
166
177
|
# @return [void]
|
167
178
|
def post_impressions
|
168
|
-
|
169
|
-
|
179
|
+
impressions = impressions_array
|
180
|
+
|
181
|
+
if impressions.empty?
|
182
|
+
@config.logger.debug('No impressions to report') if @config.debug_enabled
|
183
|
+
return
|
184
|
+
end
|
185
|
+
|
186
|
+
res = post_api('/testImpressions/bulk', impressions)
|
187
|
+
if res.status / 100 != 2
|
188
|
+
@config.logger.error("Unexpected status code while posting impressions: #{res.status}")
|
170
189
|
else
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
filtered = []
|
175
|
-
keys_treatments_seen = []
|
176
|
-
|
177
|
-
impressions = i[:impressions]
|
178
|
-
impressions.each do |imp|
|
179
|
-
if keys_treatments_seen.include?("#{imp.key}:#{imp.treatment}")
|
180
|
-
next
|
181
|
-
end
|
182
|
-
keys_treatments_seen << "#{imp.key}:#{imp.treatment}"
|
183
|
-
filtered << imp
|
184
|
-
end
|
190
|
+
@config.logger.debug("Impressions reported: #{calculate_tot_impressions(impressions)}") if @config.debug_enabled
|
191
|
+
end
|
192
|
+
end
|
185
193
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
194
|
+
def calculate_tot_impressions(impressions = nil)
|
195
|
+
tot = 0
|
196
|
+
return tot if impressions.nil?
|
197
|
+
impressions.each do |test_impression|
|
198
|
+
tot += test_impression[:keyImpressions].length
|
199
|
+
end
|
200
|
+
tot
|
201
|
+
end
|
191
202
|
|
192
|
-
|
193
|
-
|
194
|
-
|
203
|
+
# REFACTOR
|
204
|
+
def impressions_array(impressions = nil)
|
205
|
+
impressions_data = impressions || @impressions_repository
|
206
|
+
popped_impressions = impressions_data.clear
|
207
|
+
test_impression_array = []
|
208
|
+
filtered_impressions = []
|
209
|
+
keys_treatments_seen = []
|
195
210
|
|
196
|
-
|
197
|
-
|
198
|
-
|
211
|
+
if !popped_impressions.empty?
|
212
|
+
popped_impressions.each do |item|
|
213
|
+
item_hash = "#{item[:impressions]['key_name']}:#{item[:impressions]['treatment']}"
|
214
|
+
|
215
|
+
next if keys_treatments_seen.include?(item_hash)
|
216
|
+
|
217
|
+
keys_treatments_seen << item_hash
|
218
|
+
filtered_impressions << item
|
199
219
|
end
|
200
220
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
221
|
+
return [] unless filtered_impressions
|
222
|
+
|
223
|
+
features = filtered_impressions.map { |i| i[:feature] }.uniq
|
224
|
+
test_impression_array = features.each_with_object([]) do |feature, memo|
|
225
|
+
current_impressions = filtered_impressions.select { |i| i[:feature] == feature }
|
226
|
+
current_impressions.map! do |i|
|
227
|
+
{
|
228
|
+
keyName: i[:impressions]['key_name'],
|
229
|
+
treatment: i[:impressions]['treatment'],
|
230
|
+
time: i[:impressions]['time']
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
memo << {
|
235
|
+
testName: feature,
|
236
|
+
keyImpressions: current_impressions
|
237
|
+
}
|
206
238
|
end
|
207
239
|
end
|
240
|
+
|
241
|
+
test_impression_array
|
208
242
|
end
|
209
243
|
|
210
244
|
#
|
@@ -224,7 +258,7 @@ module SplitIoClient
|
|
224
258
|
if res.status / 100 != 2
|
225
259
|
@config.logger.error("Unexpected status code while posting time metrics: #{res.status}")
|
226
260
|
else
|
227
|
-
@config.logger.debug("Metric time reported: #{metrics_time}") if @config.debug_enabled
|
261
|
+
@config.logger.debug("Metric time reported: #{metrics_time.size()}") if @config.debug_enabled
|
228
262
|
end
|
229
263
|
end
|
230
264
|
end
|
@@ -240,7 +274,7 @@ module SplitIoClient
|
|
240
274
|
if res.status / 100 != 2
|
241
275
|
@config.logger.error("Unexpected status code while posting count metrics: #{res.status}")
|
242
276
|
else
|
243
|
-
@config.logger.debug("Metric counts reported: #{metrics_count}") if @config.debug_enabled
|
277
|
+
@config.logger.debug("Metric counts reported: #{metrics_count.size()}") if @config.debug_enabled
|
244
278
|
end
|
245
279
|
end
|
246
280
|
end
|
@@ -256,7 +290,7 @@ module SplitIoClient
|
|
256
290
|
if res.status / 100 != 2
|
257
291
|
@config.logger.error("Unexpected status code while posting gauge metrics: #{res.status}")
|
258
292
|
else
|
259
|
-
@config.logger.debug("Metric gauge reported: #{metrics_gauge}") if @config.debug_enabled
|
293
|
+
@config.logger.debug("Metric gauge reported: #{metrics_gauge.size()}") if @config.debug_enabled
|
260
294
|
end
|
261
295
|
end
|
262
296
|
end
|
data/lib/splitclient-rb.rb
CHANGED
@@ -5,11 +5,16 @@ require 'splitclient-rb/localhost_split_factory_builder'
|
|
5
5
|
require 'splitclient-rb/localhost_split_factory'
|
6
6
|
require 'splitclient-rb/split_config'
|
7
7
|
require 'exceptions/sdk_blocker_timeout_expired_exception'
|
8
|
+
require 'cache/adapters/memory_adapters/map_adapter'
|
9
|
+
require 'cache/adapters/memory_adapters/sized_queue_adapter'
|
8
10
|
require 'cache/adapters/memory_adapter'
|
9
11
|
require 'cache/adapters/redis_adapter'
|
10
12
|
require 'cache/repositories/repository'
|
11
13
|
require 'cache/repositories/segments_repository'
|
12
14
|
require 'cache/repositories/splits_repository'
|
15
|
+
require 'cache/repositories/impressions_repository'
|
16
|
+
require 'cache/repositories/impressions/memory_repository'
|
17
|
+
require 'cache/repositories/impressions/redis_repository'
|
13
18
|
require 'cache/stores/sdk_blocker'
|
14
19
|
require 'cache/stores/segment_store'
|
15
20
|
require 'cache/stores/split_store'
|
@@ -32,7 +37,6 @@ require 'engine/matchers/greater_than_or_equal_to_matcher'
|
|
32
37
|
require 'engine/matchers/less_than_or_equal_to_matcher'
|
33
38
|
require 'engine/matchers/between_matcher'
|
34
39
|
require 'engine/evaluator/splitter'
|
35
|
-
require 'engine/impressions/impressions'
|
36
40
|
require 'engine/metrics/metrics'
|
37
41
|
require 'engine/metrics/binary_search_latency_tracker'
|
38
42
|
require 'engine/models/split'
|
@@ -29,14 +29,21 @@ module SplitIoClient
|
|
29
29
|
@events_uri = (opts[:events_uri] || SplitConfig.default_events_uri).chomp('/')
|
30
30
|
@mode = opts[:mode] || SplitConfig.default_mode
|
31
31
|
@redis_url = opts[:redis_url] || SplitConfig.default_redis_url
|
32
|
-
@cache_adapter = SplitConfig.init_cache_adapter(
|
32
|
+
@cache_adapter = SplitConfig.init_cache_adapter(
|
33
|
+
opts[:cache_adapter] || SplitConfig.default_cache_adapter, :map_adapter, @redis_url, false
|
34
|
+
)
|
33
35
|
@connection_timeout = opts[:connection_timeout] || SplitConfig.default_connection_timeout
|
34
36
|
@read_timeout = opts[:read_timeout] || SplitConfig.default_read_timeout
|
35
37
|
@features_refresh_rate = opts[:features_refresh_rate] || SplitConfig.default_features_refresh_rate
|
36
38
|
@segments_refresh_rate = opts[:segments_refresh_rate] || SplitConfig.default_segments_refresh_rate
|
37
39
|
@metrics_refresh_rate = opts[:metrics_refresh_rate] || SplitConfig.default_metrics_refresh_rate
|
40
|
+
|
38
41
|
@impressions_refresh_rate = opts[:impressions_refresh_rate] || SplitConfig.default_impressions_refresh_rate
|
39
42
|
@impressions_queue_size = opts[:impressions_queue_size] || SplitConfig.default_impressions_queue_size
|
43
|
+
@impressions_adapter = SplitConfig.init_cache_adapter(
|
44
|
+
opts[:cache_adapter] || SplitConfig.default_cache_adapter, :sized_queue_adapter, @redis_url, @impressions_queue_size
|
45
|
+
)
|
46
|
+
|
40
47
|
@logger = opts[:logger] || SplitConfig.default_logger
|
41
48
|
@debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
|
42
49
|
@transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
|
@@ -76,6 +83,12 @@ module SplitIoClient
|
|
76
83
|
# @return [Object] Cache adapter instance
|
77
84
|
attr_reader :cache_adapter
|
78
85
|
|
86
|
+
#
|
87
|
+
# The cache adapter to store impressions in
|
88
|
+
#
|
89
|
+
# @return [Object] Impressions adapter instance
|
90
|
+
attr_reader :impressions_adapter
|
91
|
+
|
79
92
|
#
|
80
93
|
# The connection timeout for network connections in seconds.
|
81
94
|
#
|
@@ -143,10 +156,15 @@ module SplitIoClient
|
|
143
156
|
'https://events.split.io/api/'
|
144
157
|
end
|
145
158
|
|
146
|
-
def self.init_cache_adapter(adapter, redis_url)
|
159
|
+
def self.init_cache_adapter(adapter, data_structure, redis_url = nil, impressions_queue_size = nil)
|
147
160
|
case adapter
|
148
161
|
when :memory
|
149
|
-
|
162
|
+
# takes :memory_adapter (symbol) and returns MemoryAdapter (string)
|
163
|
+
adapter = SplitIoClient::Cache::Adapters::MemoryAdapters.const_get(
|
164
|
+
data_structure.to_s.split('_').collect(&:capitalize).join
|
165
|
+
).new(impressions_queue_size)
|
166
|
+
|
167
|
+
SplitIoClient::Cache::Adapters::MemoryAdapter.new(adapter)
|
150
168
|
when :redis
|
151
169
|
SplitIoClient::Cache::Adapters::RedisAdapter.new(redis_url)
|
152
170
|
end
|
@@ -131,7 +131,7 @@ module SplitIoClient
|
|
131
131
|
# @param api_key [String] the API key for your split account
|
132
132
|
#
|
133
133
|
# @return [SplitIoClient] split.io client instance
|
134
|
-
def initialize(api_key, config = {}, adapter = nil, localhost_mode = false, splits_repository, segments_repository)
|
134
|
+
def initialize(api_key, config = {}, adapter = nil, localhost_mode = false, splits_repository, segments_repository, impressions_repository)
|
135
135
|
@localhost_mode = localhost_mode
|
136
136
|
@localhost_mode_features = []
|
137
137
|
|
@@ -139,6 +139,7 @@ module SplitIoClient
|
|
139
139
|
|
140
140
|
@splits_repository = splits_repository
|
141
141
|
@segments_repository = segments_repository
|
142
|
+
@impressions_repository = impressions_repository
|
142
143
|
|
143
144
|
if api_key == LOCALHOST_MODE
|
144
145
|
@localhost_mode = true
|
@@ -198,8 +199,11 @@ module SplitIoClient
|
|
198
199
|
result = result.nil? ? Treatments::CONTROL : result
|
199
200
|
|
200
201
|
begin
|
201
|
-
@adapter.impressions.log(matching_key, split_name, result, (Time.now.to_f * 1000.0))
|
202
202
|
latency = (Time.now - start) * 1000.0
|
203
|
+
if @config.impressions_queue_size > 0
|
204
|
+
# Disable impressions if @config.impressions_queue_size == -1
|
205
|
+
@impressions_repository.add(split_name, 'key_name' => matching_key, 'treatment' => result, 'time' => (Time.now.to_f * 1000.0).to_i)
|
206
|
+
end
|
203
207
|
|
204
208
|
# Measure
|
205
209
|
@adapter.metrics.time("sdk.get_treatment", latency)
|
@@ -226,7 +230,7 @@ module SplitIoClient
|
|
226
230
|
#
|
227
231
|
# @return [string] version value for this sdk
|
228
232
|
def self.sdk_version
|
229
|
-
'
|
233
|
+
'ruby-'+SplitIoClient::VERSION
|
230
234
|
end
|
231
235
|
|
232
236
|
private
|
@@ -275,9 +279,10 @@ module SplitIoClient
|
|
275
279
|
@cache_adapter = @config.cache_adapter
|
276
280
|
@splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(@cache_adapter)
|
277
281
|
@segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(@cache_adapter)
|
282
|
+
@impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(@config.impressions_adapter, @config)
|
278
283
|
@sdk_blocker = SplitIoClient::Cache::Stores::SDKBlocker.new(@config)
|
279
284
|
@adapter = api_key != 'localhost' \
|
280
|
-
? SplitAdapter.new(api_key, @config, @splits_repository, @segments_repository, @sdk_blocker)
|
285
|
+
? SplitAdapter.new(api_key, @config, @splits_repository, @segments_repository, @impressions_repository, @sdk_blocker)
|
281
286
|
: nil
|
282
287
|
@localhost_mode = api_key == 'localhost'
|
283
288
|
|
@@ -304,7 +309,7 @@ module SplitIoClient
|
|
304
309
|
attr_reader :adapter
|
305
310
|
|
306
311
|
def init_client
|
307
|
-
SplitClient.new(@api_key, @config, @adapter, @localhost_mode, @splits_repository, @segments_repository)
|
312
|
+
SplitClient.new(@api_key, @config, @adapter, @localhost_mode, @splits_repository, @segments_repository, @impressions_repository)
|
308
313
|
end
|
309
314
|
|
310
315
|
def init_manager
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.0.pre.
|
4
|
+
version: 3.1.0.pre.rc8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Split Software
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -251,7 +251,12 @@ files:
|
|
251
251
|
- Rakefile
|
252
252
|
- exe/splitio
|
253
253
|
- lib/cache/adapters/memory_adapter.rb
|
254
|
+
- lib/cache/adapters/memory_adapters/map_adapter.rb
|
255
|
+
- lib/cache/adapters/memory_adapters/sized_queue_adapter.rb
|
254
256
|
- lib/cache/adapters/redis_adapter.rb
|
257
|
+
- lib/cache/repositories/impressions/memory_repository.rb
|
258
|
+
- lib/cache/repositories/impressions/redis_repository.rb
|
259
|
+
- lib/cache/repositories/impressions_repository.rb
|
255
260
|
- lib/cache/repositories/repository.rb
|
256
261
|
- lib/cache/repositories/segments_repository.rb
|
257
262
|
- lib/cache/repositories/splits_repository.rb
|
@@ -262,7 +267,6 @@ files:
|
|
262
267
|
- lib/engine/api/segments.rb
|
263
268
|
- lib/engine/api/splits.rb
|
264
269
|
- lib/engine/evaluator/splitter.rb
|
265
|
-
- lib/engine/impressions/impressions.rb
|
266
270
|
- lib/engine/matchers/all_keys_matcher.rb
|
267
271
|
- lib/engine/matchers/between_matcher.rb
|
268
272
|
- lib/engine/matchers/combiners.rb
|
@@ -1,88 +0,0 @@
|
|
1
|
-
module SplitIoClient
|
2
|
-
|
3
|
-
#
|
4
|
-
# class to manage cached impressions
|
5
|
-
#
|
6
|
-
class Impressions < NoMethodError
|
7
|
-
|
8
|
-
# the queue of cached impression values
|
9
|
-
#
|
10
|
-
# @return [object] array of impressions
|
11
|
-
attr_accessor :queue
|
12
|
-
|
13
|
-
# max number of cached entries for impressions
|
14
|
-
#
|
15
|
-
# @return [int] max numbre of entries
|
16
|
-
attr_accessor :max_number_of_keys
|
17
|
-
|
18
|
-
#
|
19
|
-
# initializes the class
|
20
|
-
#
|
21
|
-
# @param config [SplitConfig] the config object
|
22
|
-
def initialize(config)
|
23
|
-
@config = config
|
24
|
-
@queue = SizedQueue.new(config.impressions_queue_size <= 0? 1 : config.impressions_queue_size)
|
25
|
-
@max_number_of_keys = config.impressions_queue_size
|
26
|
-
end
|
27
|
-
|
28
|
-
#
|
29
|
-
# generates a new entry for impressions list
|
30
|
-
#
|
31
|
-
# @param id [string] user key
|
32
|
-
# @param feature [string] feature name
|
33
|
-
# @param treatment [string] treatment value
|
34
|
-
# @param time [time] time value in milisenconds
|
35
|
-
#
|
36
|
-
# @return void
|
37
|
-
def log(id, feature, treatment, time)
|
38
|
-
return if @max_number_of_keys <= 0 # shortcut to desable impressions
|
39
|
-
impressions = KeyImpressions.new(id, treatment, time)
|
40
|
-
begin
|
41
|
-
@queue.push( {feature: feature, impressions: impressions} , true ) # don't wait if queue is full
|
42
|
-
rescue ThreadError
|
43
|
-
@random_sampler ||= Random.new
|
44
|
-
if @random_sampler.rand(1..1000) <= 2 # log only 0.2 % of the time.
|
45
|
-
@config.logger.warn("Dropping impressions. Current size is #{@max_number_of_keys}. Consider increasing impressions_queue_size")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
#
|
51
|
-
# clears the impressions queue
|
52
|
-
#
|
53
|
-
# @returns void
|
54
|
-
def clear
|
55
|
-
popped_impressions = []
|
56
|
-
begin
|
57
|
-
loop do
|
58
|
-
impression_element = @queue.pop(true)
|
59
|
-
feature_hash = popped_impressions.find { |i| i[:feature] == impression_element[:feature] }
|
60
|
-
if feature_hash.nil?
|
61
|
-
popped_impressions << {feature: impression_element[:feature], impressions: [] << impression_element[:impressions]}
|
62
|
-
else
|
63
|
-
feature_hash[:impressions] << impression_element[:impressions]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
rescue ThreadError
|
67
|
-
end
|
68
|
-
popped_impressions
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
#
|
74
|
-
# small class to use as DTO for impressions
|
75
|
-
#
|
76
|
-
class KeyImpressions
|
77
|
-
attr_accessor :key
|
78
|
-
attr_accessor :treatment
|
79
|
-
attr_accessor :time
|
80
|
-
|
81
|
-
def initialize(key, treatment, time)
|
82
|
-
@key = key
|
83
|
-
@treatment = treatment
|
84
|
-
@time = time
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|