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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.txt +7 -0
  3. data/Detailed-README.md +20 -5
  4. data/exe/splitio +54 -3
  5. data/lib/cache/adapters/memory_adapters/map_adapter.rb +6 -0
  6. data/lib/cache/adapters/redis_adapter.rb +6 -1
  7. data/lib/cache/repositories/impressions/memory_repository.rb +8 -2
  8. data/lib/cache/repositories/impressions/redis_repository.rb +2 -2
  9. data/lib/cache/repositories/impressions_repository.rb +3 -0
  10. data/lib/cache/repositories/metrics_repository.rb +1 -0
  11. data/lib/cache/repositories/repository.rb +1 -1
  12. data/lib/cache/repositories/segments_repository.rb +16 -1
  13. data/lib/cache/repositories/splits_repository.rb +21 -7
  14. data/lib/cache/senders/impressions_sender.rb +16 -12
  15. data/lib/cache/senders/metrics_sender.rb +41 -0
  16. data/lib/cache/stores/sdk_blocker.rb +9 -8
  17. data/lib/cache/stores/segment_store.rb +1 -1
  18. data/lib/engine/api/client.rb +15 -2
  19. data/lib/engine/api/metrics.rb +57 -0
  20. data/lib/engine/parser/split_adapter.rb +9 -140
  21. data/lib/engine/parser/split_treatment.rb +7 -5
  22. data/lib/engine/partitions/treatments.rb +1 -5
  23. data/lib/splitclient-rb.rb +14 -4
  24. data/lib/splitclient-rb/clients/localhost_split_client.rb +89 -0
  25. data/lib/splitclient-rb/clients/split_client.rb +114 -0
  26. data/lib/splitclient-rb/localhost_split_factory.rb +6 -187
  27. data/lib/splitclient-rb/localhost_utils.rb +36 -0
  28. data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
  29. data/lib/splitclient-rb/managers/split_manager.rb +77 -0
  30. data/lib/splitclient-rb/split_config.rb +20 -0
  31. data/lib/splitclient-rb/split_factory.rb +16 -217
  32. data/lib/splitclient-rb/split_factory_builder.rb +3 -2
  33. data/lib/splitclient-rb/version.rb +1 -1
  34. data/lib/splitclient-rb_utilitites.rb +24 -19
  35. data/splitclient-rb.gemspec +1 -1
  36. data/{splitio.yml → splitio.yml.example} +0 -0
  37. metadata +26 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b06d31341d8cfe73c6aa77924fa2615553fe2f3
4
- data.tar.gz: e64313db0fc86fc2d3e6720ed58bdff449667fa2
3
+ metadata.gz: cf6a979569b37d8f4fbdb3dd072199b1602c33b0
4
+ data.tar.gz: f21c0cb168466ae3ff5e7b2939d738e0ef0bf32b
5
5
  SHA512:
6
- metadata.gz: e32c4fbd33aa6082660d2db98861ecca19c626f86353f41d831d75d25e3c1882d8f9162f729c37311ffcab8a01f32c6ff9b9e97d877eaf8df371a0bc45435455
7
- data.tar.gz: a8bd683be8b74302270344c2f5fdff93179b93247f522549dc465fcc3b5513c95eb6f26b9f4500735982b4c5527c9f3727434e87bda5f2e0280163dcdf72008f
6
+ metadata.gz: e06b3667ff3ec8e06d5abaf20bb25a128f01b9b9a1c784bbb1ab720b35e3d911cbbe132702b9789a6d77793612d966b67df0e2caa0d60fc236341a8ec475ff53
7
+ data.tar.gz: 97f58dabae2e49fed331bb0d8db5d305ca873a5737e5c6efefc31c63b1bb9e5945f93b168708a0f50922810130875429ca56fe9f18dcb1e21756f2389f5ea8a9
data/CHANGES.txt CHANGED
@@ -1,3 +1,10 @@
1
+ 3.1.4
2
+ - Allow to store block until ready flag in Redis
3
+
4
+ 3.1.3
5
+ - Refactor SplitFactory - split it into separate mangers and client classes
6
+ - Refactor Utilities to comply style guide
7
+
1
8
  3.1.2
2
9
  - Fix issue with complex key where get_treatment and get_treatments return different values.
3
10
 
data/Detailed-README.md CHANGED
@@ -76,13 +76,18 @@ new-navigation v3
76
76
  To use SDK in the localhost mode you should pass `localhost` as an API key like this:
77
77
 
78
78
  ```ruby
79
- factory = SplitIoClient::SplitFactoryBuilder.build('localhost', path: '/where/to-look-for/<file_name>')
79
+ factory = SplitIoClient::SplitFactoryBuilder.build('localhost', path: '/where/to-look-for/<file_name>')
80
80
  split_client = factory.client
81
81
  ```
82
82
 
83
- By default SDK will look in your home directory (i.e. `~`) for a `.split` file, but you can specify a different
83
+ By default SDK will look in your home directory (i.e. `~`) for a `.split` file, but you can specify a different
84
84
  file name (full path) to look for the file (note: you must provide absolute path):
85
85
 
86
+ When in localhost mode you can make use of the SDK ability to automatically refresh splits from file, to do that just specify reload rate in seconds like this:
87
+
88
+ ```ruby
89
+ factory = SplitIoClient::SplitFactoryBuilder.build('localhost', path: '/where/to-look-for/<file_name>', reload_rate: 3)
90
+ ```
86
91
 
87
92
  ### Ruby on Rails
88
93
  ---
@@ -327,6 +332,7 @@ SDK can be ran in `producer` mode both in the scope of the application (e.g. as
327
332
  :redis_url: 'redis://127.0.0.1:6379/0'
328
333
  ```
329
334
 
335
+
330
336
  - Install binstubs
331
337
  ```ruby
332
338
  bundle binstubs splitclient-rb
@@ -337,7 +343,15 @@ bundle binstubs splitclient-rb
337
343
  bundle exec bin/splitio -c ~/path/to/config/file.yml
338
344
  ```
339
345
 
340
- That's it!
346
+ Also, you can pass options directly to the cli command, like this:
347
+ ```
348
+ bundle exec bin/splitio -c ~/path/to/config/file.yml --debug
349
+ ```
350
+
351
+ Note: options passed through cli have higher priority than those specified in the configuration file. To see the full list of supported options you can run:
352
+ ```
353
+ bundle exec bin/splitio -h
354
+ ```
341
355
 
342
356
  ## Development
343
357
 
@@ -349,10 +363,11 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
349
363
 
350
364
  The gem uses rspec for unit testing. Under the default `/spec` folder you will find the files for the unit tests and the specs helper file ( spec_helper.rb ). If a new spec file with new unit tests is required you just simply need to create it under the spec folder and all its test will be executed on the next rspec execution.
351
365
 
352
- To run the suite of unit tests a rake task is provided. It's executed with the following command:
366
+ To run the suite of unit tests a rake task is provided.
353
367
 
368
+ Make sure redis is running in localhost at redis://127.0.0.1:6379/0 and then just run:
354
369
  ```bash
355
- $ rake spec
370
+ SPLITCLIENT_ENV=test bundle exec rspecrake spec
356
371
  ```
357
372
 
358
373
  Also, simplecov is used for coverage reporting. After the execution of the rake task it will create the `/coverage` folder with coverage reports in pretty HTML format.
data/exe/splitio CHANGED
@@ -9,12 +9,61 @@ require_relative '../lib/splitclient-rb'
9
9
 
10
10
  ARGV << '-h' if ARGV.empty?
11
11
 
12
+ config_path = ''
12
13
  options = {}
13
14
  opt_parser = OptionParser.new do |opts|
14
15
  opts.banner = "Usage: splitio [options]"
15
16
 
16
17
  opts.on("-cPATH", "--config=PATH", "Set the path to splitio.yml config file") do |c|
17
- options[:config_path] = c
18
+ config_path = c
19
+ end
20
+
21
+ opts.on("--base-uri=BASE_URI", "Set the base uri for Split SDK") do |c|
22
+ options[:base_uri] = c
23
+ end
24
+
25
+ opts.on("--events-uri=EVENTS_URI", "Set the events uri for Split SDK") do |c|
26
+ options[:events_uri] = c
27
+ end
28
+
29
+ opts.on("--read-timeout=READ_TIMEOUT", "Read timeout in seconds") do |c|
30
+ options[:read_timeout] = c
31
+ end
32
+
33
+ opts.on("--connection-timeout=CONNECTION_TIMEOUT", "Connection timeout in seconds") do |c|
34
+ options[:connection_timeout] = c
35
+ end
36
+
37
+ opts.on("--features-refresh-rate=FEATURES_REFRESH_RATE", "Features refresh rate in seconds") do |c|
38
+ options[:features_refresh_rate] = c
39
+ end
40
+
41
+ opts.on("--segments-refresh-rate=SEGMENTS_REFRESH_RATE", "Segments refresh rate in seconds") do |c|
42
+ options[:segments_refresh_rate] = c
43
+ end
44
+
45
+ opts.on("--metrics-refresh-rate=METRICS_REFRESH_RATE", "Metrics refresh rate in seconds") do |c|
46
+ options[:metrics_refresh_rate] = c
47
+ end
48
+
49
+ opts.on("--impressions-refresh-rate=IMPRESSIONS_REFRESH_RATE", "Impressions refresh rate in seconds") do |c|
50
+ options[:impressions_refresh_rate] = c
51
+ end
52
+
53
+ opts.on("--block-until-ready=SECONDS", "Seconds to block the app until SDK is ready or false to run in non-blocking mode") do |c|
54
+ options[:block_until_ready] = c
55
+ end
56
+
57
+ opts.on("--redis-url=REDIS_URL", "Set base uri for Split SDK") do |c|
58
+ options[:redis_url] = c
59
+ end
60
+
61
+ opts.on("--transport-debug", "Enable transport debug") do
62
+ options[:transport_debug_enabled] = true
63
+ end
64
+
65
+ opts.on("-d", "--debug", "Enable debug mode") do
66
+ options[:debug_enabled] = true
18
67
  end
19
68
 
20
69
  opts.on_tail("-h", "--help", "Prints this help") do
@@ -31,7 +80,9 @@ begin
31
80
  exit(1)
32
81
  end
33
82
 
34
- config = YAML.load_file(options[:config_path])
35
- config.merge!(mode: :producer, cache_adapter: :redis)
83
+ config = YAML.load_file(config_path)
84
+ config
85
+ .merge!(mode: :producer, cache_adapter: :redis)
86
+ .merge!(options)
36
87
 
37
88
  SplitIoClient::SplitFactory.new(config[:api_key], config)
@@ -116,6 +116,12 @@ module SplitIoClient
116
116
  @map.delete(key)
117
117
  end
118
118
  end
119
+
120
+ # This method is used in Redis adapter
121
+ # "stubbing" it here to keep the interface
122
+ def pipelined(&block)
123
+ block.call
124
+ end
119
125
  end
120
126
  end
121
127
  end
@@ -1,4 +1,3 @@
1
- require 'redis'
2
1
  require 'json'
3
2
 
4
3
  module SplitIoClient
@@ -121,6 +120,12 @@ module SplitIoClient
121
120
  def inc(key, inc = 1)
122
121
  @redis.incrby(key, inc)
123
122
  end
123
+
124
+ def pipelined(&block)
125
+ @redis.pipelined do
126
+ block.call
127
+ end
128
+ end
124
129
  end
125
130
  end
126
131
  end
@@ -18,9 +18,15 @@ module SplitIoClient
18
18
  end
19
19
  end
20
20
 
21
- def add_bulk(key, treatments, time)
21
+ def add_bulk(key, bucketing_key, treatments, time)
22
22
  treatments.each do |split_name, treatment|
23
- add(split_name, 'key_name' => key, 'treatment' => treatment, 'time' => time)
23
+ add(
24
+ split_name,
25
+ 'key_name' => key,
26
+ 'bucketing_key' => bucketing_key,
27
+ 'treatment' => treatment,
28
+ 'time' => time
29
+ )
24
30
  end
25
31
  end
26
32
 
@@ -17,11 +17,11 @@ module SplitIoClient
17
17
  )
18
18
  end
19
19
 
20
- def add_bulk(key, treatments, time)
20
+ def add_bulk(key, bucketing_key, treatments, time)
21
21
  @adapter.redis.pipelined do
22
22
  treatments.each_slice(IMPRESSIONS_SLICE) do |treatments_slice|
23
23
  treatments_slice.each do |split_name, treatment|
24
- add(split_name, 'key_name' => key, 'treatment' => treatment, 'time' => time)
24
+ add(split_name, 'key_name' => key, 'bucketing_key' => bucketing_key, 'treatment' => treatment, 'time' => time)
25
25
  end
26
26
  end
27
27
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module SplitIoClient
2
4
  module Cache
3
5
  module Repositories
@@ -7,6 +9,7 @@ module SplitIoClient
7
9
  def_delegators :@adapter, :add, :add_bulk, :clear, :empty?
8
10
 
9
11
  def initialize(adapter, config)
12
+ @config = config
10
13
  @adapter = case adapter.class.to_s
11
14
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
12
15
  Repositories::Impressions::MemoryRepository.new(adapter, config)
@@ -8,6 +8,7 @@ module SplitIoClient
8
8
  :clear_counts, :clear_latencies, :clear_gauges
9
9
 
10
10
  def initialize(adapter, config)
11
+ @config = config
11
12
  @adapter = case adapter.class.to_s
12
13
  when 'SplitIoClient::Cache::Adapters::MemoryAdapter'
13
14
  Repositories::Metrics::MemoryRepository.new(adapter, config)
@@ -12,7 +12,7 @@ module SplitIoClient
12
12
  protected
13
13
 
14
14
  def namespace_key(key)
15
- "SPLITIO.#{key}"
15
+ "#{@config.redis_namespace}/#{key}"
16
16
  end
17
17
  end
18
18
  end
@@ -4,8 +4,11 @@ module SplitIoClient
4
4
  class SegmentsRepository < Repository
5
5
  KEYS_SLICE = 3000
6
6
 
7
- def initialize(adapter)
7
+ attr_reader :adapter
8
+
9
+ def initialize(adapter, config)
8
10
  @adapter = adapter
11
+ @config = config
9
12
 
10
13
  @adapter.set_bool(namespace_key('ready'), false)
11
14
  end
@@ -40,6 +43,18 @@ module SplitIoClient
40
43
  @adapter.string(namespace_key("segment.#{name}.till")) || -1
41
44
  end
42
45
 
46
+ def ready?
47
+ @adapter.string(namespace_key('cache.ready.segments')).to_i != -1
48
+ end
49
+
50
+ def not_ready!
51
+ @adapter.set_string(namespace_key('cache.ready.segments'), -1)
52
+ end
53
+
54
+ def ready!
55
+ @adapter.set_string(namespace_key('cache.ready.segments'), Time.now.utc.to_i)
56
+ end
57
+
43
58
  private
44
59
 
45
60
  def segment_data(name)
@@ -6,10 +6,13 @@ module SplitIoClient
6
6
  class SplitsRepository < Repository
7
7
  SPLITS_SLICE = 10
8
8
 
9
- def initialize(adapter)
9
+ attr_reader :adapter
10
+
11
+ def initialize(adapter, config)
10
12
  @adapter = adapter
13
+ @config = config
11
14
 
12
- @adapter.set_string(namespace_key('split.till'), '-1')
15
+ @adapter.set_string(namespace_key('splits.till'), '-1')
13
16
  @adapter.initialize_map(namespace_key('segments.registered'))
14
17
  end
15
18
 
@@ -54,19 +57,18 @@ module SplitIoClient
54
57
  splits_hash
55
58
  end
56
59
 
57
- # Return an array of Split Names excluding control keys like split.till
60
+ # Return an array of Split Names excluding control keys like splits.till
58
61
  def split_names
59
- @adapter.find_strings_by_prefix(namespace_key('split'))
60
- .reject { |split| split == namespace_key('split.till') }
62
+ @adapter.find_strings_by_prefix(namespace_key('split.'))
61
63
  .map { |split| split.gsub(namespace_key('split.'), '') }
62
64
  end
63
65
 
64
66
  def set_change_number(since)
65
- @adapter.set_string(namespace_key('split.till'), since)
67
+ @adapter.set_string(namespace_key('splits.till'), since)
66
68
  end
67
69
 
68
70
  def get_change_number
69
- @adapter.string(namespace_key('split.till'))
71
+ @adapter.string(namespace_key('splits.till'))
70
72
  end
71
73
 
72
74
  def set_segment_names(names)
@@ -80,6 +82,18 @@ module SplitIoClient
80
82
  def exists?(name)
81
83
  @adapter.exists?(namespace_key("split.#{name}"))
82
84
  end
85
+
86
+ def ready?
87
+ @adapter.string(namespace_key('cache.ready.splits')).to_i != -1
88
+ end
89
+
90
+ def not_ready!
91
+ @adapter.set_string(namespace_key('cache.ready.splits'), -1)
92
+ end
93
+
94
+ def ready!
95
+ @adapter.set_string(namespace_key('cache.ready.splits'), Time.now.utc.to_i)
96
+ end
83
97
  end
84
98
  end
85
99
  end
@@ -18,13 +18,11 @@ module SplitIoClient
18
18
  if ENV['SPLITCLIENT_ENV'] == 'test'
19
19
  post_impressions
20
20
  else
21
- Thread.new do
22
- @config.logger.info('Starting impressions service')
21
+ impressions_thread
23
22
 
24
- loop do
25
- post_impressions
26
-
27
- sleep(randomize_interval(@config.impressions_refresh_rate))
23
+ if defined?(PhusionPassenger)
24
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
25
+ impressions_thread if forked
28
26
  end
29
27
  end
30
28
  end
@@ -32,6 +30,18 @@ module SplitIoClient
32
30
 
33
31
  private
34
32
 
33
+ def impressions_thread
34
+ Thread.new do
35
+ @config.logger.info('Starting impressions service')
36
+
37
+ loop do
38
+ post_impressions
39
+
40
+ sleep(::Utilities.randomize_interval(@config.impressions_refresh_rate))
41
+ end
42
+ end
43
+ end
44
+
35
45
  def post_impressions
36
46
  impressions_client.post
37
47
  rescue StandardError => error
@@ -45,12 +55,6 @@ module SplitIoClient
45
55
  def impressions_client
46
56
  SplitIoClient::Api::Impressions.new(@api_key, @config, formatted_impressions)
47
57
  end
48
-
49
- def randomize_interval(interval)
50
- @random_generator ||= Random.new
51
- random_factor = @random_generator.rand(50..100)/100.0
52
- interval * random_factor
53
- end
54
58
  end
55
59
  end
56
60
  end
@@ -0,0 +1,41 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Senders
4
+ class MetricsSender
5
+ def initialize(metrics_repository, config, api_key)
6
+ @metrics_repository = metrics_repository
7
+ @config = config
8
+ @api_key = api_key
9
+ end
10
+
11
+ def call
12
+ return if ENV['SPLITCLIENT_ENV'] == 'test'
13
+
14
+ post_metrics
15
+
16
+ Thread.new do
17
+ @config.logger.info('Starting metrics service')
18
+
19
+ loop do
20
+ post_metrics
21
+
22
+ sleep(::Utilities.randomize_interval(@config.metrics_refresh_rate))
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def post_metrics
30
+ metrics_client.post
31
+ rescue StandardError => error
32
+ @config.log_found_exception(__method__.to_s, error)
33
+ end
34
+
35
+ def metrics_client
36
+ SplitIoClient::Api::Metrics.new(@api_key, @config, @metrics_repository)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -5,29 +5,30 @@ module SplitIoClient
5
5
  module Cache
6
6
  module Stores
7
7
  class SDKBlocker
8
- attr_reader :splits_ready
8
+ attr_reader :splits_repository
9
9
  attr_writer :splits_thread, :segments_thread
10
10
 
11
- def initialize(config)
11
+ def initialize(config, splits_repository, segments_repository)
12
12
  @config = config
13
+ @splits_repository = splits_repository
14
+ @segments_repository = segments_repository
13
15
 
14
- @splits_ready = false
15
- @segments_ready = false
16
+ @splits_repository.not_ready!
17
+ @segments_repository.not_ready!
16
18
  end
17
19
 
18
20
  def splits_ready!
19
- @splits_ready = true
21
+ @splits_repository.ready!
20
22
  end
21
23
 
22
24
  def segments_ready!
23
- @segments_ready = true
25
+ @segments_repository.ready!
24
26
  end
25
27
 
26
28
  def block
27
29
  begin
28
30
  Timeout::timeout(@config.block_until_ready) do
29
31
  sleep 0.1 until ready?
30
- sleep 0.1 until ready?
31
32
  end
32
33
  rescue Timeout::Error
33
34
  fail SDKBlockerTimeoutExpiredException, 'SDK start up timeout expired'
@@ -39,7 +40,7 @@ module SplitIoClient
39
40
  end
40
41
 
41
42
  def ready?
42
- @splits_ready && @segments_ready
43
+ @splits_repository.ready? && @segments_repository.ready?
43
44
  end
44
45
  end
45
46
  end