splitclient-rb 4.5.1-java

Sign up to get free protection for your applications and to get access to all the features.
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