splitclient-rb 3.1.3 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e5c4c6e0e239b150d86755ca6d3f5ae58aec3be6
4
- data.tar.gz: 559ebfa78216411b6aecbe31bd885a17d2ae8d24
3
+ metadata.gz: 8c70d0111affdbf6d68572303ca335f9712fc8b3
4
+ data.tar.gz: 58b5eacffa5a95c9ff1b906b376660e0d6331788
5
5
  SHA512:
6
- metadata.gz: 05fb963b7cec39bb32ab3fff03f5333fb3dde6a631af31c897c8552995f2d556d79ae977b1f0f03754021760a334bdd90d26b9886e065878b2cf955ff14ae2dd
7
- data.tar.gz: 0cbde59397c915e4967358798cff31fabb750f440d605895ac83a8a1206e74de22d1036f10c28b145acc75d620e3ab51762a99462ae3630991113a4b242c7a96
6
+ metadata.gz: 99550497b640c1fdfc623ecf1800dea0e4d4ca6837132b81fd9c1ddd302bce2e251976fc46897670fe9ba87a1d62a1ba1a3fc4a385df43e90cbe3d1e86ada374
7
+ data.tar.gz: d1e7b4822aae284c209b2b866a61c4e8cfc95a71204c5b9ff96fa91c11884fa6bae83342571e81495b4b29a24610ee7d3f33fc4fe845f56a87277fd6a2c7a664
data/CHANGES.txt CHANGED
@@ -1,3 +1,6 @@
1
+ 3.2.0
2
+ - Add impression labeling
3
+
1
4
  3.1.3
2
5
  - Refactor SplitFactory - split it into separate mangers and client classes
3
6
  - Refactor Utilities to comply style guide
data/Detailed-README.md CHANGED
@@ -152,9 +152,13 @@ split_client.get_treatment('user_id','feature_name', attr: 'val')
152
152
 
153
153
  *default value* = `Logger.new($stdout)`
154
154
 
155
- **block_until_ready** : The SDK will block your app for provided amount of seconds until it's ready. If timeout expires `SplitIoClient::SDKBlockerTimeoutExpiredException` will be thrown. If `false` provided, then SDK would run in non-blocking mode
155
+ **ready** : The SDK will block your app for provided amount of seconds until it's ready. If timeout expires `SplitIoClient::SDKBlockerTimeoutExpiredException` will be thrown. If `0` provided, then SDK would run in non-blocking mode
156
156
 
157
- *default value* = `false`
157
+ *default value* = `0`
158
+
159
+ **labels_enabled** : Enables sending labels along with sensitive information
160
+
161
+ *default value* = `true`
158
162
 
159
163
  **mode** : See [SDK modes section](#sdk-modes).
160
164
 
@@ -211,7 +215,7 @@ end
211
215
 
212
216
  #### IMPORTANT
213
217
 
214
- 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.
218
+ For now, SDK does not support both `producer` mode and `ready`. You must either run SDK in `standalone` mode, or do not use `ready` option.
215
219
 
216
220
  This begin-rescue-end block is optional, you might want to use it to catch timeout expired exception and apply some logic.
217
221
 
@@ -367,7 +371,7 @@ To run the suite of unit tests a rake task is provided.
367
371
 
368
372
  Make sure redis is running in localhost at redis://127.0.0.1:6379/0 and then just run:
369
373
  ```bash
370
- SPLITCLIENT_ENV=test bundle exec rspecrake spec
374
+ SPLITCLIENT_ENV=test bundle exec rspec spec
371
375
  ```
372
376
 
373
377
  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/console ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ bundle exec rake console
data/exe/splitio CHANGED
@@ -54,8 +54,8 @@ opt_parser = OptionParser.new do |opts|
54
54
  options[:impressions_refresh_rate] = c
55
55
  end
56
56
 
57
- 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|
58
- options[:block_until_ready] = c
57
+ opts.on("--ready=SECONDS", "Seconds to block the app until SDK is ready or false to run in non-blocking mode") do |c|
58
+ options[:ready] = c
59
59
  end
60
60
 
61
61
  opts.on("--redis-url=REDIS_URL", "Set base uri for Split SDK") do |c|
@@ -5,6 +5,8 @@ module SplitIoClient
5
5
  module Adapters
6
6
  # Redis adapter used to provide interface to Redis
7
7
  class RedisAdapter
8
+ SCAN_SLICE = 5000
9
+
8
10
  attr_reader :redis
9
11
 
10
12
  def initialize(redis_url)
@@ -52,7 +54,17 @@ module SplitIoClient
52
54
  end
53
55
 
54
56
  def find_strings_by_prefix(prefix)
55
- @redis.keys("#{prefix}*")
57
+ memo = { items: [], cursor: 0 }
58
+
59
+ loop do
60
+ memo[:cursor], items = @redis.scan(memo[:cursor], match: "#{prefix}*", count: SCAN_SLICE)
61
+
62
+ memo[:items].push(*items)
63
+
64
+ break if memo[:cursor] == '0'
65
+ end
66
+
67
+ memo[:items]
56
68
  end
57
69
 
58
70
  def multiple_strings(keys)
@@ -18,13 +18,15 @@ module SplitIoClient
18
18
  end
19
19
  end
20
20
 
21
- def add_bulk(key, bucketing_key, treatments, time)
22
- treatments.each do |split_name, treatment|
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
22
+ treatments_labels_change_numbers.each do |split_name, treatment_label_number|
23
23
  add(
24
24
  split_name,
25
25
  'key_name' => key,
26
26
  'bucketing_key' => bucketing_key,
27
- 'treatment' => treatment,
27
+ 'treatment' => treatment_label_number[:treatment],
28
+ 'label' => @config.labels_enabled ? treatment_label_number[:label] : nil,
29
+ 'change_number' => treatment_label_number[:change_number],
28
30
  'time' => time
29
31
  )
30
32
  end
@@ -32,7 +34,7 @@ module SplitIoClient
32
34
 
33
35
  # Get everything from the queue and leave it empty
34
36
  def clear
35
- @adapter.clear
37
+ @adapter.clear.map { |impression| impression.update(ip: @config.machine_ip) }
36
38
  end
37
39
 
38
40
  private
@@ -13,15 +13,23 @@ module SplitIoClient
13
13
  # Store impression data in Redis
14
14
  def add(split_name, data)
15
15
  @adapter.add_to_set(
16
- namespace_key("impressions.#{split_name}"), data.merge(split_name: split_name).to_json
16
+ namespace_key("impressions.#{split_name}"),
17
+ data.merge(split_name: split_name).to_json
17
18
  )
18
19
  end
19
20
 
20
- def add_bulk(key, bucketing_key, treatments, time)
21
+ def add_bulk(key, bucketing_key, treatments_labels_change_numbers, time)
21
22
  @adapter.redis.pipelined do
22
- treatments.each_slice(IMPRESSIONS_SLICE) do |treatments_slice|
23
- treatments_slice.each do |split_name, treatment|
24
- add(split_name, 'key_name' => key, 'bucketing_key' => bucketing_key, 'treatment' => treatment, 'time' => time)
23
+ treatments_labels_change_numbers.each_slice(IMPRESSIONS_SLICE) do |treatments_labels_change_numbers_slice|
24
+ treatments_labels_change_numbers_slice.each do |split_name, treatment_label_change_number|
25
+ add(split_name,
26
+ 'key_name' => key,
27
+ 'bucketing_key' => bucketing_key,
28
+ 'treatment' => treatment_label_change_number[:treatment],
29
+ 'label' => @config.labels_enabled ? treatment_label_change_number[:label] : nil,
30
+ 'change_number' => treatment_label_change_number[:change_number],
31
+ 'time' => time
32
+ )
25
33
  end
26
34
  end
27
35
  end
@@ -31,13 +39,15 @@ module SplitIoClient
31
39
  # delete fetched impressions afterwards
32
40
  def clear
33
41
  impressions = impression_keys.each_with_object([]) do |key, memo|
42
+ _, _, ip, = key.split('/')
34
43
  members = @adapter.random_set_elements(key, @config.impressions_queue_size)
35
44
  members.each do |impression|
36
45
  parsed_impression = JSON.parse(impression)
37
46
 
38
47
  memo << {
39
48
  feature: parsed_impression['split_name'],
40
- impressions: parsed_impression.reject { |k, _| k == 'split_name' }
49
+ impressions: parsed_impression.reject { |k| k == 'split_name' },
50
+ ip: ip
41
51
  }
42
52
  end
43
53
 
@@ -51,7 +61,7 @@ module SplitIoClient
51
61
 
52
62
  # Get all sets by prefix
53
63
  def impression_keys
54
- @adapter.find_sets_by_prefix(namespace_key('impressions.'))
64
+ @adapter.find_sets_by_prefix("#{@config.redis_namespace}/*/impressions.*")
55
65
  end
56
66
  end
57
67
  end
@@ -12,7 +12,7 @@ module SplitIoClient
12
12
  protected
13
13
 
14
14
  def namespace_key(key)
15
- "#{@config.redis_namespace}/#{key}"
15
+ "#{@config.redis_namespace}/#{@config.machine_ip}/#{key}"
16
16
  end
17
17
  end
18
18
  end
@@ -14,20 +14,26 @@ module SplitIoClient
14
14
  return [] if impressions.empty? || filtered_impressions.empty?
15
15
 
16
16
  formatted_impressions = unique_features(filtered_impressions).each_with_object([]) do |feature, memo|
17
+ ip = nil
17
18
  current_impressions =
18
19
  filtered_impressions
19
- .select { |i| i[:feature] == feature }
20
- .map do |i|
20
+ .select { |impression| impression[:feature] == feature }
21
+ .map do |impression|
22
+ ip = impression[:ip]
21
23
  {
22
- keyName: i[:impressions]['key_name'],
23
- treatment: i[:impressions]['treatment'],
24
- time: i[:impressions]['time']
24
+ keyName: impression[:impressions]['key_name'],
25
+ treatment: impression[:impressions]['treatment'],
26
+ time: impression[:impressions]['time'],
27
+ bucketingKey: impression[:impressions]['bucketing_key'],
28
+ label: impression[:impressions]['label'],
29
+ changeNumber: impression[:impressions]['change_number'],
25
30
  }
26
31
  end
27
32
 
28
33
  memo << {
29
34
  testName: feature,
30
- keyImpressions: current_impressions
35
+ keyImpressions: current_impressions,
36
+ ip: ip
31
37
  }
32
38
  end
33
39
 
@@ -37,7 +43,7 @@ module SplitIoClient
37
43
  private
38
44
 
39
45
  def unique_features(impressions)
40
- impressions.map { |i| i[:feature] }.uniq
46
+ impressions.map { |impression| impression[:feature] }.uniq
41
47
  end
42
48
 
43
49
  # Filter seen impressions by impression_hash
@@ -57,6 +63,8 @@ module SplitIoClient
57
63
  def impression_hash(impression)
58
64
  "#{impression[:feature]}:" \
59
65
  "#{impression[:impressions]['key_name']}:" \
66
+ "#{impression[:impressions]['bucketing_key']}:" \
67
+ "#{impression[:impressions]['change_number']}:" \
60
68
  "#{impression[:impressions]['treatment']}"
61
69
  end
62
70
  end
@@ -50,7 +50,7 @@ module SplitIoClient
50
50
 
51
51
  @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
52
52
 
53
- if @config.block_until_ready && !@sdk_blocker.ready?
53
+ if @config.block_until_ready > 0 && !@sdk_blocker.ready?
54
54
  @sdk_blocker.splits_ready!
55
55
  @config.logger.info('splits are ready')
56
56
  end
@@ -15,12 +15,16 @@ module SplitIoClient
15
15
  config.logger.debug("GET #{url}") if config.debug_enabled
16
16
  end
17
17
  rescue StandardError => e
18
- config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
18
+ config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
19
+
20
+ false
19
21
  end
20
22
 
21
- def post_api(url, config, api_key, data, params = {})
23
+ def post_api(url, config, api_key, data, headers = {}, params = {})
22
24
  api_client.post(url) do |req|
23
- req.headers = common_headers(api_key, config).merge('Content-Type' => 'application/json')
25
+ req.headers = common_headers(api_key, config)
26
+ .merge('Content-Type' => 'application/json')
27
+ .merge(headers)
24
28
 
25
29
  req.body = data.to_json
26
30
 
@@ -35,6 +39,8 @@ module SplitIoClient
35
39
  end
36
40
  rescue StandardError => e
37
41
  config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
42
+
43
+ false
38
44
  end
39
45
 
40
46
  private
@@ -13,12 +13,14 @@ module SplitIoClient
13
13
  return
14
14
  end
15
15
 
16
- result = post_api("#{@config.events_uri}/testImpressions/bulk", @config, @api_key, @impressions)
16
+ impressions_by_ip.each do |ip, impressions|
17
+ result = post_api("#{@config.events_uri}/testImpressions/bulk", @config, @api_key, impressions, 'SplitSDKMachineIP' => ip)
17
18
 
18
- if result.status / 100 != 2
19
- @config.logger.error("Unexpected status code while posting impressions: #{result.status}")
20
- else
21
- @config.logger.debug("Impressions reported: #{total_impressions(@impressions)}") if @config.debug_enabled
19
+ if (200..299).include? result.status
20
+ @config.logger.debug("Impressions reported: #{total_impressions(@impressions)}") if @config.debug_enabled
21
+ else
22
+ @config.logger.error("Unexpected status code while posting impressions: #{result.status}")
23
+ end
22
24
  end
23
25
  end
24
26
 
@@ -29,6 +31,12 @@ module SplitIoClient
29
31
  impressions_count += impression[:keyImpressions].length
30
32
  end
31
33
  end
34
+
35
+ private
36
+
37
+ def impressions_by_ip
38
+ @impressions.group_by { |impression| impression[:ip] }
39
+ end
32
40
  end
33
41
  end
34
42
  end
@@ -23,11 +23,7 @@ module SplitIoClient
23
23
 
24
24
  result = post_api("#{@config.events_uri}/metrics/time", @config, @api_key, metrics_time)
25
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
26
+ log_status(result, metrics_time.size)
31
27
  end
32
28
  end
33
29
 
@@ -43,15 +39,23 @@ module SplitIoClient
43
39
 
44
40
  result = post_api("#{@config.events_uri}/metrics/counter", @config, @api_key, metrics_count)
45
41
 
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
42
+ log_status(result, metrics_count.size)
51
43
  end
52
44
  end
53
45
  @metrics_repository.clear_counts
54
46
  end
47
+
48
+ private
49
+
50
+ def log_status(result, info_to_log)
51
+ if result == false
52
+ @config.logger.error("Failed to make a http request")
53
+ elsif (200..299).include? result.status
54
+ @config.logger.debug("Metric time reported: #{info_to_log}") if @config.debug_enabled
55
+ else
56
+ @config.logger.error("Unexpected status code while posting time metrics: #{result.status}")
57
+ end
58
+ end
55
59
  end
56
60
  end
57
61
  end
@@ -35,7 +35,9 @@ module SplitIoClient
35
35
  segments = []
36
36
  segment = get_api("#{@config.base_uri}/segmentChanges/#{name}", @config, @api_key, since: since)
37
37
 
38
- if segment.status / 100 == 2
38
+ if segment == false
39
+ @config.logger.error("Failed to make a http request")
40
+ elsif segment.status / 100 == 2
39
41
  segment_content = JSON.parse(segment.body, symbolize_names: true)
40
42
  @segments_repository.set_change_number(name, segment_content[:till])
41
43
  @metrics.count(prefix + '.status.' + segment.status.to_s, 1)
@@ -12,7 +12,9 @@ module SplitIoClient
12
12
  prefix = 'splitChangeFetcher'
13
13
  splits = get_api("#{@config.base_uri}/splitChanges", @config, @api_key, since: since)
14
14
 
15
- if splits.status / 100 == 2
15
+ if splits == false
16
+ @config.logger.error("Failed to make a http request")
17
+ elsif splits.status / 100 == 2
16
18
  result = splits_with_segment_names(splits.body)
17
19
 
18
20
  @metrics.count(prefix + '.status.' + splits.status.to_s, 1)
@@ -0,0 +1,12 @@
1
+ module SplitIoClient
2
+ module Engine
3
+ module Models
4
+ class Label
5
+ ARCHIVED = 'archived'
6
+ NO_RULE_MATCHED = 'no rule matched'
7
+ EXCEPTION = 'exception'
8
+ KILLED = 'killed'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,6 +1,9 @@
1
1
  require 'json'
2
2
  require 'thread'
3
3
 
4
+ include SplitIoClient::Cache::Stores
5
+ include SplitIoClient::Cache::Senders
6
+
4
7
  module SplitIoClient
5
8
  #
6
9
  # acts as an api adapater to connect to split endpoints
@@ -56,22 +59,22 @@ module SplitIoClient
56
59
 
57
60
  # Starts thread which loops constantly and stores splits in the splits_repository of choice
58
61
  def split_store
59
- SplitIoClient::Cache::Stores::SplitStore.new(@splits_repository, @config, @api_key, @metrics, @sdk_blocker).call
62
+ SplitStore.new(@splits_repository, @config, @api_key, @metrics, @sdk_blocker).call
60
63
  end
61
64
 
62
65
  # Starts thread which loops constantly and stores segments in the segments_repository of choice
63
66
  def segment_store
64
- SplitIoClient::Cache::Stores::SegmentStore.new(@segments_repository, @config, @api_key, @metrics, @sdk_blocker).call
67
+ SegmentStore.new(@segments_repository, @config, @api_key, @metrics, @sdk_blocker).call
65
68
  end
66
69
 
67
70
  # Starts thread which loops constantly and sends impressions to the Split API
68
71
  def impressions_sender
69
- SplitIoClient::Cache::Senders::ImpressionsSender.new(@impressions_repository, @config, @api_key).call
72
+ ImpressionsSender.new(@impressions_repository, @config, @api_key).call
70
73
  end
71
74
 
72
75
  # Starts thread which loops constantly and sends metrics to the Split API
73
76
  def metrics_sender
74
- SplitIoClient::Cache::Senders::MetricsSender.new(@metrics_repository, @config, @api_key).call
77
+ MetricsSender.new(@metrics_repository, @config, @api_key).call
75
78
  end
76
79
  end
77
80
  end
@@ -8,29 +8,38 @@ module SplitIoClient
8
8
 
9
9
  def call(keys, split, attributes = nil)
10
10
  split_model = Models::Split.new(split)
11
- default_treatment = split[:defaultTreatment]
11
+ @default_treatment = split[:defaultTreatment]
12
12
 
13
- return Treatments::CONTROL if split_model.archived?
13
+ return treatment(Models::Label::ARCHIVED, Treatments::CONTROL, split[:changeNumber]) if split_model.archived?
14
14
 
15
- split_model.matchable? ? match(split, keys, attributes, default_treatment) : default_treatment
15
+ if split_model.matchable?
16
+ match(split, keys, attributes)
17
+ else
18
+ treatment(Models::Label::KILLED, @default_treatment, split[:changeNumber])
19
+ end
16
20
  end
17
21
 
18
22
  private
19
23
 
20
- def match(split, keys, attributes, default_treatment)
24
+ def match(split, keys, attributes)
21
25
  split[:conditions].each do |c|
22
26
  condition = SplitIoClient::Condition.new(c)
23
27
 
24
28
  next if condition.empty?
25
29
 
26
30
  if matcher_type(condition).match?(keys[:matching_key], attributes)
27
- treatment = Splitter.get_treatment(keys[:bucketing_key], split[:seed], condition.partitions)
31
+ key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
32
+ result = Splitter.get_treatment(key, split[:seed], condition.partitions)
28
33
 
29
- return treatment.nil? ? default_treatment : treatment
34
+ if result.nil?
35
+ return treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
36
+ else
37
+ return treatment(c[:label], result, split[:changeNumber])
38
+ end
30
39
  end
31
40
  end
32
41
 
33
- default_treatment
42
+ treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
34
43
  end
35
44
 
36
45
  def matcher_type(condition)
@@ -53,6 +62,10 @@ module SplitIoClient
53
62
  final_matcher
54
63
  end
55
64
  end
65
+
66
+ def treatment(label, treatment, change_number = nil)
67
+ { label: label, treatment: treatment, change_number: change_number }
68
+ end
56
69
  end
57
70
  end
58
71
  end
@@ -56,4 +56,5 @@ require 'engine/evaluator/splitter'
56
56
  require 'engine/metrics/metrics'
57
57
  require 'engine/metrics/binary_search_latency_tracker'
58
58
  require 'engine/models/split'
59
+ require 'engine/models/label'
59
60
  require 'splitclient-rb_utilitites'
@@ -1,114 +1,136 @@
1
1
  module SplitIoClient
2
2
  class SplitClient
3
- #
4
- # Creates a new split client instance that connects to split.io API.
5
- #
6
- # @param api_key [String] the API key for your split account
7
- #
8
- # @return [SplitIoClient] split.io client instance
9
- def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository)
10
- @config = config
11
-
12
- @splits_repository = splits_repository
13
- @segments_repository = segments_repository
14
- @impressions_repository = impressions_repository
15
- @metrics_repository = metrics_repository
16
-
17
- @adapter = adapter
18
- end
19
3
 
20
- def get_treatments(key, split_names, attributes = nil)
21
- bucketing_key, matching_key = keys_from_key(key)
22
- bucketing_key = matching_key if bucketing_key.nil?
4
+ #
5
+ # Creates a new split client instance that connects to split.io API.
6
+ #
7
+ # @param api_key [String] the API key for your split account
8
+ #
9
+ # @return [SplitIoClient] split.io client instance
10
+ def initialize(api_key, config = {}, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository)
11
+ @config = config
12
+
13
+ @splits_repository = splits_repository
14
+ @segments_repository = segments_repository
15
+ @impressions_repository = impressions_repository
16
+ @metrics_repository = metrics_repository
17
+
18
+ @adapter = adapter
19
+ end
20
+
21
+ def get_treatments(key, split_names, attributes = nil)
22
+ bucketing_key, matching_key = keys_from_key(key)
23
23
 
24
- treatments =
25
- @splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
26
- memo.merge!(name => get_treatment(key, name, attributes, data, false))
24
+ treatments_labels_change_numbers =
25
+ @splits_repository.get_splits(split_names).each_with_object({}) do |(name, data), memo|
26
+ memo.merge!(name => get_treatment(key, name, attributes, data, false, true))
27
+ end
28
+
29
+ if @config.impressions_queue_size > 0
30
+ @impressions_repository.add_bulk(matching_key, bucketing_key, treatments_labels_change_numbers, (Time.now.to_f * 1000.0).to_i)
27
31
  end
28
32
 
29
- if @config.impressions_queue_size > 0
30
- @impressions_repository.add_bulk(matching_key, bucketing_key, treatments, (Time.now.to_f * 1000.0).to_i)
33
+ split_names = treatments_labels_change_numbers.keys
34
+ treatments = treatments_labels_change_numbers.values.map { |v| v[:treatment] }
35
+
36
+ Hash[split_names.zip(treatments)]
31
37
  end
32
38
 
33
- treatments
34
- end
39
+ #
40
+ # obtains the treatment for a given feature
41
+ #
42
+ # @param key [String/Hash] user id or hash with matching_key/bucketing_key
43
+ # @param split_name [String/Array] name of the feature that is being validated or array of them
44
+ # @param attributes [Hash] attributes to pass to the treatment class
45
+ # @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
46
+ # @param store_impressions [Boolean] impressions aren't stored if this flag is false
47
+ # @param multiple [Hash] internal flag to signal if method is called by get_treatments
48
+ #
49
+ # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
50
+ def get_treatment(key, split_name, attributes = nil, split_data = nil, store_impressions = true, multiple = false)
51
+ bucketing_key, matching_key = keys_from_key(key)
52
+
53
+ if matching_key.nil?
54
+ @config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
55
+ return parsed_treatment(multiple, { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL })
56
+ end
35
57
 
36
- #
37
- # obtains the treatment for a given feature
38
- #
39
- # @param key [String/Hash] user id or hash with matching_key/bucketing_key
40
- # @param split_name [String/Array] name of the feature that is being validated or array of them
41
- #
42
- # @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
43
- def get_treatment(key, split_name, attributes = nil, split_data = nil, store_impressions = true)
44
- bucketing_key, matching_key = keys_from_key(key)
45
- bucketing_key = matching_key if bucketing_key.nil?
46
-
47
- if matching_key.nil?
48
- @config.logger.warn('matching_key was null for split_name: ' + split_name.to_s)
49
- return Treatments::CONTROL
50
- end
58
+ if split_name.nil?
59
+ @config.logger.warn('split_name was null for key: ' + key)
60
+ return parsed_treatment(multiple, { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL })
61
+ end
51
62
 
52
- if split_name.nil?
53
- @config.logger.warn('split_name was null for key: ' + key)
54
- return Treatments::CONTROL
55
- end
63
+ start = Time.now
64
+ treatment_label_change_number = { label: Engine::Models::Label::EXCEPTION, treatment: Treatments::CONTROL }
56
65
 
57
- start = Time.now
58
- result = nil
66
+ begin
67
+ split = multiple ? split_data : @splits_repository.get_split(split_name)
59
68
 
60
- begin
61
- split = split_data ? split_data : @splits_repository.get_split(split_name)
69
+ if split.nil?
70
+ return parsed_treatment(multiple, treatment_label_change_number)
71
+ else
72
+ treatment_label_change_number = SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository).call(
73
+ { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
74
+ )
75
+ end
76
+ rescue StandardError => error
77
+ @config.log_found_exception(__method__.to_s, error)
62
78
 
63
- result = if split.nil?
64
- Treatments::CONTROL
65
- else
66
- SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository).call(
67
- { bucketing_key: bucketing_key, matching_key: matching_key }, split, attributes
68
- )
79
+ return parsed_treatment(multiple, treatment_label_change_number)
69
80
  end
70
- rescue StandardError => error
71
- @config.log_found_exception(__method__.to_s, error)
72
- end
73
81
 
74
- result = result.nil? ? Treatments::CONTROL : result
75
-
76
- begin
77
- latency = (Time.now - start) * 1000.0
78
- if @config.impressions_queue_size > 0 && store_impressions
79
- # Disable impressions if @config.impressions_queue_size == -1
80
- @impressions_repository.add(split_name,
81
- 'key_name' => matching_key,
82
- 'bucketing_key' => bucketing_key,
83
- 'treatment' => result,
84
- 'time' => (Time.now.to_f * 1000.0).to_i
85
- )
82
+ begin
83
+ latency = (Time.now - start) * 1000.0
84
+ if @config.impressions_queue_size > 0 && store_impressions && split
85
+ # Disable impressions if @config.impressions_queue_size == -1
86
+ @impressions_repository.add(split_name,
87
+ 'key_name' => matching_key,
88
+ 'bucketing_key' => bucketing_key,
89
+ 'treatment' => treatment_label_change_number[:treatment],
90
+ 'label' => @config.labels_enabled ? treatment_label_change_number[:label] : nil,
91
+ 'time' => (Time.now.to_f * 1000.0).to_i,
92
+ 'change_number' => treatment_label_change_number[:change_number]
93
+ )
94
+ end
95
+
96
+ # Measure
97
+ @adapter.metrics.time('sdk.get_treatment', latency)
98
+ rescue StandardError => error
99
+ @config.log_found_exception(__method__.to_s, error)
100
+
101
+ return parsed_treatment(multiple, treatment_label_change_number)
86
102
  end
87
103
 
88
- # Measure
89
- @adapter.metrics.time("sdk.get_treatment", latency)
90
- rescue StandardError => error
91
- @config.log_found_exception(__method__.to_s, error)
104
+ parsed_treatment(multiple, treatment_label_change_number)
92
105
  end
93
106
 
94
- result
95
- end
107
+ def keys_from_key(key)
108
+ case key.class.to_s
109
+ when 'Hash'
110
+ key.values_at(:bucketing_key, :matching_key)
111
+ when 'String'
112
+ [nil, key]
113
+ end
114
+ end
96
115
 
97
- def keys_from_key(key)
98
- case key.class.to_s
99
- when 'Hash'
100
- key.values_at(:bucketing_key, :matching_key)
101
- when 'String'
102
- [key, key]
116
+ def parsed_treatment(multiple, treatment_label_change_number)
117
+ if multiple
118
+ {
119
+ treatment: treatment_label_change_number[:treatment],
120
+ label: treatment_label_change_number[:label],
121
+ change_number: treatment_label_change_number[:change_number]
122
+ }
123
+ else
124
+ treatment_label_change_number[:treatment]
125
+ end
103
126
  end
104
- end
105
127
 
106
- #
107
- # method that returns the sdk gem version
108
- #
109
- # @return [string] version value for this sdk
110
- def self.sdk_version
111
- 'ruby-'+SplitIoClient::VERSION
128
+ #
129
+ # method that returns the sdk gem version
130
+ #
131
+ # @return [string] version value for this sdk
132
+ def self.sdk_version
133
+ 'ruby-'+SplitIoClient::VERSION
134
+ end
112
135
  end
113
136
  end
114
- end
@@ -52,10 +52,12 @@ module SplitIoClient
52
52
  @logger = opts[:logger] || SplitConfig.default_logger
53
53
  @debug_enabled = opts[:debug_enabled] || SplitConfig.default_debug
54
54
  @transport_debug_enabled = opts[:transport_debug_enabled] || SplitConfig.default_debug
55
- @block_until_ready = opts[:block_until_ready] || false
55
+ @block_until_ready = opts[:ready] || opts[:block_until_ready] || 0
56
56
  @machine_name = SplitConfig.get_hostname
57
57
  @machine_ip = SplitConfig.get_ip
58
58
 
59
+ @labels_enabled = opts[:labels_enabled].nil? ? SplitConfig.default_labels_logging : opts[:labels_enabled]
60
+
59
61
  startup_log
60
62
  end
61
63
 
@@ -125,6 +127,12 @@ module SplitIoClient
125
127
  # @return [Boolean] The value for the debug flag
126
128
  attr_reader :transport_debug_enabled
127
129
 
130
+ #
131
+ # Enable logging labels and sending potentially sensitive information
132
+ #
133
+ # @return [Boolean] The value for the labels enabled flag
134
+ attr_reader :labels_enabled
135
+
128
136
  #
129
137
  # The number of seconds to wait for SDK readiness
130
138
  # or false to disable waiting
@@ -161,7 +169,7 @@ module SplitIoClient
161
169
  #
162
170
  # @return [string] version value for this sdk
163
171
  def self.sdk_version
164
- 'RubyClientSDK-'+SplitIoClient::VERSION
172
+ 'ruby-'+SplitIoClient::VERSION
165
173
  end
166
174
 
167
175
  #
@@ -261,6 +269,14 @@ module SplitIoClient
261
269
  false
262
270
  end
263
271
 
272
+ #
273
+ # The default labels logging value
274
+ #
275
+ # @return [boolean]
276
+ def self.default_labels_logging
277
+ true
278
+ end
279
+
264
280
  def self.default_redis_url
265
281
  'redis://127.0.0.1:6379/0'
266
282
  end
@@ -316,12 +332,9 @@ module SplitIoClient
316
332
  #
317
333
  # @return [string]
318
334
  def self.get_ip
319
- begin
320
- Socket::getaddrinfo(Socket.gethostname, 'echo', Socket::AF_INET)[0][3]
321
- rescue
322
- #unable to get local ip
323
- '127.0.0.0'
324
- end
335
+ Socket.ip_address_list.detect { |intf| intf.ipv4_private? }.ip_address
336
+ rescue StandardError
337
+ 'unknown'
325
338
  end
326
339
  end
327
340
  end
@@ -22,7 +22,7 @@ module SplitIoClient
22
22
  @client = SplitClient.new(@api_key, @config, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository)
23
23
  @manager = SplitManager.new(@api_key, @config, @adapter, @splits_repository)
24
24
 
25
- @sdk_blocker.block if @config.block_until_ready
25
+ @sdk_blocker.block if @config.block_until_ready > 0
26
26
  end
27
27
  end
28
28
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '3.1.3'
2
+ VERSION = '3.2.0'
3
3
  end
data/runTests ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ SPLITCLIENT_ENV=test bundle exec rspec spec
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.3
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-04 00:00:00.000000000 Z
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -250,6 +250,7 @@ files:
250
250
  - NEWS
251
251
  - README.md
252
252
  - Rakefile
253
+ - console
253
254
  - exe/splitio
254
255
  - lib/cache/adapters/memory_adapter.rb
255
256
  - lib/cache/adapters/memory_adapters/map_adapter.rb
@@ -289,6 +290,7 @@ files:
289
290
  - lib/engine/matchers/whitelist_matcher.rb
290
291
  - lib/engine/metrics/binary_search_latency_tracker.rb
291
292
  - lib/engine/metrics/metrics.rb
293
+ - lib/engine/models/label.rb
292
294
  - lib/engine/models/split.rb
293
295
  - lib/engine/parser/condition.rb
294
296
  - lib/engine/parser/partition.rb
@@ -308,6 +310,7 @@ files:
308
310
  - lib/splitclient-rb/split_factory_builder.rb
309
311
  - lib/splitclient-rb/version.rb
310
312
  - lib/splitclient-rb_utilitites.rb
313
+ - runTests
311
314
  - splitclient-rb.gemspec
312
315
  - splitio.yml.example
313
316
  - tasks/benchmark_get_treatment.rake