splitclient-rb 4.5.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES.txt +147 -0
  4. data/Detailed-README.md +571 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +13 -0
  7. data/NEWS +75 -0
  8. data/README.md +43 -0
  9. data/Rakefile +24 -0
  10. data/exe/splitio +96 -0
  11. data/ext/murmurhash/MurmurHash3.java +162 -0
  12. data/lib/murmurhash/base.rb +58 -0
  13. data/lib/murmurhash/murmurhash.jar +0 -0
  14. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  15. data/lib/splitclient-rb.rb +90 -0
  16. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  17. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +133 -0
  18. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +44 -0
  19. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +165 -0
  20. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +30 -0
  21. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +29 -0
  22. data/lib/splitclient-rb/cache/repositories/events_repository.rb +41 -0
  23. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +49 -0
  24. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +78 -0
  25. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +21 -0
  26. data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +129 -0
  27. data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +98 -0
  28. data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +22 -0
  29. data/lib/splitclient-rb/cache/repositories/repository.rb +23 -0
  30. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +82 -0
  31. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +106 -0
  32. data/lib/splitclient-rb/cache/routers/impression_router.rb +52 -0
  33. data/lib/splitclient-rb/cache/senders/events_sender.rb +47 -0
  34. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +73 -0
  35. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +67 -0
  36. data/lib/splitclient-rb/cache/senders/metrics_sender.rb +49 -0
  37. data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +48 -0
  38. data/lib/splitclient-rb/cache/stores/segment_store.rb +82 -0
  39. data/lib/splitclient-rb/cache/stores/split_store.rb +97 -0
  40. data/lib/splitclient-rb/clients/localhost_split_client.rb +92 -0
  41. data/lib/splitclient-rb/clients/split_client.rb +214 -0
  42. data/lib/splitclient-rb/engine/api/client.rb +74 -0
  43. data/lib/splitclient-rb/engine/api/events.rb +48 -0
  44. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +55 -0
  45. data/lib/splitclient-rb/engine/api/impressions.rb +42 -0
  46. data/lib/splitclient-rb/engine/api/metrics.rb +61 -0
  47. data/lib/splitclient-rb/engine/api/segments.rb +62 -0
  48. data/lib/splitclient-rb/engine/api/splits.rb +60 -0
  49. data/lib/splitclient-rb/engine/evaluator/splitter.rb +123 -0
  50. data/lib/splitclient-rb/engine/matchers/all_keys_matcher.rb +46 -0
  51. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +56 -0
  52. data/lib/splitclient-rb/engine/matchers/combiners.rb +9 -0
  53. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +86 -0
  54. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +21 -0
  55. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +19 -0
  56. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +30 -0
  57. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +20 -0
  58. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +26 -0
  59. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +27 -0
  60. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +54 -0
  61. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +19 -0
  62. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +53 -0
  63. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +53 -0
  64. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +24 -0
  65. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +60 -0
  66. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +23 -0
  67. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +20 -0
  68. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +26 -0
  69. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +45 -0
  70. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  71. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +128 -0
  72. data/lib/splitclient-rb/engine/metrics/metrics.rb +83 -0
  73. data/lib/splitclient-rb/engine/models/label.rb +8 -0
  74. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  75. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  76. data/lib/splitclient-rb/engine/parser/condition.rb +210 -0
  77. data/lib/splitclient-rb/engine/parser/evaluator.rb +118 -0
  78. data/lib/splitclient-rb/engine/parser/partition.rb +35 -0
  79. data/lib/splitclient-rb/engine/parser/split_adapter.rb +88 -0
  80. data/lib/splitclient-rb/exceptions/impressions_shutdown_exception.rb +4 -0
  81. data/lib/splitclient-rb/exceptions/sdk_blocker_timeout_expired_exception.rb +4 -0
  82. data/lib/splitclient-rb/localhost_split_factory.rb +13 -0
  83. data/lib/splitclient-rb/localhost_utils.rb +36 -0
  84. data/lib/splitclient-rb/managers/localhost_split_manager.rb +45 -0
  85. data/lib/splitclient-rb/managers/split_manager.rb +77 -0
  86. data/lib/splitclient-rb/split_config.rb +391 -0
  87. data/lib/splitclient-rb/split_factory.rb +35 -0
  88. data/lib/splitclient-rb/split_factory_builder.rb +16 -0
  89. data/lib/splitclient-rb/utilitites.rb +41 -0
  90. data/lib/splitclient-rb/version.rb +3 -0
  91. data/splitclient-rb.gemspec +50 -0
  92. data/splitio.yml.example +7 -0
  93. data/tasks/benchmark_get_treatment.rake +43 -0
  94. data/tasks/irb.rake +4 -0
  95. data/tasks/rspec.rake +3 -0
  96. metadata +321 -0
@@ -0,0 +1,74 @@
1
+ require 'net/http/persistent'
2
+
3
+ module SplitIoClient
4
+ module Api
5
+ class Client
6
+ RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
7
+
8
+ def get_api(url, config, api_key, params = {})
9
+ api_client.get(url, params) do |req|
10
+ req.headers = common_headers(api_key, config).merge('Accept-Encoding' => 'gzip')
11
+
12
+ req.options[:timeout] = config.read_timeout
13
+ req.options[:open_timeout] = config.connection_timeout
14
+
15
+ config.logger.debug("GET #{url} proxy: #{api_client.proxy}") if config.debug_enabled
16
+ end
17
+ rescue StandardError => e
18
+ config.logger.warn("#{e}\nURL:#{url}\nparams:#{params}")
19
+
20
+ false
21
+ end
22
+
23
+ def post_api(url, config, api_key, data, headers = {}, params = {})
24
+ api_client.post(url) do |req|
25
+ req.headers = common_headers(api_key, config)
26
+ .merge('Content-Type' => 'application/json')
27
+ .merge(headers)
28
+
29
+ req.body = data.to_json
30
+
31
+ req.options[:timeout] = config.read_timeout
32
+ req.options[:open_timeout] = config.connection_timeout
33
+
34
+ if config.transport_debug_enabled
35
+ config.logger.debug("POST #{url} #{req.body}")
36
+ elsif config.debug_enabled
37
+ config.logger.debug("POST #{url}")
38
+ end
39
+ end
40
+ rescue StandardError => e
41
+ config.logger.warn("#{e}\nURL:#{url}\ndata:#{data}\nparams:#{params}")
42
+
43
+ false
44
+ end
45
+
46
+ private
47
+
48
+ def api_client
49
+ @api_client ||= Faraday.new do |builder|
50
+ builder.use SplitIoClient::FaradayMiddleware::Gzip
51
+ builder.adapter :net_http_persistent
52
+ end
53
+ end
54
+
55
+ def common_headers(api_key, config)
56
+ {
57
+ 'Authorization' => "Bearer #{api_key}",
58
+ 'SplitSDKVersion' => "#{config.language}-#{config.version}",
59
+ 'SplitSDKMachineName' => config.machine_name,
60
+ 'SplitSDKMachineIP' => config.machine_ip,
61
+ 'Referer' => referer(config)
62
+ }
63
+ end
64
+
65
+ def referer(config)
66
+ result = "#{config.language}-#{config.version}"
67
+
68
+ result = "#{result}::#{SplitIoClient::SplitConfig.machine_hostname}" unless SplitIoClient::SplitConfig.machine_hostname == 'localhost'
69
+
70
+ result
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,48 @@
1
+ module SplitIoClient
2
+ module Api
3
+ class Events < Client
4
+ def initialize(api_key, config, events)
5
+ @config = config
6
+ @api_key = api_key
7
+ @events = events
8
+ end
9
+
10
+ def post
11
+ if @events.empty?
12
+ @config.logger.debug('No events to report') if @config.debug_enabled
13
+ return
14
+ end
15
+
16
+ @events.each_slice(@config.events_queue_size) do |event_slice|
17
+ result = post_api(
18
+ "#{@config.events_uri}/events/bulk",
19
+ @config,
20
+ @api_key,
21
+ event_slice.map { |event| formatted_event(event[:e]) },
22
+ 'SplitSDKMachineIP' => event_slice[0][:m][:i],
23
+ 'SplitSDKMachineName' => event_slice[0][:m][:n],
24
+ 'SplitSDKVersion' => event_slice[0][:m][:s]
25
+ )
26
+
27
+ if (200..299).include? result.status
28
+ @config.logger.debug("Events reported: #{event_slice.size}") if @config.debug_enabled
29
+ else
30
+ @config.logger.error("Unexpected status code while posting events: #{result.status}")
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def formatted_event(event)
38
+ {
39
+ key: event[:key],
40
+ trafficTypeName: event[:trafficTypeName],
41
+ eventTypeId: event[:eventTypeId],
42
+ value: event[:value].to_f,
43
+ timestamp: event[:timestamp].to_i
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'faraday'
2
+
3
+ module SplitIoClient
4
+ module FaradayMiddleware
5
+ class Gzip < Faraday::Middleware
6
+ ACCEPT_ENCODING = 'Accept-Encoding'.freeze
7
+ CONTENT_ENCODING = 'Content-Encoding'.freeze
8
+ CONTENT_LENGTH = 'Content-Length'.freeze
9
+ SUPPORTED_ENCODINGS = 'gzip,deflate'.freeze
10
+ RUBY_ENCODING = '1.9'.respond_to?(:force_encoding)
11
+
12
+ def call(env)
13
+ env[:request_headers][ACCEPT_ENCODING] ||= SUPPORTED_ENCODINGS
14
+ @app.call(env).on_complete do |response_env|
15
+ case response_env[:response_headers][CONTENT_ENCODING]
16
+ when 'gzip'
17
+ reset_body(response_env, &method(:uncompress_gzip))
18
+ when 'deflate'
19
+ reset_body(response_env, &method(:inflate))
20
+ end
21
+ end
22
+ end
23
+
24
+ def reset_body(env)
25
+ env[:body] = yield(env[:body])
26
+ env[:response_headers].delete(CONTENT_ENCODING)
27
+ env[:response_headers][CONTENT_LENGTH] = env[:body].length
28
+ end
29
+
30
+ def uncompress_gzip(body)
31
+ io = StringIO.new(body)
32
+ gzip_reader = if RUBY_ENCODING
33
+ Zlib::GzipReader.new(io, :encoding => 'ASCII-8BIT')
34
+ else
35
+ Zlib::GzipReader.new(io)
36
+ end
37
+ gzip_reader.read
38
+ end
39
+
40
+ def inflate(body)
41
+ # Inflate as a DEFLATE (RFC 1950+RFC 1951) stream
42
+ Zlib::Inflate.inflate(body)
43
+ rescue Zlib::DataError
44
+ # Fall back to inflating as a "raw" deflate stream which
45
+ # Microsoft servers return
46
+ inflate = Zlib::Inflate.new(-Zlib::MAX_WBITS)
47
+ begin
48
+ inflate.inflate(body)
49
+ ensure
50
+ inflate.close
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ module SplitIoClient
2
+ module Api
3
+ class Impressions < Client
4
+ def initialize(api_key, config, impressions)
5
+ @config = config
6
+ @api_key = api_key
7
+ @impressions = impressions
8
+ end
9
+
10
+ def post
11
+ if @impressions.empty?
12
+ @config.logger.debug('No impressions to report') if @config.debug_enabled
13
+ return
14
+ end
15
+
16
+ impressions_by_ip.each do |ip, impressions|
17
+ result = post_api("#{@config.events_uri}/testImpressions/bulk", @config, @api_key, impressions, 'SplitSDKMachineIP' => ip)
18
+
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
24
+ end
25
+ end
26
+
27
+ def total_impressions(impressions)
28
+ return 0 if impressions.nil?
29
+
30
+ impressions.reduce(0) do |impressions_count, impression|
31
+ impressions_count += impression[:keyImpressions].length
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def impressions_by_ip
38
+ @impressions.group_by { |impression| impression[:ip] }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
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
+ log_status(result, metrics_time.size)
27
+ end
28
+ end
29
+
30
+ @metrics_repository.clear_latencies
31
+ end
32
+
33
+ def post_counts
34
+ if @metrics_repository.counts.empty?
35
+ @config.logger.debug('No counts to report.') if @config.debug_enabled
36
+ else
37
+ @metrics_repository.counts.each do |name, count|
38
+ metrics_count = { name: name, delta: count }
39
+
40
+ result = post_api("#{@config.events_uri}/metrics/counter", @config, @api_key, metrics_count)
41
+
42
+ log_status(result, metrics_count.size)
43
+ end
44
+ end
45
+ @metrics_repository.clear_counts
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
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ module SplitIoClient
2
+ module Api
3
+ class Segments < Client
4
+ def initialize(api_key, config, metrics, segments_repository)
5
+ @config = config
6
+ @metrics = metrics
7
+ @api_key = api_key
8
+ @segments_repository = segments_repository
9
+ end
10
+
11
+ def store_segments_by_names(names)
12
+ start = Time.now
13
+ prefix = 'segmentChangeFetcher'
14
+
15
+ return if names.nil? || names.empty?
16
+
17
+ names.each do |name|
18
+ since = @segments_repository.get_change_number(name)
19
+ while true
20
+ fetch_segments(name, prefix, since).each { |segment| @segments_repository.add_to_segment(segment) }
21
+ @config.logger.debug("Segment #{name} fetched before: #{since}, till: #{@segments_repository.get_change_number(name)}") if @config.debug_enabled
22
+
23
+ break if (since.to_i >= @segments_repository.get_change_number(name).to_i)
24
+ since = @segments_repository.get_change_number(name)
25
+ end
26
+ end
27
+
28
+ latency = (Time.now - start) * 1000.0
29
+ @metrics.time(prefix + '.time', latency)
30
+ end
31
+
32
+ private
33
+
34
+ def fetch_segments(name, prefix, since)
35
+ segments = []
36
+ segment = get_api("#{@config.base_uri}/segmentChanges/#{name}", @config, @api_key, since: since)
37
+
38
+ if segment == false
39
+ @config.logger.error("Failed to make a http request")
40
+ elsif segment.status / 100 == 2
41
+ segment_content = JSON.parse(segment.body, symbolize_names: true)
42
+ @segments_repository.set_change_number(name, segment_content[:till])
43
+ @metrics.count(prefix + '.status.' + segment.status.to_s, 1)
44
+
45
+ if @config.debug_enabled
46
+ @config.logger.debug("\'#{segment_content[:name]}\' segment retrieved.")
47
+ @config.logger.debug("\'#{segment_content[:name]}\' #{segment_content[:added].size} added keys") if segment_content[:added].size > 0
48
+ @config.logger.debug("\'#{segment_content[:name]}\' #{segment_content[:removed].size} removed keys") if segment_content[:removed].size > 0
49
+ end
50
+ @config.logger.debug("#{segment_content}") if @config.transport_debug_enabled
51
+
52
+ segments << segment_content
53
+ else
54
+ @config.logger.error("Unexpected result from API Call for Segment #{name} status #{segment.status.to_s} since #{since.to_s}")
55
+ @metrics.count(prefix + '.status.' + segment.status.to_s, 1)
56
+ end
57
+
58
+ segments
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,60 @@
1
+ module SplitIoClient
2
+ module Api
3
+ class Splits < Client
4
+ def initialize(api_key, config, metrics)
5
+ @api_key = api_key
6
+ @config = config
7
+ @metrics = metrics
8
+ end
9
+
10
+ def since(since)
11
+ start = Time.now
12
+ prefix = 'splitChangeFetcher'
13
+ splits = get_api("#{@config.base_uri}/splitChanges", @config, @api_key, since: since)
14
+
15
+ if splits == false
16
+ @config.logger.error("Failed to make a http request")
17
+ elsif splits.status / 100 == 2
18
+ result = splits_with_segment_names(splits.body)
19
+
20
+ @metrics.count(prefix + '.status.' + splits.status.to_s, 1)
21
+
22
+ @config.logger.debug("#{result[:splits].length} splits retrieved. since=#{since}") if @config.debug_enabled and result[:splits].length > 0
23
+ @config.logger.debug("#{result}") if @config.transport_debug_enabled
24
+ else
25
+ @metrics.count(prefix + '.status.' + splits.status.to_s, 1)
26
+
27
+ @config.logger.error('Unexpected result from Splits API call')
28
+ end
29
+
30
+ latency = (Time.now - start) * 1000.0
31
+ @metrics.time(prefix + '.time', latency)
32
+
33
+ result
34
+ end
35
+
36
+ private
37
+
38
+ def splits_with_segment_names(splits_json)
39
+ parsed_splits = JSON.parse(splits_json, symbolize_names: true)
40
+
41
+ parsed_splits[:segment_names] =
42
+ parsed_splits[:splits].each_with_object(Set.new) do |split, splits|
43
+ splits << segment_names(split)
44
+ end.flatten
45
+
46
+ parsed_splits
47
+ end
48
+
49
+ def segment_names(split)
50
+ split[:conditions].each_with_object(Set.new) do |condition, names|
51
+ condition[:matcherGroup][:matchers].each do |matcher|
52
+ next if matcher[:userDefinedSegmentMatcherData].nil?
53
+
54
+ names << matcher[:userDefinedSegmentMatcherData][:segmentName]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,123 @@
1
+ module SplitIoClient
2
+ # Misc class in charge of providing hash functions and
3
+ # determination of treatment based on concept of buckets
4
+ # based on provided key
5
+ #
6
+ class Splitter < NoMethodError
7
+ def initialize
8
+ @murmur_hash = case RUBY_PLATFORM
9
+ when 'java'
10
+ Proc.new { |key, seed| Java::MurmurHash3.murmurhash3_x86_32(key, seed) }
11
+ else
12
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x86_32.rawdigest(key, [seed].pack('L')) }
13
+ end
14
+ end
15
+
16
+ #
17
+ # Checks if the partiotion size is 100%
18
+ #
19
+ # @param partitions [object] array of partitions
20
+ #
21
+ # @return [boolean] true if partition is 100% false otherwise
22
+ def hundred_percent_one_treatment?(partitions)
23
+ (partitions.size != 1) ? false : (partitions.first.size == 100)
24
+ end
25
+
26
+ #
27
+ # gets the appropriate treatment based on id, seed and partition value
28
+ #
29
+ # @param id [string] user key
30
+ # @param seed [number] seed for the user key
31
+ # @param partitions [object] array of partitions
32
+ #
33
+ # @return traetment [object] treatment value
34
+ def get_treatment(id, seed, partitions, legacy_algo)
35
+ legacy = [1, nil].include?(legacy_algo)
36
+
37
+ if partitions.empty?
38
+ return SplitIoClient::Engine::Models::Treatment::CONTROL
39
+ end
40
+
41
+ if hundred_percent_one_treatment?(partitions)
42
+ return (partitions.first).treatment
43
+ end
44
+
45
+ return get_treatment_for_key(bucket(count_hash(id, seed, legacy_algo)), partitions)
46
+ end
47
+
48
+ # returns a hash value for the give key, seed pair
49
+ #
50
+ # @param key [String] user key
51
+ # @param seed [Fixnum] seed for the user key
52
+ #
53
+ # @return hash [String] hash value
54
+ def count_hash(key, seed, legacy)
55
+ legacy ? legacy_hash(key, seed) : murmur_hash(key, seed)
56
+ end
57
+
58
+ def murmur_hash(key, seed)
59
+ @murmur_hash.call(key, seed)
60
+ end
61
+
62
+ def legacy_hash(key, seed)
63
+ h = 0
64
+
65
+ for i in 0..key.length-1
66
+ h = to_int32(31 * h + key[i].ord)
67
+ end
68
+
69
+ h^seed
70
+ end
71
+
72
+ #
73
+ # misc method to convert ruby number to int 32 since overflow is handled different to java
74
+ #
75
+ # @param number [number] ruby number value
76
+ #
77
+ # @return [int] returns the int 32 value of the provided number
78
+ def to_int32(number)
79
+ begin
80
+ sign = number < 0 ? -1 : 1
81
+ abs = number.abs
82
+ return 0 if abs == 0 || abs == Float::INFINITY
83
+ rescue
84
+ return 0
85
+ end
86
+
87
+ pos_int = sign * abs.floor
88
+ int_32bit = pos_int % 2**32
89
+
90
+ return int_32bit - 2**32 if int_32bit >= 2**31
91
+ int_32bit
92
+ end
93
+
94
+ #
95
+ # returns the treatment for a bucket given the partitions
96
+ #
97
+ # @param bucket [number] bucket value
98
+ # @param parittions [object] array of partitions
99
+ #
100
+ # @return treatment [treatment] treatment value for this bucket and partitions
101
+ def get_treatment_for_key(bucket, partitions)
102
+ buckets_covered_thus_far = 0
103
+ partitions.each do |p|
104
+ unless p.is_empty?
105
+ buckets_covered_thus_far += p.size
106
+ return p.treatment if buckets_covered_thus_far >= bucket
107
+ end
108
+ end
109
+
110
+ return SplitIoClient::Engine::Models::Treatment::CONTROL
111
+ end
112
+
113
+ #
114
+ # returns bucket value for the given hash value
115
+ #
116
+ # @param hash_value [string] hash value
117
+ #
118
+ # @return bucket [number] bucket number
119
+ def bucket(hash_value)
120
+ (hash_value.abs % 100) + 1
121
+ end
122
+ end
123
+ end