splitclient-rb 3.1.2 → 3.1.3.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.txt +7 -0
- data/Detailed-README.md +20 -5
- data/exe/splitio +54 -3
- data/lib/cache/adapters/memory_adapters/map_adapter.rb +6 -0
- data/lib/cache/adapters/redis_adapter.rb +6 -1
- data/lib/cache/repositories/impressions/memory_repository.rb +8 -2
- data/lib/cache/repositories/impressions/redis_repository.rb +2 -2
- data/lib/cache/repositories/impressions_repository.rb +3 -0
- data/lib/cache/repositories/metrics_repository.rb +1 -0
- data/lib/cache/repositories/repository.rb +1 -1
- data/lib/cache/repositories/segments_repository.rb +16 -1
- data/lib/cache/repositories/splits_repository.rb +21 -7
- data/lib/cache/senders/impressions_sender.rb +16 -12
- data/lib/cache/senders/metrics_sender.rb +41 -0
- data/lib/cache/stores/sdk_blocker.rb +9 -8
- data/lib/cache/stores/segment_store.rb +1 -1
- data/lib/engine/api/client.rb +15 -2
- data/lib/engine/api/metrics.rb +57 -0
- data/lib/engine/parser/split_adapter.rb +9 -140
- data/lib/engine/parser/split_treatment.rb +7 -5
- data/lib/engine/partitions/treatments.rb +1 -5
- data/lib/splitclient-rb.rb +14 -4
- data/lib/splitclient-rb/clients/localhost_split_client.rb +89 -0
- data/lib/splitclient-rb/clients/split_client.rb +114 -0
- data/lib/splitclient-rb/localhost_split_factory.rb +6 -187
- data/lib/splitclient-rb/localhost_utils.rb +36 -0
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
- data/lib/splitclient-rb/managers/split_manager.rb +77 -0
- data/lib/splitclient-rb/split_config.rb +20 -0
- data/lib/splitclient-rb/split_factory.rb +16 -217
- data/lib/splitclient-rb/split_factory_builder.rb +3 -2
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb_utilitites.rb +24 -19
- data/splitclient-rb.gemspec +1 -1
- data/{splitio.yml → splitio.yml.example} +0 -0
- metadata +26 -19
@@ -26,7 +26,7 @@ module SplitIoClient
|
|
26
26
|
|
27
27
|
def blocked_store
|
28
28
|
loop do
|
29
|
-
next unless @sdk_blocker.
|
29
|
+
next unless @sdk_blocker.splits_repository.ready?
|
30
30
|
|
31
31
|
store_segments
|
32
32
|
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
data/lib/engine/api/client.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'faraday/http_cache'
|
2
|
+
require 'bundler/vendor/net/http/persistent' unless defined?(Net::HTTP)
|
3
|
+
require 'faraday_middleware'
|
4
|
+
|
1
5
|
module SplitIoClient
|
2
6
|
module Api
|
3
7
|
class Client
|
@@ -41,11 +45,20 @@ module SplitIoClient
|
|
41
45
|
def common_headers(api_key, config)
|
42
46
|
{
|
43
47
|
'Authorization' => "Bearer #{api_key}",
|
44
|
-
'SplitSDKVersion' => SplitIoClient::
|
48
|
+
'SplitSDKVersion' => SplitIoClient::SplitConfig.sdk_version,
|
45
49
|
'SplitSDKMachineName' => config.machine_name,
|
46
|
-
'SplitSDKMachineIP' => config.machine_ip
|
50
|
+
'SplitSDKMachineIP' => config.machine_ip,
|
51
|
+
'Referer' => referer
|
47
52
|
}
|
48
53
|
end
|
54
|
+
|
55
|
+
def referer
|
56
|
+
result = SplitIoClient::SplitConfig.sdk_version
|
57
|
+
|
58
|
+
result = "#{result}::#{SplitIoClient::SplitConfig.get_hostname}" unless SplitIoClient::SplitConfig.get_hostname == 'localhost'
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
51
64
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
module Api
|
3
|
+
class Metrics < Client
|
4
|
+
def initialize(api_key, config, metrics_repository)
|
5
|
+
@config = config
|
6
|
+
@api_key = api_key
|
7
|
+
@metrics_repository = metrics_repository
|
8
|
+
end
|
9
|
+
|
10
|
+
def post
|
11
|
+
post_latencies
|
12
|
+
post_counts
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def post_latencies
|
18
|
+
if @metrics_repository.latencies.empty?
|
19
|
+
@config.logger.debug('No latencies to report.') if @config.debug_enabled
|
20
|
+
else
|
21
|
+
@metrics_repository.latencies.each do |name, latencies|
|
22
|
+
metrics_time = { name: name, latencies: latencies }
|
23
|
+
|
24
|
+
result = post_api("#{@config.events_uri}/metrics/time", @config, @api_key, metrics_time)
|
25
|
+
|
26
|
+
if result.status / 100 != 2
|
27
|
+
@config.logger.error("Unexpected status code while posting time metrics: #{result.status}")
|
28
|
+
else
|
29
|
+
@config.logger.debug("Metric time reported: #{metrics_time.size}") if @config.debug_enabled
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@metrics_repository.clear_latencies
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_counts
|
38
|
+
if @metrics_repository.counts.empty?
|
39
|
+
@config.logger.debug('No counts to report.') if @config.debug_enabled
|
40
|
+
else
|
41
|
+
@metrics_repository.counts.each do |name, count|
|
42
|
+
metrics_count = { name: name, delta: count }
|
43
|
+
|
44
|
+
result = post_api("#{@config.events_uri}/metrics/counter", @config, @api_key, metrics_count)
|
45
|
+
|
46
|
+
if result.status / 100 != 2
|
47
|
+
@config.logger.error("Unexpected status code while posting count metrics: #{result.status}")
|
48
|
+
else
|
49
|
+
@config.logger.debug("Metric counts reported: #{metrics_count.size}") if @config.debug_enabled
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@metrics_repository.clear_counts
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,58 +1,37 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'thread'
|
3
|
-
require 'faraday/http_cache'
|
4
|
-
require 'bundler/vendor/net/http/persistent' unless defined?(Net::HTTP)
|
5
|
-
require 'faraday_middleware'
|
6
|
-
|
7
3
|
|
8
4
|
module SplitIoClient
|
9
|
-
|
10
5
|
#
|
11
6
|
# acts as an api adapater to connect to split endpoints
|
12
7
|
# uses a configuration object that can be modified when creating the client instance
|
13
8
|
# also, uses safe threads to execute fetches and post give the time execution values from the config
|
14
9
|
#
|
15
10
|
class SplitAdapter < NoMethodError
|
16
|
-
|
17
|
-
# handler for metrics
|
18
|
-
attr_reader :metrics
|
19
|
-
|
20
|
-
#
|
21
|
-
# handler for parsed splits
|
22
|
-
attr_reader :parsed_splits
|
23
|
-
|
24
|
-
#
|
25
|
-
# handeler for parsed segments
|
26
|
-
attr_reader :parsed_segments
|
27
|
-
|
28
|
-
attr_reader :impressions_producer
|
29
|
-
|
30
|
-
attr_reader :splits_repository, :segments_repository, :impressions_repository
|
11
|
+
attr_reader :splits_repository, :segments_repository, :impressions_repository, :metrics
|
31
12
|
|
32
13
|
#
|
33
14
|
# Creates a new split api adapter instance that consumes split api endpoints
|
34
15
|
#
|
35
16
|
# @param api_key [String] the API key for your split account
|
17
|
+
# @param config [SplitConfig] SplitConfig instance
|
18
|
+
# @param splits_repository [SplitsRepository] SplitsRepository instance to store splits in
|
19
|
+
# @param segments_repository [SegmentsRepository] SegmentsRepository instance to store segments in
|
20
|
+
# @param impressions_repository [ImpressionsRepository] ImpressionsRepository instance to store impressions in
|
21
|
+
# @param metrics_repository [MetricsRepository] SplitsRepository instance to store metrics in
|
22
|
+
# @param sdk_blocker [SDKBlocker] SDKBlocker instance which blocks splits_repository/segments_repository
|
36
23
|
#
|
37
24
|
# @return [SplitIoClient] split.io client instance
|
38
25
|
def initialize(api_key, config, splits_repository, segments_repository, impressions_repository, metrics_repository, sdk_blocker)
|
39
26
|
@api_key = api_key
|
40
27
|
@config = config
|
41
|
-
|
42
28
|
@splits_repository = splits_repository
|
43
29
|
@segments_repository = segments_repository
|
44
30
|
@impressions_repository = impressions_repository
|
45
31
|
@metrics_repository = metrics_repository
|
46
|
-
|
47
32
|
@metrics = Metrics.new(100, @config, @metrics_repository)
|
48
|
-
|
49
33
|
@sdk_blocker = sdk_blocker
|
50
34
|
|
51
|
-
@api_client = Faraday.new do |builder|
|
52
|
-
builder.use FaradayMiddleware::Gzip
|
53
|
-
builder.adapter :net_http_persistent
|
54
|
-
end
|
55
|
-
|
56
35
|
start_based_on_mode(@config.mode)
|
57
36
|
end
|
58
37
|
|
@@ -90,119 +69,9 @@ module SplitIoClient
|
|
90
69
|
SplitIoClient::Cache::Senders::ImpressionsSender.new(@impressions_repository, @config, @api_key).call
|
91
70
|
end
|
92
71
|
|
93
|
-
#
|
94
|
-
# helper method to execute a post request to the provided endpoint
|
95
|
-
#
|
96
|
-
# @param path [string] api endpoint path
|
97
|
-
# @param params [object] hash of params that will be added to the request
|
98
|
-
#
|
99
|
-
# @return [object] response to the request
|
100
|
-
def post_api(path, param)
|
101
|
-
@api_client.post (@config.events_uri + path) do |req|
|
102
|
-
req.headers['Authorization'] = 'Bearer ' + @api_key
|
103
|
-
req.headers['Content-Type'] = 'application/json'
|
104
|
-
req.headers['SplitSDKVersion'] = SplitIoClient::SplitFactory.sdk_version
|
105
|
-
req.headers['SplitSDKMachineName'] = @config.machine_name
|
106
|
-
req.headers['SplitSDKMachineIP'] = @config.machine_ip
|
107
|
-
req.body = param.to_json
|
108
|
-
req.options.timeout = @config.read_timeout
|
109
|
-
req.options.open_timeout = @config.connection_timeout
|
110
|
-
|
111
|
-
if @config.transport_debug_enabled
|
112
|
-
@config.logger.debug("POST #{@config.events_uri + path} #{req.body}")
|
113
|
-
elsif @config.debug_enabled
|
114
|
-
@config.logger.debug("POST #{@config.events_uri + path}")
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
#
|
121
|
-
# @return parsed_splits [object] parsed splits for this adapter
|
122
|
-
def parsed_splits
|
123
|
-
@parsed_splits
|
124
|
-
end
|
125
|
-
|
126
|
-
#
|
127
|
-
# @return parsed_segments [object] parsed segments for this adapter
|
128
|
-
def parsed_segments
|
129
|
-
@parsed_segments
|
130
|
-
end
|
131
|
-
|
132
|
-
#
|
133
|
-
# creates two safe threads that will be executing api calls
|
134
|
-
# for posting impressions and metrics given the execution time
|
135
|
-
# provided within the configuration
|
136
|
-
#
|
137
|
-
|
72
|
+
# Starts thread which loops constantly and sends metrics to the Split API
|
138
73
|
def metrics_sender
|
139
|
-
|
140
|
-
return if ENV['SPLITCLIENT_ENV'] == 'test'
|
141
|
-
|
142
|
-
Thread.new do
|
143
|
-
|
144
|
-
@config.logger.info('Starting metrics service')
|
145
|
-
|
146
|
-
loop do
|
147
|
-
begin
|
148
|
-
|
149
|
-
post_metrics
|
150
|
-
|
151
|
-
rescue StandardError => error
|
152
|
-
@config.log_found_exception(__method__.to_s, error)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Sleep either on success of failure.
|
156
|
-
sleep(randomize_interval(@config.metrics_refresh_rate))
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
#
|
163
|
-
# creates the appropriate json data for the cached metrics values
|
164
|
-
# include latencies, counts and gauges
|
165
|
-
# and then sends them to the appropriate api endpoint with a valida body format
|
166
|
-
#
|
167
|
-
# @return [void]
|
168
|
-
def post_metrics
|
169
|
-
if @metrics_repository.latencies.empty?
|
170
|
-
@config.logger.debug('No latencies to report.') if @config.debug_enabled
|
171
|
-
else
|
172
|
-
@metrics_repository.latencies.each do |name, latencies|
|
173
|
-
metrics_time = { name: name, latencies: latencies }
|
174
|
-
res = post_api('/metrics/time', metrics_time)
|
175
|
-
if res.status / 100 != 2
|
176
|
-
@config.logger.error("Unexpected status code while posting time metrics: #{res.status}")
|
177
|
-
else
|
178
|
-
@config.logger.debug("Metric time reported: #{metrics_time.size}") if @config.debug_enabled
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
@metrics_repository.clear_latencies
|
183
|
-
|
184
|
-
if @metrics_repository.counts.empty?
|
185
|
-
@config.logger.debug('No counts to report.') if @config.debug_enabled
|
186
|
-
else
|
187
|
-
@metrics_repository.counts.each do |name, count|
|
188
|
-
metrics_count = { name: name, delta: count }
|
189
|
-
res = post_api('/metrics/counter', metrics_count)
|
190
|
-
if res.status / 100 != 2
|
191
|
-
@config.logger.error("Unexpected status code while posting count metrics: #{res.status}")
|
192
|
-
else
|
193
|
-
@config.logger.debug("Metric counts reported: #{metrics_count.size}") if @config.debug_enabled
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
@metrics_repository.clear_counts
|
198
|
-
end
|
199
|
-
|
200
|
-
private
|
201
|
-
|
202
|
-
def randomize_interval(interval)
|
203
|
-
@random_generator ||= Random.new
|
204
|
-
random_factor = @random_generator.rand(50..100)/100.0
|
205
|
-
interval * random_factor
|
74
|
+
SplitIoClient::Cache::Senders::MetricsSender.new(@metrics_repository, @config, @api_key).call
|
206
75
|
end
|
207
76
|
end
|
208
77
|
end
|
@@ -36,11 +36,13 @@ module SplitIoClient
|
|
36
36
|
def matcher_type(condition)
|
37
37
|
matchers = []
|
38
38
|
|
39
|
-
|
40
|
-
matchers
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
@segments_repository.adapter.pipelined do
|
40
|
+
condition.matchers.each do |matcher|
|
41
|
+
matchers << condition.send(
|
42
|
+
"matcher_#{matcher[:matcherType].downcase}",
|
43
|
+
matcher: matcher, segments_repository: @segments_repository
|
44
|
+
)
|
45
|
+
end
|
44
46
|
end
|
45
47
|
|
46
48
|
final_matcher = condition.create_condition_matcher(matchers)
|
@@ -1,10 +1,8 @@
|
|
1
1
|
module SplitIoClient
|
2
|
-
|
3
2
|
#
|
4
3
|
# represents the possible return values for a treatment
|
5
4
|
#
|
6
5
|
class Treatments < NoMethodError
|
7
|
-
|
8
6
|
# Constants to represent treatment values
|
9
7
|
CONTROL = 'control'
|
10
8
|
OFF = 'off'
|
@@ -34,7 +32,5 @@ module SplitIoClient
|
|
34
32
|
def self.is_control?(treatment)
|
35
33
|
get_type(treatment).equal?(CONTROL) ? true : false
|
36
34
|
end
|
37
|
-
|
38
35
|
end
|
39
|
-
|
40
|
-
end
|
36
|
+
end
|
data/lib/splitclient-rb.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
require 'splitclient-rb/version'
|
2
|
-
|
3
|
-
require 'splitclient-rb/split_factory_builder'
|
4
|
-
require 'splitclient-rb/localhost_split_factory'
|
5
|
-
require 'splitclient-rb/split_config'
|
2
|
+
|
6
3
|
require 'exceptions/sdk_blocker_timeout_expired_exception'
|
7
4
|
require 'cache/adapters/memory_adapters/map_adapter'
|
8
5
|
require 'cache/adapters/memory_adapters/queue_adapter'
|
@@ -20,11 +17,24 @@ require 'cache/repositories/metrics/memory_repository'
|
|
20
17
|
require 'cache/repositories/metrics/redis_repository'
|
21
18
|
require 'cache/senders/impressions_formatter'
|
22
19
|
require 'cache/senders/impressions_sender'
|
20
|
+
require 'cache/senders/metrics_sender'
|
23
21
|
require 'cache/stores/sdk_blocker'
|
24
22
|
require 'cache/stores/segment_store'
|
25
23
|
require 'cache/stores/split_store'
|
24
|
+
|
25
|
+
require 'splitclient-rb/localhost_utils'
|
26
|
+
require 'splitclient-rb/clients/localhost_split_client'
|
27
|
+
require 'splitclient-rb/clients/split_client'
|
28
|
+
require 'splitclient-rb/managers/localhost_split_manager'
|
29
|
+
require 'splitclient-rb/managers/split_manager'
|
30
|
+
require 'splitclient-rb/split_factory'
|
31
|
+
require 'splitclient-rb/split_factory_builder'
|
32
|
+
require 'splitclient-rb/localhost_split_factory'
|
33
|
+
require 'splitclient-rb/split_config'
|
34
|
+
|
26
35
|
require 'engine/api/client'
|
27
36
|
require 'engine/api/impressions'
|
37
|
+
require 'engine/api/metrics'
|
28
38
|
require 'engine/api/segments'
|
29
39
|
require 'engine/api/splits'
|
30
40
|
require 'engine/parser/condition'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module SplitIoClient
|
2
|
+
class LocalhostSplitClient
|
3
|
+
include SplitIoClient::LocalhostUtils
|
4
|
+
|
5
|
+
#
|
6
|
+
# variables to if the sdk is being used in localhost mode and store the list of features
|
7
|
+
attr_reader :localhost_mode
|
8
|
+
attr_reader :localhost_mode_features
|
9
|
+
|
10
|
+
#
|
11
|
+
# Creates a new split client instance that reads from the given splits file
|
12
|
+
#
|
13
|
+
# @param splits_file [File] file that contains some splits
|
14
|
+
#
|
15
|
+
# @return [LocalhostSplitIoClient] split.io localhost client instance
|
16
|
+
def initialize(splits_file, reload_rate = nil)
|
17
|
+
@localhost_mode = true
|
18
|
+
@localhost_mode_features = []
|
19
|
+
load_localhost_mode_features(splits_file, reload_rate)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# method that returns the sdk gem version
|
24
|
+
#
|
25
|
+
# @return [string] version value for this sdk
|
26
|
+
def self.sdk_version
|
27
|
+
'RubyClientSDK-'+SplitIoClient::VERSION
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_treatments(key, split_names, attributes = nil)
|
31
|
+
split_names.each_with_object({}) do |name, memo|
|
32
|
+
puts "name #{name} memo #{memo}"
|
33
|
+
memo.merge!(name => get_treatment(key, name, attributes))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# obtains the treatment for a given feature
|
39
|
+
#
|
40
|
+
# @param id [string] user id
|
41
|
+
# @param feature [string] name of the feature that is being validated
|
42
|
+
#
|
43
|
+
# @return [Treatment] treatment constant value
|
44
|
+
def get_treatment(id, feature, attributes = nil)
|
45
|
+
unless id
|
46
|
+
@config.logger.warn('id was null for feature: ' + feature)
|
47
|
+
return Treatments::CONTROL
|
48
|
+
end
|
49
|
+
|
50
|
+
unless feature
|
51
|
+
@config.logger.warn('feature was null for id: ' + id)
|
52
|
+
return Treatments::CONTROL
|
53
|
+
end
|
54
|
+
|
55
|
+
result = get_localhost_treatment(feature)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
#
|
61
|
+
# auxiliary method to get the treatments avoding exceptions
|
62
|
+
#
|
63
|
+
# @param id [string] user id
|
64
|
+
# @param feature [string] name of the feature that is being validated
|
65
|
+
#
|
66
|
+
# @return [Treatment] tretment constant value
|
67
|
+
def get_treatment_without_exception_handling(id, feature, attributes = nil)
|
68
|
+
get_treatment(id, feature, attributes)
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# method to check if the sdk is running in localhost mode based on api key
|
73
|
+
#
|
74
|
+
# @return [boolean] True if is in localhost mode, false otherwise
|
75
|
+
def is_localhost_mode?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# method to check the treatment for the given feature in localhost mode
|
81
|
+
#
|
82
|
+
# @return [boolean] true if the feature is available in localhost mode, false otherwise
|
83
|
+
def get_localhost_treatment(feature)
|
84
|
+
treatment = @localhost_mode_features.select { |h| h[:feature] == feature }.last || {}
|
85
|
+
|
86
|
+
treatment[:treatment] || Treatments::CONTROL
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|