splitclient-rb 3.3.0 → 4.0.0.pre.rc1
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.
- 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
|