splitclient-rb 3.3.0 → 4.0.0.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.txt +4 -0
- data/Detailed-README.md +8 -0
- data/NEWS +5 -0
- data/lib/cache/adapters/memory_adapters/map_adapter.rb +1 -1
- data/lib/engine/evaluator/splitter.rb +93 -85
- data/lib/engine/parser/split_treatment.rb +4 -2
- data/lib/splitclient-rb/clients/split_client.rb +8 -3
- data/lib/splitclient-rb/split_config.rb +10 -6
- data/lib/splitclient-rb/version.rb +1 -1
- data/splitclient-rb.gemspec +2 -0
- data/tasks/benchmark_hashing_algorithm.rake +51 -0
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6868ad86ef9ff027cff7dbdb58cfa973c7d02d2
|
4
|
+
data.tar.gz: 254804356acd6e57dedd0f01fcec6549374651e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d6f2d5097557c0eda262290c9d80a6a031e1c2b7f4acab2dc3fb163113136241435ccbbe137cd5d5e63f37d46dcea2c9514d0269a792f3ff7f4ea9359db8bc3
|
7
|
+
data.tar.gz: 7a164d6e280b8223108d418392d7051ef2b9f7b3d78389aea46d772180b35af68264cdc3e0943f2634c1ccac0a4b1cfa9fbaf243befc3058a39426193d8b0f0d
|
data/CHANGES.txt
CHANGED
data/Detailed-README.md
CHANGED
@@ -467,6 +467,14 @@ This will generate a file gemspec with the right version, then:
|
|
467
467
|
gem push splitclient-rb-<VERSION>.gem
|
468
468
|
```
|
469
469
|
|
470
|
+
## Benchmarking
|
471
|
+
|
472
|
+
To benchmark hashing algorithms (currently we're using MurmurHash) you'll need to run:
|
473
|
+
|
474
|
+
```bash
|
475
|
+
bundle exec rake benchmark_hashing_algorithm
|
476
|
+
```
|
477
|
+
|
470
478
|
## Contributing
|
471
479
|
|
472
480
|
Bug reports and pull requests are welcome on GitHub at https://github.com/splitio/ruby-client.
|
data/NEWS
CHANGED
@@ -1,110 +1,118 @@
|
|
1
|
+
require 'digest/murmurhash'
|
2
|
+
|
1
3
|
module SplitIoClient
|
2
4
|
# Misc class in charge of providing hash functions and
|
3
5
|
# determination of treatment based on concept of buckets
|
4
6
|
# based on provided key
|
5
7
|
#
|
6
8
|
class Splitter < NoMethodError
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
return false
|
9
|
+
class << self
|
10
|
+
#
|
11
|
+
# Checks if the partiotion size is 100%
|
12
|
+
#
|
13
|
+
# @param partitions [object] array of partitions
|
14
|
+
#
|
15
|
+
# @return [boolean] true if partition is 100% false otherwise
|
16
|
+
def hundred_percent_one_treatment?(partitions)
|
17
|
+
(partitions.size != 1) ? false : (partitions.first.size == 100)
|
17
18
|
end
|
18
|
-
return (partitions.first).size == 100
|
19
|
-
end
|
20
19
|
|
20
|
+
#
|
21
|
+
# gets the appropriate treatment based on id, seed and partition value
|
22
|
+
#
|
23
|
+
# @param id [string] user key
|
24
|
+
# @param seed [number] seed for the user key
|
25
|
+
# @param partitions [object] array of partitions
|
26
|
+
#
|
27
|
+
# @return traetment [object] treatment value
|
28
|
+
def get_treatment(id, seed, partitions, legacy_algo)
|
29
|
+
legacy = (legacy_algo == 1 || legacy_algo == nil) ? true : false
|
21
30
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# @param id [string] user key
|
26
|
-
# @param seed [number] seed for the user key
|
27
|
-
# @param partitions [object] array of partitions
|
28
|
-
#
|
29
|
-
# @return traetment [object] treatment value
|
30
|
-
def self.get_treatment(id, seed, partitions)
|
31
|
-
if partitions.empty?
|
32
|
-
return Treatments::CONTROL
|
33
|
-
end
|
31
|
+
if partitions.empty?
|
32
|
+
return Treatments::CONTROL
|
33
|
+
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
if hundred_percent_one_treatment?(partitions)
|
36
|
+
return (partitions.first).treatment
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
return get_treatment_for_key(bucket(count_hash(id, seed, legacy_algo)), partitions)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
h = 0
|
51
|
-
for i in 0..key.length-1
|
52
|
-
h = to_int32(31 * h + key[i].ord)
|
42
|
+
# returns a hash value for the give key, seed pair
|
43
|
+
#
|
44
|
+
# @param key [String] user key
|
45
|
+
# @param seed [Fixnum] seed for the user key
|
46
|
+
#
|
47
|
+
# @return hash [String] hash value
|
48
|
+
def count_hash(key, seed, legacy)
|
49
|
+
legacy ? legacy_hash(key, seed) : murmur_hash(key, seed)
|
53
50
|
end
|
54
|
-
h^seed
|
55
|
-
end
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
# @param number [number] ruby number value
|
61
|
-
#
|
62
|
-
# @return [int] returns the int 32 value of the provided number
|
63
|
-
def self.to_int32(number)
|
64
|
-
begin
|
65
|
-
sign = number < 0 ? -1 : 1
|
66
|
-
abs = number.abs
|
67
|
-
return 0 if abs == 0 || abs == Float::INFINITY
|
68
|
-
rescue
|
69
|
-
return 0
|
52
|
+
def murmur_hash(key, seed)
|
53
|
+
Digest::MurmurHash3_x86_32.rawdigest(key, [seed].pack('L'))
|
70
54
|
end
|
71
55
|
|
72
|
-
|
73
|
-
|
56
|
+
def legacy_hash(key, seed)
|
57
|
+
h = 0
|
74
58
|
|
75
|
-
|
76
|
-
|
77
|
-
|
59
|
+
for i in 0..key.length-1
|
60
|
+
h = to_int32(31 * h + key[i].ord)
|
61
|
+
end
|
78
62
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
63
|
+
h^seed
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# misc method to convert ruby number to int 32 since overflow is handled different to java
|
68
|
+
#
|
69
|
+
# @param number [number] ruby number value
|
70
|
+
#
|
71
|
+
# @return [int] returns the int 32 value of the provided number
|
72
|
+
def to_int32(number)
|
73
|
+
begin
|
74
|
+
sign = number < 0 ? -1 : 1
|
75
|
+
abs = number.abs
|
76
|
+
return 0 if abs == 0 || abs == Float::INFINITY
|
77
|
+
rescue
|
78
|
+
return 0
|
92
79
|
end
|
80
|
+
|
81
|
+
pos_int = sign * abs.floor
|
82
|
+
int_32bit = pos_int % 2**32
|
83
|
+
|
84
|
+
return int_32bit - 2**32 if int_32bit >= 2**31
|
85
|
+
int_32bit
|
93
86
|
end
|
94
87
|
|
95
|
-
|
96
|
-
|
88
|
+
#
|
89
|
+
# returns the treatment for a bucket given the partitions
|
90
|
+
#
|
91
|
+
# @param bucket [number] bucket value
|
92
|
+
# @param parittions [object] array of partitions
|
93
|
+
#
|
94
|
+
# @return treatment [treatment] treatment value for this bucket and partitions
|
95
|
+
def get_treatment_for_key(bucket, partitions)
|
96
|
+
buckets_covered_thus_far = 0
|
97
|
+
partitions.each do |p|
|
98
|
+
unless p.is_empty?
|
99
|
+
buckets_covered_thus_far += p.size
|
100
|
+
return p.treatment if buckets_covered_thus_far >= bucket
|
101
|
+
end
|
102
|
+
end
|
97
103
|
|
98
|
-
|
99
|
-
|
100
|
-
#
|
101
|
-
# @param hash_value [string] hash value
|
102
|
-
#
|
103
|
-
# @return bucket [number] bucket number
|
104
|
-
def self.bucket(hash_value)
|
105
|
-
(hash_value.abs % 100) + 1
|
106
|
-
end
|
104
|
+
return Treatments::CONTROL
|
105
|
+
end
|
107
106
|
|
107
|
+
#
|
108
|
+
# returns bucket value for the given hash value
|
109
|
+
#
|
110
|
+
# @param hash_value [string] hash value
|
111
|
+
#
|
112
|
+
# @return bucket [number] bucket number
|
113
|
+
def bucket(hash_value)
|
114
|
+
(hash_value.abs % 100) + 1
|
115
|
+
end
|
116
|
+
end
|
108
117
|
end
|
109
|
-
|
110
118
|
end
|
@@ -23,6 +23,7 @@ module SplitIoClient
|
|
23
23
|
def match(split, keys, attributes)
|
24
24
|
in_rollout = false
|
25
25
|
key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
|
26
|
+
legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false
|
26
27
|
|
27
28
|
split[:conditions].each do |c|
|
28
29
|
condition = SplitIoClient::Condition.new(c)
|
@@ -31,7 +32,7 @@ module SplitIoClient
|
|
31
32
|
|
32
33
|
if !in_rollout && condition.type == SplitIoClient::Condition::TYPE_ROLLOUT
|
33
34
|
if split[:trafficAllocation] < 100
|
34
|
-
bucket = Splitter.bucket(Splitter.
|
35
|
+
bucket = Splitter.bucket(Splitter.count_hash(key, split[:trafficAllocationSeed].to_i, legacy_algo))
|
35
36
|
|
36
37
|
if bucket >= split[:trafficAllocation]
|
37
38
|
return treatment(Models::Label::NOT_IN_SPLIT, @default_treatment, split[:changeNumber])
|
@@ -42,7 +43,8 @@ module SplitIoClient
|
|
42
43
|
end
|
43
44
|
|
44
45
|
if matcher_type(condition).match?(keys[:matching_key], attributes)
|
45
|
-
|
46
|
+
key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
|
47
|
+
result = Splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo])
|
46
48
|
|
47
49
|
if result.nil?
|
48
50
|
return treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module SplitIoClient
|
2
2
|
class SplitClient
|
3
|
-
|
4
3
|
#
|
5
4
|
# Creates a new split client instance that connects to split.io API.
|
6
5
|
#
|
@@ -79,7 +78,7 @@ module SplitIoClient
|
|
79
78
|
|
80
79
|
store_impression(
|
81
80
|
split_name, matching_key, bucketing_key,
|
82
|
-
{ treatment: SplitIoClient::Treatments::CONTROL, label: Models::Label::EXCEPTION },
|
81
|
+
{ treatment: SplitIoClient::Treatments::CONTROL, label: Engine::Models::Label::EXCEPTION },
|
83
82
|
store_impressions
|
84
83
|
)
|
85
84
|
|
@@ -98,7 +97,7 @@ module SplitIoClient
|
|
98
97
|
|
99
98
|
store_impression(
|
100
99
|
split_name, matching_key, bucketing_key,
|
101
|
-
{ treatment: SplitIoClient::Treatments::CONTROL, label: Models::Label::EXCEPTION },
|
100
|
+
{ treatment: SplitIoClient::Treatments::CONTROL, label: Engine::Models::Label::EXCEPTION },
|
102
101
|
store_impressions
|
103
102
|
)
|
104
103
|
|
@@ -141,5 +140,11 @@ module SplitIoClient
|
|
141
140
|
treatment_label_change_number[:treatment]
|
142
141
|
end
|
143
142
|
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def split_treatment
|
147
|
+
@split_treatment ||= SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository)
|
148
|
+
end
|
144
149
|
end
|
145
150
|
end
|
@@ -185,12 +185,7 @@ module SplitIoClient
|
|
185
185
|
def self.init_cache_adapter(adapter, data_structure, redis_url = nil, impressions_queue_size = nil)
|
186
186
|
case adapter
|
187
187
|
when :memory
|
188
|
-
|
189
|
-
adapter = SplitIoClient::Cache::Adapters::MemoryAdapters.const_get(
|
190
|
-
data_structure.to_s.split('_').collect(&:capitalize).join
|
191
|
-
).new(impressions_queue_size)
|
192
|
-
|
193
|
-
SplitIoClient::Cache::Adapters::MemoryAdapter.new(adapter)
|
188
|
+
SplitIoClient::Cache::Adapters::MemoryAdapter.new(map_memory_adapter(data_structure, impressions_queue_size))
|
194
189
|
when :redis
|
195
190
|
begin
|
196
191
|
require 'redis'
|
@@ -202,6 +197,15 @@ module SplitIoClient
|
|
202
197
|
end
|
203
198
|
end
|
204
199
|
|
200
|
+
def self.map_memory_adapter(name, queue_size)
|
201
|
+
case name
|
202
|
+
when :map_adapter
|
203
|
+
SplitIoClient::Cache::Adapters::MemoryAdapters::MapAdapter.new
|
204
|
+
when :queue_adapter
|
205
|
+
SplitIoClient::Cache::Adapters::MemoryAdapters::QueueAdapter.new(queue_size)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
205
209
|
def self.default_mode
|
206
210
|
:standalone
|
207
211
|
end
|
data/splitclient-rb.gemspec
CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency "pry"
|
28
28
|
spec.add_development_dependency "pry-byebug"
|
29
29
|
spec.add_development_dependency "simplecov"
|
30
|
+
spec.add_development_dependency "allocation_stats"
|
30
31
|
|
31
32
|
spec.add_runtime_dependency "json", "~> 1.8"
|
32
33
|
spec.add_runtime_dependency "thread_safe"
|
@@ -36,4 +37,5 @@ Gem::Specification.new do |spec|
|
|
36
37
|
spec.add_runtime_dependency "faraday_middleware"
|
37
38
|
spec.add_runtime_dependency "net-http-persistent", "<= 2.9.4"
|
38
39
|
spec.add_runtime_dependency "redis"
|
40
|
+
spec.add_runtime_dependency "digest-murmurhash"
|
39
41
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'digest/murmurhash'
|
3
|
+
|
4
|
+
desc 'Benchmark murmur32 hashing algorithm'
|
5
|
+
|
6
|
+
task :benchmark_hashing_algorithm do
|
7
|
+
iterations = 200_000
|
8
|
+
key = SecureRandom.uuid
|
9
|
+
|
10
|
+
Benchmark.bmbm do |x|
|
11
|
+
x.report('MurmurHash1') do
|
12
|
+
iterations.times { Digest::MurmurHash1.rawdigest(key) }
|
13
|
+
end
|
14
|
+
|
15
|
+
x.report('MurmurHash2') do
|
16
|
+
iterations.times { Digest::MurmurHash2.rawdigest(key) }
|
17
|
+
end
|
18
|
+
|
19
|
+
x.report('MurmurHash2A') do
|
20
|
+
iterations.times { Digest::MurmurHash2A.rawdigest(key) }
|
21
|
+
end
|
22
|
+
|
23
|
+
x.report('LegacyHash') do
|
24
|
+
iterations.times { legacy_hash(key, 123) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def legacy_hash(key, seed)
|
30
|
+
h = 0
|
31
|
+
for i in 0..key.length-1
|
32
|
+
h = to_int32(31 * h + key[i].ord)
|
33
|
+
end
|
34
|
+
h^seed
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_int32(number)
|
38
|
+
begin
|
39
|
+
sign = number < 0 ? -1 : 1
|
40
|
+
abs = number.abs
|
41
|
+
return 0 if abs == 0 || abs == Float::INFINITY
|
42
|
+
rescue
|
43
|
+
return 0
|
44
|
+
end
|
45
|
+
|
46
|
+
pos_int = sign * abs.floor
|
47
|
+
int_32bit = pos_int % 2**32
|
48
|
+
|
49
|
+
return int_32bit - 2**32 if int_32bit >= 2**31
|
50
|
+
int_32bit
|
51
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Split Software
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: allocation_stats
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: json
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -234,6 +248,20 @@ dependencies:
|
|
234
248
|
- - ">="
|
235
249
|
- !ruby/object:Gem::Version
|
236
250
|
version: '0'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: digest-murmurhash
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
type: :runtime
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
237
265
|
description: Ruby client for using split SDK.
|
238
266
|
email:
|
239
267
|
- pato@split.io
|
@@ -313,6 +341,7 @@ files:
|
|
313
341
|
- splitclient-rb.gemspec
|
314
342
|
- splitio.yml.example
|
315
343
|
- tasks/benchmark_get_treatment.rake
|
344
|
+
- tasks/benchmark_hashing_algorithm.rake
|
316
345
|
- tasks/console.rake
|
317
346
|
- tasks/rspec.rake
|
318
347
|
homepage: https://github.com/splitio/ruby-client
|
@@ -330,9 +359,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
330
359
|
version: '0'
|
331
360
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
332
361
|
requirements:
|
333
|
-
- - "
|
362
|
+
- - ">"
|
334
363
|
- !ruby/object:Gem::Version
|
335
|
-
version:
|
364
|
+
version: 1.3.1
|
336
365
|
requirements: []
|
337
366
|
rubyforge_project:
|
338
367
|
rubygems_version: 2.5.2
|