rubcask 0.1.0
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 +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
|