ublk 1.0.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/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/Rakefile +42 -0
- data/examples/encrypted.rb +44 -0
- data/examples/fault_injection.rb +31 -0
- data/examples/http_disk.rb +26 -0
- data/examples/loop.rb +21 -0
- data/examples/ramdisk.rb +26 -0
- data/examples/tracing.rb +25 -0
- data/ext/ublk/compat.h +188 -0
- data/ext/ublk/constants.c +18 -0
- data/ext/ublk/control.c +292 -0
- data/ext/ublk/extconf.rb +16 -0
- data/ext/ublk/internal.h +43 -0
- data/ext/ublk/server.c +413 -0
- data/ext/ublk/ublk.c +34 -0
- data/lib/ublk/control.rb +46 -0
- data/lib/ublk/device.rb +182 -0
- data/lib/ublk/device_info.rb +14 -0
- data/lib/ublk/params.rb +35 -0
- data/lib/ublk/target.rb +42 -0
- data/lib/ublk/version.rb +5 -0
- data/lib/ublk.rb +36 -0
- data/sig/ublk.rbs +96 -0
- data/tools/bench.rb +34 -0
- data/tools/dump_constants.c +19 -0
- data/tools/vm/Makefile +5 -0
- data/tools/vm/config-fragment +4 -0
- data/tools/vm/guest-test.sh +6 -0
- data/tools/vm/run.sh +5 -0
- metadata +75 -0
data/lib/ublk/params.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UBLK
|
|
4
|
+
Params = Data.define(
|
|
5
|
+
:size, :logical_block_size, :physical_block_size, :max_io_bytes,
|
|
6
|
+
:read_only, :rotational, :discard
|
|
7
|
+
) do
|
|
8
|
+
def initialize(size:, logical_block_size: 512, physical_block_size: 4096,
|
|
9
|
+
max_io_bytes: 512 * 1024, read_only: false, rotational: false,
|
|
10
|
+
discard: true)
|
|
11
|
+
[logical_block_size, physical_block_size].each do |block_size|
|
|
12
|
+
valid = block_size.is_a?(Integer) && block_size.between?(512, 4096) && (block_size & (block_size - 1)).zero?
|
|
13
|
+
raise ArgumentError, "block sizes must be powers of two between 512 and 4096" unless valid
|
|
14
|
+
end
|
|
15
|
+
raise ArgumentError, "physical block size must not be smaller than logical block size" if physical_block_size < logical_block_size
|
|
16
|
+
raise ArgumentError, "size must be aligned to the logical block size" unless (size % logical_block_size).zero?
|
|
17
|
+
raise ArgumentError, "max_io_bytes must be a positive multiple of 512" unless max_io_bytes.is_a?(Integer) && max_io_bytes.positive? && (max_io_bytes % 512).zero?
|
|
18
|
+
raise ArgumentError, "max_io_bytes must not exceed 32 MiB" if max_io_bytes > 32 * 1024 * 1024
|
|
19
|
+
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.from_target(target, max_io_bytes: 512 * 1024)
|
|
24
|
+
new(
|
|
25
|
+
size: target.size,
|
|
26
|
+
logical_block_size: target.logical_block_size,
|
|
27
|
+
physical_block_size: target.physical_block_size,
|
|
28
|
+
max_io_bytes:,
|
|
29
|
+
read_only: target.read_only?,
|
|
30
|
+
rotational: target.rotational?,
|
|
31
|
+
discard: target.method(:discard).owner != Target || target.method(:write_zeroes).owner != Target
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/ublk/target.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module UBLK
|
|
4
|
+
class Target
|
|
5
|
+
attr_reader :size, :queues, :depth
|
|
6
|
+
|
|
7
|
+
def initialize(size:, queues: 1, depth: 128)
|
|
8
|
+
raise ArgumentError, "size must be a positive multiple of 512" unless size.is_a?(Integer) && size.positive? && (size % 512).zero?
|
|
9
|
+
raise ArgumentError, "queues must be between 1 and 4096" unless queues.is_a?(Integer) && queues.between?(1, 4096)
|
|
10
|
+
raise ArgumentError, "depth must be between 1 and 4096" unless depth.is_a?(Integer) && depth.between?(1, 4096)
|
|
11
|
+
|
|
12
|
+
@size = size
|
|
13
|
+
@queues = queues
|
|
14
|
+
@depth = depth
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read(_offset, _length)
|
|
18
|
+
raise NotImplementedError, "targets must implement #read"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def write(_offset, _data)
|
|
22
|
+
raise Errno::EROFS if read_only?
|
|
23
|
+
|
|
24
|
+
raise NotImplementedError, "targets must implement #write"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def flush = 0
|
|
28
|
+
|
|
29
|
+
def discard(_offset, _length)
|
|
30
|
+
raise Errno::EOPNOTSUPP
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def write_zeroes(_offset, _length)
|
|
34
|
+
raise Errno::EOPNOTSUPP
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def logical_block_size = 512
|
|
38
|
+
def physical_block_size = 4096
|
|
39
|
+
def read_only? = false
|
|
40
|
+
def rotational? = false
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/ublk/version.rb
ADDED
data/lib/ublk.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ublk/version"
|
|
4
|
+
require_relative "ublk/target"
|
|
5
|
+
require_relative "ublk/device_info"
|
|
6
|
+
require_relative "ublk/params"
|
|
7
|
+
|
|
8
|
+
module UBLK
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
class UnsupportedError < Error; end
|
|
11
|
+
class DeviceError < Error; end
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
require "ublk/ublk"
|
|
15
|
+
rescue LoadError => error
|
|
16
|
+
@native_load_error = error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
attr_reader :native_load_error
|
|
21
|
+
|
|
22
|
+
def supported?
|
|
23
|
+
!!(defined?(Native) && Native.supported?)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ensure_supported!
|
|
27
|
+
return if supported?
|
|
28
|
+
|
|
29
|
+
detail = native_load_error&.message || "/dev/ublk-control is unavailable"
|
|
30
|
+
raise UnsupportedError, "ublk requires Linux 6.6+, ublk_drv, and liburing (#{detail})"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
require_relative "ublk/control"
|
|
36
|
+
require_relative "ublk/device"
|
data/sig/ublk.rbs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module UBLK
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class UnsupportedError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class DeviceError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.supported?: () -> bool
|
|
14
|
+
def self.ensure_supported!: () -> nil
|
|
15
|
+
def self.native_load_error: () -> LoadError?
|
|
16
|
+
|
|
17
|
+
class Target
|
|
18
|
+
attr_reader size: Integer
|
|
19
|
+
attr_reader queues: Integer
|
|
20
|
+
attr_reader depth: Integer
|
|
21
|
+
|
|
22
|
+
def initialize: (size: Integer, ?queues: Integer, ?depth: Integer) -> void
|
|
23
|
+
def read: (Integer offset, Integer length) -> String
|
|
24
|
+
def write: (Integer offset, String data) -> Integer
|
|
25
|
+
def flush: () -> Integer
|
|
26
|
+
def discard: (Integer offset, Integer length) -> Integer
|
|
27
|
+
def write_zeroes: (Integer offset, Integer length) -> Integer
|
|
28
|
+
def logical_block_size: () -> Integer
|
|
29
|
+
def physical_block_size: () -> Integer
|
|
30
|
+
def read_only?: () -> bool
|
|
31
|
+
def rotational?: () -> bool
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class DeviceInfo
|
|
35
|
+
attr_reader id: Integer
|
|
36
|
+
attr_reader queues: Integer
|
|
37
|
+
attr_reader depth: Integer
|
|
38
|
+
attr_reader state: Integer
|
|
39
|
+
attr_reader max_io_bytes: Integer
|
|
40
|
+
attr_reader pid: Integer
|
|
41
|
+
attr_reader flags: Integer
|
|
42
|
+
attr_reader owner_uid: Integer
|
|
43
|
+
attr_reader owner_gid: Integer
|
|
44
|
+
|
|
45
|
+
def path: () -> String
|
|
46
|
+
def char_path: () -> String
|
|
47
|
+
def self.from_native: (Array[Integer] values) -> DeviceInfo
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Params
|
|
51
|
+
attr_reader size: Integer
|
|
52
|
+
attr_reader logical_block_size: Integer
|
|
53
|
+
attr_reader physical_block_size: Integer
|
|
54
|
+
attr_reader max_io_bytes: Integer
|
|
55
|
+
attr_reader read_only: bool
|
|
56
|
+
attr_reader rotational: bool
|
|
57
|
+
attr_reader discard: bool
|
|
58
|
+
|
|
59
|
+
def initialize: (size: Integer, ?logical_block_size: Integer, ?physical_block_size: Integer, ?max_io_bytes: Integer, ?read_only: bool, ?rotational: bool, ?discard: bool) -> void
|
|
60
|
+
def self.from_target: (Target target, ?max_io_bytes: Integer) -> Params
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Control
|
|
64
|
+
SYSFS: String
|
|
65
|
+
|
|
66
|
+
def initialize: () -> void
|
|
67
|
+
def add_dev: (?id: Integer?, ?queues: Integer, ?depth: Integer, ?max_io_bytes: Integer, ?recovery: bool) -> DeviceInfo
|
|
68
|
+
def set_params: (Integer id, Params params) -> Params
|
|
69
|
+
def start_dev: (Integer id, ?pid: Integer) -> bool
|
|
70
|
+
def stop_dev: (Integer id) -> bool
|
|
71
|
+
def del_dev: (Integer id) -> bool
|
|
72
|
+
def start_user_recovery: (Integer id) -> bool
|
|
73
|
+
def end_user_recovery: (Integer id, ?pid: Integer) -> bool
|
|
74
|
+
def get_dev_info: (Integer id) -> DeviceInfo
|
|
75
|
+
def list: () -> Array[DeviceInfo]
|
|
76
|
+
def close: () -> nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class Device
|
|
80
|
+
attr_reader target: Target
|
|
81
|
+
attr_reader info: DeviceInfo
|
|
82
|
+
attr_reader params: Params
|
|
83
|
+
|
|
84
|
+
def self.create: (Target target, ?id: Integer?, ?queues: Integer, ?depth: Integer, ?max_io_bytes: Integer, ?recovery: bool, ?mlock: bool) -> Device
|
|
85
|
+
def self.recover: (Target target, id: Integer, ?mlock: bool) -> Device
|
|
86
|
+
def self.list: () -> Array[DeviceInfo]
|
|
87
|
+
def self.delete_all!: () -> void
|
|
88
|
+
def id: () -> Integer
|
|
89
|
+
def path: () -> String
|
|
90
|
+
def char_path: () -> String
|
|
91
|
+
def start: (?threads: :auto | Integer) -> Device
|
|
92
|
+
def stop: () -> Device
|
|
93
|
+
def delete: () -> Device
|
|
94
|
+
def run: (?threads: :auto | Integer) -> Device
|
|
95
|
+
end
|
|
96
|
+
end
|
data/tools/bench.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require_relative "../examples/loop"
|
|
5
|
+
|
|
6
|
+
abort "set UBLK_BENCH=1 inside a privileged Linux VM" unless ENV["UBLK_BENCH"] == "1"
|
|
7
|
+
|
|
8
|
+
device = nil
|
|
9
|
+
backing = nil
|
|
10
|
+
at_exit do
|
|
11
|
+
device&.delete
|
|
12
|
+
backing&.close!
|
|
13
|
+
end
|
|
14
|
+
duration = Integer(ENV.fetch("UBLK_BENCH_SECONDS", "60"), 10)
|
|
15
|
+
path = ENV["UBLK_BENCH_DEVICE"]
|
|
16
|
+
if path
|
|
17
|
+
abort "UBLK_BENCH_DEVICE must be a block device" unless File.blockdev?(path)
|
|
18
|
+
else
|
|
19
|
+
GC.stress = true if ENV["UBLK_BENCH_GC_STRESS"] == "1"
|
|
20
|
+
backing = Tempfile.new("ublk-bench")
|
|
21
|
+
backing.truncate(256 * 1024 * 1024)
|
|
22
|
+
device = UBLK::Device.create(LoopTarget.new(backing.path), depth: 128, mlock: false)
|
|
23
|
+
device.start
|
|
24
|
+
path = device.path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
%w[randread randwrite read].each do |workload|
|
|
28
|
+
success = system(
|
|
29
|
+
"fio", "--name=#{workload}", "--filename=#{path}", "--direct=1", "--rw=#{workload}",
|
|
30
|
+
"--ioengine=io_uring", "--bs=4k", "--iodepth=32", "--runtime=#{duration}", "--time_based=1",
|
|
31
|
+
"--output-format=json"
|
|
32
|
+
)
|
|
33
|
+
abort "fio #{workload} failed" unless success
|
|
34
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#include <stdio.h>
|
|
2
|
+
#include "../ext/ublk/compat.h"
|
|
3
|
+
|
|
4
|
+
#define DUMP(value) printf("%s=%llu\n", #value, (unsigned long long)(value))
|
|
5
|
+
|
|
6
|
+
int main(void)
|
|
7
|
+
{
|
|
8
|
+
DUMP(UBLK_F_USER_COPY);
|
|
9
|
+
DUMP(UBLK_F_CMD_IOCTL_ENCODE);
|
|
10
|
+
DUMP(UBLK_F_USER_RECOVERY);
|
|
11
|
+
DUMP(UBLK_MAX_QUEUE_DEPTH);
|
|
12
|
+
DUMP(UBLKSRV_IO_BUF_OFFSET);
|
|
13
|
+
DUMP(UBLK_IO_OP_READ);
|
|
14
|
+
DUMP(UBLK_IO_OP_WRITE);
|
|
15
|
+
DUMP(UBLK_IO_OP_FLUSH);
|
|
16
|
+
DUMP(UBLK_IO_OP_DISCARD);
|
|
17
|
+
DUMP(UBLK_IO_OP_WRITE_ZEROES);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
data/tools/vm/Makefile
ADDED
data/tools/vm/run.sh
ADDED
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ublk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A native Ruby binding for Linux ublk using io_uring and UBLK_F_USER_COPY.
|
|
13
|
+
email:
|
|
14
|
+
- t.yudai92@gmail.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions:
|
|
17
|
+
- ext/ublk/extconf.rb
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- Rakefile
|
|
23
|
+
- examples/encrypted.rb
|
|
24
|
+
- examples/fault_injection.rb
|
|
25
|
+
- examples/http_disk.rb
|
|
26
|
+
- examples/loop.rb
|
|
27
|
+
- examples/ramdisk.rb
|
|
28
|
+
- examples/tracing.rb
|
|
29
|
+
- ext/ublk/compat.h
|
|
30
|
+
- ext/ublk/constants.c
|
|
31
|
+
- ext/ublk/control.c
|
|
32
|
+
- ext/ublk/extconf.rb
|
|
33
|
+
- ext/ublk/internal.h
|
|
34
|
+
- ext/ublk/server.c
|
|
35
|
+
- ext/ublk/ublk.c
|
|
36
|
+
- lib/ublk.rb
|
|
37
|
+
- lib/ublk/control.rb
|
|
38
|
+
- lib/ublk/device.rb
|
|
39
|
+
- lib/ublk/device_info.rb
|
|
40
|
+
- lib/ublk/params.rb
|
|
41
|
+
- lib/ublk/target.rb
|
|
42
|
+
- lib/ublk/version.rb
|
|
43
|
+
- sig/ublk.rbs
|
|
44
|
+
- tools/bench.rb
|
|
45
|
+
- tools/dump_constants.c
|
|
46
|
+
- tools/vm/Makefile
|
|
47
|
+
- tools/vm/config-fragment
|
|
48
|
+
- tools/vm/guest-test.sh
|
|
49
|
+
- tools/vm/run.sh
|
|
50
|
+
homepage: https://github.com/ydah/ublk
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata:
|
|
54
|
+
allowed_push_host: https://rubygems.org
|
|
55
|
+
homepage_uri: https://github.com/ydah/ublk
|
|
56
|
+
source_code_uri: https://github.com/ydah/ublk
|
|
57
|
+
rubygems_mfa_required: 'true'
|
|
58
|
+
rdoc_options: []
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: 3.2.0
|
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
requirements: []
|
|
72
|
+
rubygems_version: 4.0.19
|
|
73
|
+
specification_version: 4
|
|
74
|
+
summary: Write Linux ublk block-device targets in Ruby
|
|
75
|
+
test_files: []
|