rubcask 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +14 -0
- data/benchmark/benchmark_io.rb +49 -0
- data/benchmark/benchmark_server.rb +10 -0
- data/benchmark/benchmark_server_pipeline.rb +24 -0
- data/benchmark/benchmark_worker.rb +46 -0
- data/benchmark/op_times.rb +32 -0
- data/benchmark/profile.rb +15 -0
- data/benchmark/server_benchmark_helper.rb +138 -0
- data/example/server_runner.rb +15 -0
- data/lib/rubcask/bytes.rb +11 -0
- data/lib/rubcask/concurrency/fake_atomic_fixnum.rb +34 -0
- data/lib/rubcask/concurrency/fake_lock.rb +41 -0
- data/lib/rubcask/concurrency/fake_monitor_mixin.rb +21 -0
- data/lib/rubcask/config.rb +55 -0
- data/lib/rubcask/data_entry.rb +9 -0
- data/lib/rubcask/data_file.rb +91 -0
- data/lib/rubcask/directory.rb +437 -0
- data/lib/rubcask/expirable_entry.rb +9 -0
- data/lib/rubcask/hint_entry.rb +9 -0
- data/lib/rubcask/hint_file.rb +56 -0
- data/lib/rubcask/hinted_file.rb +148 -0
- data/lib/rubcask/keydir_entry.rb +9 -0
- data/lib/rubcask/merge_directory.rb +75 -0
- data/lib/rubcask/protocol.rb +74 -0
- data/lib/rubcask/server/abstract_server.rb +113 -0
- data/lib/rubcask/server/async.rb +78 -0
- data/lib/rubcask/server/client.rb +131 -0
- data/lib/rubcask/server/config.rb +31 -0
- data/lib/rubcask/server/pipeline.rb +49 -0
- data/lib/rubcask/server/runner/config.rb +43 -0
- data/lib/rubcask/server/runner.rb +107 -0
- data/lib/rubcask/server/threaded.rb +171 -0
- data/lib/rubcask/task/clean_directory.rb +19 -0
- data/lib/rubcask/tombstone.rb +40 -0
- data/lib/rubcask/version.rb +5 -0
- data/lib/rubcask/worker/direct_worker.rb +23 -0
- data/lib/rubcask/worker/factory.rb +42 -0
- data/lib/rubcask/worker/ractor_worker.rb +40 -0
- data/lib/rubcask/worker/thread_worker.rb +40 -0
- data/lib/rubcask.rb +19 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7d54e3884c99955ecbab6624997f84c8469d1f60ad73a34631e206efffebbeb
|
4
|
+
data.tar.gz: d2c62d450a8ebd9abfc5e12ac26c2747365afbe98ff72fe8117fc8659c02b1d0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6be6603b4238edd7a93548e6440c4dcee3263afb254b40999b774e7b8e60dc39d021457bd88a4f3cc0017a2812685c48d574aacbc1f35a0ae0eda35c46fad468
|
7
|
+
data.tar.gz: 57e451dd32e7e555e6692bbaf441370a90df623cffef4aa52169db0869ad1b99650372e20abed61ca0eefe3f1247ca4e8ae39f06d3a142d2b932713387c787dc
|
data/.standard.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in rubcask.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
|
12
|
+
gem "standard", "~> 1.20"
|
13
|
+
|
14
|
+
gem "benchmark-ips", "~> 2.10"
|
15
|
+
|
16
|
+
gem "kalibera", "~> 0.1.2"
|
17
|
+
|
18
|
+
gem "timecop", "~> 0.9.6"
|
19
|
+
|
20
|
+
gem "simplecov", "~> 0.22.0"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rubcask (0.1.0)
|
5
|
+
concurrent-ruby (~> 1.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.4.2)
|
11
|
+
benchmark-ips (2.10.0)
|
12
|
+
concurrent-ruby (1.1.10)
|
13
|
+
docile (1.4.0)
|
14
|
+
json (2.6.3)
|
15
|
+
json (2.6.3-java)
|
16
|
+
kalibera (0.1.2)
|
17
|
+
memoist (~> 0.16)
|
18
|
+
rbzip2 (~> 0.3)
|
19
|
+
language_server-protocol (3.17.0.2)
|
20
|
+
memoist (0.16.2)
|
21
|
+
minitest (5.16.3)
|
22
|
+
parallel (1.22.1)
|
23
|
+
parser (3.1.3.0)
|
24
|
+
ast (~> 2.4.1)
|
25
|
+
rainbow (3.1.1)
|
26
|
+
rake (13.0.6)
|
27
|
+
rbzip2 (0.3.0)
|
28
|
+
regexp_parser (2.6.1)
|
29
|
+
rexml (3.2.5)
|
30
|
+
rubocop (1.40.0)
|
31
|
+
json (~> 2.3)
|
32
|
+
parallel (~> 1.10)
|
33
|
+
parser (>= 3.1.2.1)
|
34
|
+
rainbow (>= 2.2.2, < 4.0)
|
35
|
+
regexp_parser (>= 1.8, < 3.0)
|
36
|
+
rexml (>= 3.2.5, < 4.0)
|
37
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
38
|
+
ruby-progressbar (~> 1.7)
|
39
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
40
|
+
rubocop-ast (1.24.1)
|
41
|
+
parser (>= 3.1.1.0)
|
42
|
+
rubocop-performance (1.15.1)
|
43
|
+
rubocop (>= 1.7.0, < 2.0)
|
44
|
+
rubocop-ast (>= 0.4.0)
|
45
|
+
ruby-progressbar (1.11.0)
|
46
|
+
simplecov (0.22.0)
|
47
|
+
docile (~> 1.1)
|
48
|
+
simplecov-html (~> 0.11)
|
49
|
+
simplecov_json_formatter (~> 0.1)
|
50
|
+
simplecov-html (0.12.3)
|
51
|
+
simplecov_json_formatter (0.1.4)
|
52
|
+
standard (1.20.0)
|
53
|
+
language_server-protocol (~> 3.17.0.2)
|
54
|
+
rubocop (= 1.40.0)
|
55
|
+
rubocop-performance (= 1.15.1)
|
56
|
+
timecop (0.9.6)
|
57
|
+
unicode-display_width (2.3.0)
|
58
|
+
|
59
|
+
PLATFORMS
|
60
|
+
ruby
|
61
|
+
universal-java-17
|
62
|
+
|
63
|
+
DEPENDENCIES
|
64
|
+
benchmark-ips (~> 2.10)
|
65
|
+
kalibera (~> 0.1.2)
|
66
|
+
minitest (~> 5.0)
|
67
|
+
rake (~> 13.0)
|
68
|
+
rubcask!
|
69
|
+
simplecov (~> 0.22.0)
|
70
|
+
standard (~> 1.20)
|
71
|
+
timecop (~> 0.9.6)
|
72
|
+
|
73
|
+
BUNDLED WITH
|
74
|
+
2.3.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Marcin Henryk Bartkowiak
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Rubcask
|
2
|
+
Rubcask is a Bitcask-like log-structured Key/Value storage library.
|
3
|
+
|
4
|
+
It ships with a TCP server and client implementing a custom protocol.
|
5
|
+
|
6
|
+
It has design very similar to bitcask including merge operation, moving to a next file after reaching a configurable limit; timestamp however is used for expiration only.
|
7
|
+
|
8
|
+
## Documentation
|
9
|
+
https://rubydoc.info/github/mhib/rubcask/master
|
10
|
+
|
11
|
+
## Disclaimer
|
12
|
+
This library is not production-ready.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'rubcask'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle install
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install rubcask
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
Rubcask's main methods are very similar to ruby's hash.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
dir = Rubcask::Directory.new("path_to_directory")
|
35
|
+
dir["key"] = "value"
|
36
|
+
dir["key"] # => "value"
|
37
|
+
dir.delete("key") # => true
|
38
|
+
dir.close
|
39
|
+
```
|
40
|
+
|
41
|
+
You can also set value with a ttl.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
dir = Rubcask::Directory.new("path_to_directory")
|
45
|
+
dir.set_with_ttl("key", "value", 10)
|
46
|
+
dir["key"] # => "value"
|
47
|
+
sleep(11)
|
48
|
+
dir["key"] # => nil
|
49
|
+
dir.close
|
50
|
+
```
|
51
|
+
|
52
|
+
Rubcask does not store encoding information and stores keys as bytes, so using utf-8 strings is the same as using ASCII strings.
|
53
|
+
The same goes with values.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
dir = Rubcask::Directory.new("path_to_directory")
|
57
|
+
dir["jeż"] = "3"
|
58
|
+
dir["jeż".b] # => 3
|
59
|
+
dir.close
|
60
|
+
```
|
61
|
+
|
62
|
+
Rubcask can be used both as a library and as a server.
|
63
|
+
|
64
|
+
See `examples/server_runner.rb` for example configuration with a TCP server and a merge worker.
|
65
|
+
|
66
|
+
See `lib/rubcask/server/client.rb` for server client
|
67
|
+
|
68
|
+
## Thread safety
|
69
|
+
By default `Rubcask::Directory` is thread safe.
|
70
|
+
|
71
|
+
Consider installing `concurrent-ruby-ext` for some performance gains.
|
72
|
+
|
73
|
+
If you do not want to pay performance penalty for synchronization, it is possible to disable thread synchronization in config.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
config = Rubcask::Config.configure { |c| c.threadsafe = false }
|
77
|
+
dir = Rubcask::Directory.new("path_to_directory", config: config)
|
78
|
+
```
|
79
|
+
|
80
|
+
It is only safe to do that when using Rubcask as a library; server implementations assumes that Directory is run with `threadsafe = true`.
|
81
|
+
|
82
|
+
## Server implementations
|
83
|
+
Projects is shipped with threaded server that does not require any dependencies, and with async server that requires `async-io`.
|
84
|
+
|
85
|
+
They implement the same custom protocol.
|
86
|
+
|
87
|
+
Generally async server is faster especially for pipelines as it buffers reads.
|
88
|
+
|
89
|
+
Note that async server might not work on JRuby.
|
90
|
+
|
91
|
+
## Supported Ruby implementations
|
92
|
+
Tested against supported versions of CRuby and JRuby. TruffleRuby currently does not work due to some `IO` incompatibilities.
|
93
|
+
|
94
|
+
## Todo
|
95
|
+
* A script in `bin/` for running server runner easily.
|
96
|
+
* (maybe) Nice drb support
|
97
|
+
* (maybe) Using trie instead of key-dir with ordered iteration support
|
98
|
+
|
99
|
+
## Development
|
100
|
+
|
101
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
102
|
+
|
103
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
104
|
+
|
105
|
+
## Contributing
|
106
|
+
|
107
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mhib/rubcask.
|
108
|
+
|
109
|
+
## License
|
110
|
+
|
111
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "standard/rake"
|
13
|
+
|
14
|
+
task default: %i[test standard]
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "benchmark/ips"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
require_relative "../lib/rubcask"
|
5
|
+
|
6
|
+
@values = Array.new(10_000) { SecureRandom.hex(128) }
|
7
|
+
|
8
|
+
Dir.mktmpdir do |path|
|
9
|
+
# This does not reflect raw performance well as there is dir creation overhead but works good enough for comparison
|
10
|
+
Benchmark.ips do |x|
|
11
|
+
x.warmup = 15
|
12
|
+
x.time = 30
|
13
|
+
|
14
|
+
x.stats = :bootstrap
|
15
|
+
x.confidence = 95
|
16
|
+
|
17
|
+
x.report("put_get_os") do
|
18
|
+
Dir.mktmpdir do |path|
|
19
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.configure { |x| x.io_strategy = :os })
|
20
|
+
|
21
|
+
(0...10_000).each do |idx|
|
22
|
+
dir[idx.to_s] = @values[idx]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report("put_get_ruby") do
|
28
|
+
Dir.mktmpdir do |path|
|
29
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.new)
|
30
|
+
|
31
|
+
(0...10_000).each do |idx|
|
32
|
+
dir[idx.to_s] = @values[idx]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report("put_get_os_sync") do
|
38
|
+
Dir.mktmpdir do |path|
|
39
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.configure { |x| x.io_strategy = :os_sync })
|
40
|
+
|
41
|
+
(0...10_000).each do |idx|
|
42
|
+
dir[idx.to_s] = @values[idx]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
x.compare!
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "./server_benchmark_helper"
|
2
|
+
|
3
|
+
include ServerBenchmarkHelper
|
4
|
+
|
5
|
+
pipeline_block = proc do |client|
|
6
|
+
client.pipelined do |pipe|
|
7
|
+
pipe.get("key")
|
8
|
+
pipe.get("unknown_key")
|
9
|
+
pipe.ping
|
10
|
+
pipe.get("key")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
without_pipeline_block = proc do |client|
|
15
|
+
client.get("key")
|
16
|
+
client.get("unknown_key")
|
17
|
+
client.ping
|
18
|
+
client.get("key")
|
19
|
+
end
|
20
|
+
|
21
|
+
run_benchmark_with_async_server("Asyc pipelined", &pipeline_block)
|
22
|
+
run_benchmark_with_async_server("Async without pipeline", &without_pipeline_block)
|
23
|
+
run_benchmark_with_threaded_server("Threaded pipelined", &pipeline_block)
|
24
|
+
run_benchmark_with_threaded_server("Threaded without pipeline", &without_pipeline_block)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "benchmark/ips"
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "securerandom"
|
5
|
+
require "tmpdir"
|
6
|
+
|
7
|
+
require_relative "../lib/rubcask/worker/factory"
|
8
|
+
require_relative "../lib/rubcask/task/clean_directory"
|
9
|
+
|
10
|
+
class SimulateIOTask
|
11
|
+
def call
|
12
|
+
10_000.times { IO.read("/dev/null") }
|
13
|
+
sleep(0.01)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@benchmark_procedure = lambda do |type, times|
|
18
|
+
times.times do
|
19
|
+
worker = Rubcask::Worker::Factory.new_worker(type)
|
20
|
+
10.times { worker.push(SimulateIOTask.new) }
|
21
|
+
100_000.times { IO.read("/dev/null") } # Simulate some IO
|
22
|
+
worker.close
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Benchmark.ips do |x|
|
27
|
+
x.warmup = 15
|
28
|
+
x.time = 30
|
29
|
+
|
30
|
+
x.stats = :bootstrap
|
31
|
+
x.confidence = 95
|
32
|
+
|
33
|
+
x.report("thread") do |times|
|
34
|
+
@benchmark_procedure[:thread, times]
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report("ractor") do |times|
|
38
|
+
@benchmark_procedure[:ractor, times]
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report("direct") do |times|
|
42
|
+
@benchmark_procedure[:direct, times]
|
43
|
+
end
|
44
|
+
|
45
|
+
x.compare!
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "benchmark/ips"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
require_relative "../lib/rubcask"
|
5
|
+
|
6
|
+
Benchmark.ips do |x|
|
7
|
+
x.time = 30
|
8
|
+
|
9
|
+
x.report("1 milion writes") do
|
10
|
+
Dir.mktmpdir do |path|
|
11
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.new)
|
12
|
+
|
13
|
+
1_000_000.times do |idx|
|
14
|
+
dir[idx.to_s] = SecureRandom.hex(128)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
x.report("1 milion writes gets") do
|
20
|
+
Dir.mktmpdir do |path|
|
21
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.new)
|
22
|
+
|
23
|
+
1_000_000.times do |idx|
|
24
|
+
dir[idx.to_s] = SecureRandom.hex(128)
|
25
|
+
end
|
26
|
+
|
27
|
+
1_000_000.times do |idx|
|
28
|
+
dir[idx.to_s]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "securerandom"
|
3
|
+
require "stackprof"
|
4
|
+
|
5
|
+
require_relative "../lib/rubcask"
|
6
|
+
|
7
|
+
StackProf.run(out: "tmp/stackprof-cpu-myapp.dump", raw: true) do
|
8
|
+
Dir.mktmpdir do |path|
|
9
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config.new)
|
10
|
+
|
11
|
+
1_000_000.times do |idx|
|
12
|
+
dir[idx.to_s] = SecureRandom.hex(128)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "concurrent"
|
2
|
+
require "descriptive_statistics/safe"
|
3
|
+
|
4
|
+
require "tempfile"
|
5
|
+
|
6
|
+
require_relative "../lib/rubcask"
|
7
|
+
require_relative "../lib/rubcask/server/client"
|
8
|
+
|
9
|
+
require_relative "../lib/rubcask/server/threaded"
|
10
|
+
require_relative "../lib/rubcask/server/async"
|
11
|
+
|
12
|
+
module ServerBenchmarkHelper
|
13
|
+
NUMBER_OF_THREADS = 128
|
14
|
+
VALUE = ("8 bytes" * Rubcask::Bytes::KILOBYTE).freeze
|
15
|
+
|
16
|
+
def run_benchmark(seconds, threads, hostname, port)
|
17
|
+
res = Concurrent::Array.new
|
18
|
+
|
19
|
+
threads.times.map do
|
20
|
+
Thread.new do
|
21
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
|
+
client = Rubcask::Server::Client.new(hostname, port)
|
23
|
+
array = []
|
24
|
+
while (task_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)) - start < seconds
|
25
|
+
task_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
26
|
+
yield(client)
|
27
|
+
array << Process.clock_gettime(Process::CLOCK_MONOTONIC) - task_start
|
28
|
+
end
|
29
|
+
res.concat(array)
|
30
|
+
client.close
|
31
|
+
end
|
32
|
+
end.map(&:join)
|
33
|
+
|
34
|
+
res.extend(DescriptiveStatistics)
|
35
|
+
{
|
36
|
+
"count" => res.size,
|
37
|
+
"median" => res.median,
|
38
|
+
"mean" => res.mean,
|
39
|
+
"standard deviation" => res.standard_deviation,
|
40
|
+
"95 percentile" => res.percentile(95),
|
41
|
+
"99 percentile" => res.percentile(99),
|
42
|
+
"99.99 percentile" => res.percentile(99.99),
|
43
|
+
"max" => res.max
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_benchmark_with_async_server(label, &block)
|
48
|
+
Dir.mktmpdir do |path|
|
49
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config::DEFAULT_SERVER_CONFIG)
|
50
|
+
dir["key"] = VALUE
|
51
|
+
|
52
|
+
config = Rubcask::Server::Config.new { |c| c.port = get_free_port }
|
53
|
+
server = Rubcask::Server::Async.new(dir, config: config)
|
54
|
+
from_thread_q = Thread::Queue.new
|
55
|
+
to_thread_q = Thread::Queue.new
|
56
|
+
server_thread = Thread.new(server) do |s|
|
57
|
+
Sync do
|
58
|
+
condition = Async::Condition.new
|
59
|
+
Async do
|
60
|
+
condition.wait
|
61
|
+
from_thread_q << nil
|
62
|
+
end
|
63
|
+
|
64
|
+
s.start(condition)
|
65
|
+
|
66
|
+
to_thread_q.pop
|
67
|
+
server.shutdown
|
68
|
+
end
|
69
|
+
end
|
70
|
+
from_thread_q.pop
|
71
|
+
begin
|
72
|
+
run_benchmark(10, 10, config.hostname, config.port, &block) # warmup
|
73
|
+
|
74
|
+
rd, wr = IO.pipe
|
75
|
+
|
76
|
+
Process.fork do
|
77
|
+
val = run_benchmark(10, NUMBER_OF_THREADS, config.hostname, config.port, &block)
|
78
|
+
rd.close
|
79
|
+
wr.write(val.to_s)
|
80
|
+
wr.close
|
81
|
+
end
|
82
|
+
|
83
|
+
wr.close
|
84
|
+
puts "#{label}: #{rd.read}"
|
85
|
+
rd.close
|
86
|
+
ensure
|
87
|
+
to_thread_q << nil
|
88
|
+
server_thread.join
|
89
|
+
dir.close
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def run_benchmark_with_threaded_server(label, &block)
|
95
|
+
Dir.mktmpdir do |path|
|
96
|
+
dir = Rubcask::Directory.new(path, config: Rubcask::Config::DEFAULT_SERVER_CONFIG)
|
97
|
+
dir["key"] = VALUE
|
98
|
+
|
99
|
+
config = Rubcask::Server::Config.new { |c| c.port = get_free_port }
|
100
|
+
server = Rubcask::Server::Threaded.new(dir, config: config)
|
101
|
+
server.setup_shutdown_pipe
|
102
|
+
begin
|
103
|
+
server.connect
|
104
|
+
rescue Errno::EADDRINUSE
|
105
|
+
retry
|
106
|
+
end
|
107
|
+
server_thread = Thread.new(server, &:start)
|
108
|
+
begin
|
109
|
+
run_benchmark(10, 10, config.hostname, config.port, &block) # warmup
|
110
|
+
rd, wr = IO.pipe
|
111
|
+
|
112
|
+
Process.fork do
|
113
|
+
rd.close
|
114
|
+
val = run_benchmark(10, NUMBER_OF_THREADS, config.hostname, config.port, &block)
|
115
|
+
wr.write(val.to_s)
|
116
|
+
wr.close
|
117
|
+
end
|
118
|
+
|
119
|
+
wr.close
|
120
|
+
puts "#{label}: #{rd.read}"
|
121
|
+
rd.close
|
122
|
+
ensure
|
123
|
+
server.shutdown
|
124
|
+
server_thread.join
|
125
|
+
dir.close
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_free_port
|
131
|
+
server = TCPServer.new("127.0.0.1", 0)
|
132
|
+
begin
|
133
|
+
server.addr[1]
|
134
|
+
ensure
|
135
|
+
server.close
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
2
|
+
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
require "rubcask"
|
6
|
+
require "rubcask/server/runner"
|
7
|
+
|
8
|
+
Dir.mktmpdir do |tmpdir|
|
9
|
+
runner_config = Rubcask::Server::Runner::Config.configure do |c|
|
10
|
+
c.directory_path = tmpdir
|
11
|
+
end
|
12
|
+
|
13
|
+
runner = Rubcask::Server::Runner.new(runner_config: runner_config)
|
14
|
+
runner.start
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Concurrency
|
5
|
+
# A fake class that implements interface of Concurrent::AtomicFixnum
|
6
|
+
# without actually doing any synchronization
|
7
|
+
class FakeAtomicFixnum
|
8
|
+
attr_accessor :value
|
9
|
+
def initialize(initial = 0)
|
10
|
+
@value = initial
|
11
|
+
end
|
12
|
+
|
13
|
+
def compare_and_set(expected, update)
|
14
|
+
@value = update if @value == expected
|
15
|
+
@value
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrement(delta = 1)
|
19
|
+
@value -= delta
|
20
|
+
@value
|
21
|
+
end
|
22
|
+
|
23
|
+
def increment(delta = 1)
|
24
|
+
@value += delta
|
25
|
+
@value
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
@value = yield(@value)
|
30
|
+
@value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubcask
|
4
|
+
module Concurrency
|
5
|
+
# Fake of Concurrent::ReentrantReadWriteLock
|
6
|
+
# It does not do any synchronization
|
7
|
+
class FakeLock
|
8
|
+
def acquire_read_lock
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def acquire_write_lock
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def release_read_lock
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def release_write_lock
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_waiters?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_write_lock
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_read_lock
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_locked?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|