duel-redis 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/CHANGELOG.md +49 -0
- data/LICENSE +21 -0
- data/README.md +80 -0
- data/lib/duel/redis/version.rb +7 -0
- data/lib/duel/redis.rb +174 -0
- data/sig/duel/redis.rbs +68 -0
- metadata +65 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8945f91caaf5208b93bdaef3934320e6722dc1ba837d5c6d72d8eb2c8a97245b
|
|
4
|
+
data.tar.gz: a55066487902efb5bb189a982d3de10a8698f984d22e05f62bc538c6a7d542a3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 51a4bf4253ceb07cf1b809e3ed6c124dff48884875bb73a229f2e2fb91764c8ce2a5536f97e8b45ae510a48da3c4f89e6ca872919cf2a75090d0f236003b1c03
|
|
7
|
+
data.tar.gz: d5f2ee1dd4383dd37e8b9cb359f122d7a8459cc445cadf04581daaa7fe510153df4bcc8b051cd0c0adfec81757c9831b2226d431ef1605e0804f4fd222c3801f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
The first release. duel-redis is a Duel plugin, open source under the MIT
|
|
13
|
+
license. It needs Ruby 3.4 or later, Duel 0.1, and `redis-server`
|
|
14
|
+
(Redis 6.2 or later, or Valkey) on the `PATH`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `c.use :redis`: a Redis server for each Duel worker, on a Unix
|
|
19
|
+
socket, and the `redis_cpu`, `redis_commands` and `redis_round_trips`
|
|
20
|
+
metrics.
|
|
21
|
+
- `Duel::Redis.url` and `Duel::Redis.flush`, for benchmarks: `flush`
|
|
22
|
+
opens a connection of its own, from the worker's `DUEL_REDIS_URL`.
|
|
23
|
+
- `c.use :redis` returns the plugin's own connection, made in each
|
|
24
|
+
worker (`c.in_worker`) and closed as it ends (`at_stop`). Its `value`
|
|
25
|
+
fails outside a worker. The module keeps no connection.
|
|
26
|
+
- `redis_commands` and `redis_round_trips` are in commands and round
|
|
27
|
+
trips (`unit:`), so reports read "1 command → 2 commands".
|
|
28
|
+
- `Duel::Redis.flush` raises `Duel::Error` while a benchmark is measured
|
|
29
|
+
(`Duel.measuring?`): it's for `setup` hooks, and `once` and `untimed`
|
|
30
|
+
blocks, where its `FLUSHALL` isn't counted.
|
|
31
|
+
- Its metrics are costly to read (Duel's `costly`): a benchmark with an
|
|
32
|
+
`around_iteration` hook reads the server's statistics only as each
|
|
33
|
+
sample starts and stops, not twice an iteration, and what the hook
|
|
34
|
+
sends the server (a transaction's `BEGIN` and `ROLLBACK`, say) is
|
|
35
|
+
learned in each worker and subtracted.
|
|
36
|
+
- Each worker's server works in the worker's own directory (`--dir`),
|
|
37
|
+
so persistence that `args:` turn on writes its snapshots and
|
|
38
|
+
append-only files there, never in the project.
|
|
39
|
+
- The plugin's connection (`Duel::Redis::Connection#call`) reads every
|
|
40
|
+
type of reply RESP2 has, whole: a nil bulk string (a missing key's),
|
|
41
|
+
an array (returned as an `Array`, nested ones and nil included, any
|
|
42
|
+
error in it a `RuntimeError`), and an integer, as an `Integer`. It
|
|
43
|
+
never sends `HELLO`, so it reads RESP2 only: a reply of another type
|
|
44
|
+
(RESP3's) closes the connection and raises `IOError`, rather than
|
|
45
|
+
read the next reply out of step.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
[Unreleased]: https://github.com/sferik/duel-ruby/compare/duel-redis-v0.1.0...HEAD
|
|
49
|
+
[0.1.0]: https://github.com/sferik/duel-ruby/releases/tag/duel-redis-v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Erik Berlin
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# duel-redis
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sferik/duel-ruby)
|
|
4
|
+
[](https://github.com/sferik/duel-ruby/actions/workflows/plugin-redis.yml)
|
|
5
|
+
[](https://rubygems.org/gems/duel-redis)
|
|
6
|
+
|
|
7
|
+
A [Duel](https://github.com/sferik/duel-ruby) plugin for code that talks
|
|
8
|
+
to Redis: each Duel worker gets a Redis server of its own, and every
|
|
9
|
+
benchmark is judged on what Redis did for it too.
|
|
10
|
+
|
|
11
|
+
| Metric | What it is | A regression |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `redis_cpu` | the time Redis spent running the benchmark's commands, in µs | judged like CPU time |
|
|
14
|
+
| `redis_commands` | the commands it ran | any increase |
|
|
15
|
+
| `redis_round_trips` | the replies it wrote: one per round trip | any increase |
|
|
16
|
+
|
|
17
|
+
So a change that sends one more command per job, or stops pipelining,
|
|
18
|
+
fails even when its CPU time on the Ruby side doesn't change.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Gemfile
|
|
24
|
+
group :development do
|
|
25
|
+
gem "duel-redis"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> [!NOTE]
|
|
30
|
+
> It needs `redis-server` (Redis 6.2 or later, or Valkey) on the `PATH`.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# .duel.rb
|
|
36
|
+
Duel.configure { |c| c.use :redis }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Each worker's server listens only on a Unix socket in the worker's own
|
|
40
|
+
directory, with persistence off, and works in that directory too
|
|
41
|
+
(`--dir`): if your `args:` turn persistence on, its snapshots and
|
|
42
|
+
append-only files go there, never in your project. Its URL is in
|
|
43
|
+
`DUEL_REDIS_URL`, and in `REDIS_URL` too, so a client that reads that
|
|
44
|
+
finds it and nothing reaches another Redis. Benchmarks can call
|
|
45
|
+
`Duel::Redis.url` and `Duel::Redis.flush`.
|
|
46
|
+
|
|
47
|
+
> [!IMPORTANT]
|
|
48
|
+
> `Duel::Redis.flush` is for setting up: call it in a `setup` hook, or
|
|
49
|
+
> a `once` or `untimed` block. While a benchmark is measured (`Duel.measuring?`), it
|
|
50
|
+
> raises `Duel::Error` rather than have its `FLUSHALL` counted.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# duel/cache_duel.rb
|
|
54
|
+
duel "cache: a hit" do
|
|
55
|
+
once do
|
|
56
|
+
Duel::Redis.flush # empties the worker's server (over a connection of its own)
|
|
57
|
+
@cache = MyCache.new(url: Duel::Redis.url) # its URL
|
|
58
|
+
@cache.write("k", "v")
|
|
59
|
+
end
|
|
60
|
+
@cache.read("k")
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
c.use :redis,
|
|
68
|
+
server: "valkey-server", # the program (default: DUEL_REDIS_SERVER, or redis-server)
|
|
69
|
+
args: ["--maxmemory", "64mb"], # more of its options
|
|
70
|
+
env: %w[CACHE_URL] # variables given its URL besides DUEL_REDIS_URL (default: REDIS_URL)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The metrics come from the server's `INFO` (command statistics and
|
|
74
|
+
`total_writes_processed`), read over a connection of the plugin's own,
|
|
75
|
+
opened as each worker starts (Duel's `c.in_worker`) and closed as it
|
|
76
|
+
ends. `c.use :redis` returns it, for a `.duel.rb` that reads the server
|
|
77
|
+
too (`connection.value`, in a worker only). Each reading is a command and a
|
|
78
|
+
reply itself, so Duel learns what readings add, and takes it away. Reports
|
|
79
|
+
count them: `redis_commands 4→6`, `redis_round_trips: 1 round trip → 2
|
|
80
|
+
round trips an iteration`.
|
data/lib/duel/redis.rb
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "duel"
|
|
4
|
+
require "socket"
|
|
5
|
+
require_relative "redis/version"
|
|
6
|
+
|
|
7
|
+
module Duel
|
|
8
|
+
# A Redis server of each worker's own, and what it did for each
|
|
9
|
+
# benchmark:
|
|
10
|
+
#
|
|
11
|
+
# - redis_cpu: the time Redis spent running commands, in microseconds
|
|
12
|
+
# (judged by ratio, as CPU time is)
|
|
13
|
+
# - redis_commands: the commands it ran (counted)
|
|
14
|
+
# - redis_round_trips: the replies it wrote, one per round trip (counted)
|
|
15
|
+
#
|
|
16
|
+
# # .duel.rb
|
|
17
|
+
# Duel.configure { |c| c.use :redis }
|
|
18
|
+
#
|
|
19
|
+
# Benchmarks can call Duel::Redis.url and Duel::Redis.flush.
|
|
20
|
+
#
|
|
21
|
+
# The server listens only on a Unix socket in the worker's directory,
|
|
22
|
+
# and works there too (--dir): what its options make it write, a
|
|
23
|
+
# snapshot or an append-only file, goes there, never in the project.
|
|
24
|
+
# Its URL is in DUEL_REDIS_URL, and in REDIS_URL too (unless +env+
|
|
25
|
+
# names others), so a client that reads that finds it.
|
|
26
|
+
module Redis
|
|
27
|
+
Duel.plugin(:redis, self)
|
|
28
|
+
|
|
29
|
+
URL = "DUEL_REDIS_URL"
|
|
30
|
+
|
|
31
|
+
# Every server's options: no TCP port, and nothing written to disk.
|
|
32
|
+
OPTIONS = ["--port", "0", "--save", "", "--appendonly", "no"].freeze
|
|
33
|
+
|
|
34
|
+
# Configures Duel for the plugin (c.use :redis): the server
|
|
35
|
+
# (+server+ is the program, +args+ more of its options) and its
|
|
36
|
+
# metrics. Returns the plugin's own connection to it, a worker's
|
|
37
|
+
# (see Config#in_worker, and in tests, Duel::Testing.in_worker).
|
|
38
|
+
def self.configure(config, server: ENV.fetch("DUEL_REDIS_SERVER", "redis-server"), args: [], env: %w[REDIS_URL])
|
|
39
|
+
config.prepare(:worker) do |w|
|
|
40
|
+
socket = w.path("redis.sock")
|
|
41
|
+
w.spawn(*[server, "--unixsocket", socket, "--dir", w.dir, *OPTIONS] + args)
|
|
42
|
+
w.wait_until { Connection.ping(socket) }
|
|
43
|
+
url = "unix://#{socket}"
|
|
44
|
+
([URL] + env).to_h { |name| [name, url] }
|
|
45
|
+
end
|
|
46
|
+
connection = config.in_worker { |w| Connection.new(url).tap { |redis| w.at_stop { redis.close } } }
|
|
47
|
+
config.metric(METRICS) { stats(connection.value) }.costly
|
|
48
|
+
connection
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
METRICS = {
|
|
52
|
+
"redis_cpu" => {kind: :ratio, unit: "µs", floor: 300},
|
|
53
|
+
"redis_commands" => {kind: :counted, unit: %w[command commands]},
|
|
54
|
+
"redis_round_trips" => {kind: :counted, unit: ["round trip", "round trips"]}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# What the worker's server has done so far, read over +connection+:
|
|
58
|
+
# every command's time but INFO's own (the reading's, too slight to
|
|
59
|
+
# count but not steady enough to take away), and every command and
|
|
60
|
+
# reply.
|
|
61
|
+
def self.stats(connection)
|
|
62
|
+
info = connection.call("INFO", "all") #: String
|
|
63
|
+
usec = calls = 0
|
|
64
|
+
info.scan(/^cmdstat_([^:]+):calls=(\d+),usec=(\d+)/) do |command, c, u|
|
|
65
|
+
calls += Integer(c)
|
|
66
|
+
usec += Integer(u) unless command == "info"
|
|
67
|
+
end
|
|
68
|
+
{"redis_cpu" => usec, "redis_commands" => calls, "redis_round_trips" => Integer(info[/^total_writes_processed:(\d+)/, 1])}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# For benchmarks: the worker's server.
|
|
72
|
+
def self.url = ENV.fetch(URL)
|
|
73
|
+
|
|
74
|
+
# Empties it, over a connection of its own (opened and closed: a Unix
|
|
75
|
+
# socket's is cheap). For setup or untimed blocks only: while a
|
|
76
|
+
# benchmark is measured (Duel.measuring?) it fails, as its FLUSHALL
|
|
77
|
+
# would be counted.
|
|
78
|
+
def self.flush
|
|
79
|
+
Duel.unmeasured!("Duel::Redis.flush")
|
|
80
|
+
|
|
81
|
+
connection = Connection.new(url)
|
|
82
|
+
begin
|
|
83
|
+
connection.call("FLUSHALL") #: String
|
|
84
|
+
ensure
|
|
85
|
+
connection.close
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The least a connection needs to read a server's statistics: not the
|
|
90
|
+
# code's own, whose commands are what's measured.
|
|
91
|
+
class Connection
|
|
92
|
+
CLOSED = "Redis closed the connection"
|
|
93
|
+
|
|
94
|
+
# The first byte of a reply: its type, one of RESP2's.
|
|
95
|
+
STATUS, ERROR, INTEGER, BULK, ARRAY = "+-:$*".bytes
|
|
96
|
+
|
|
97
|
+
def self.ping(path)
|
|
98
|
+
socket = UNIXSocket.new(path)
|
|
99
|
+
begin
|
|
100
|
+
socket.write("PING\r\n")
|
|
101
|
+
socket.gets == "+PONG\r\n"
|
|
102
|
+
ensure
|
|
103
|
+
socket.close
|
|
104
|
+
end
|
|
105
|
+
rescue SystemCallError
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def initialize(url)
|
|
110
|
+
@socket = UNIXSocket.new(url.delete_prefix("unix://"))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Sends a command; returns its reply, read whole (every type RESP2
|
|
114
|
+
# has): a status or a bulk string as a String, an integer as an
|
|
115
|
+
# Integer, a missing one (a nil bulk string or array) as nil, and an
|
|
116
|
+
# array as an Array of replies, any error in it a RuntimeError. An
|
|
117
|
+
# error reply raises it. A reply cut short (the server gone) raises
|
|
118
|
+
# IOError.
|
|
119
|
+
#
|
|
120
|
+
# The connection never sends HELLO, so Redis answers in RESP2. A
|
|
121
|
+
# reply of a type RESP2 doesn't have (RESP3's, after a HELLO 3)
|
|
122
|
+
# closes the connection, as the rest of it can't be read, and raises
|
|
123
|
+
# IOError.
|
|
124
|
+
def call(*args)
|
|
125
|
+
@socket.write(args.map(&:to_s).reduce("*#{args.size}\r\n") { |out, arg| out << "$#{arg.bytesize}\r\n#{arg}\r\n" })
|
|
126
|
+
reply = read
|
|
127
|
+
raise reply if reply.is_a?(Exception)
|
|
128
|
+
|
|
129
|
+
reply
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def close = @socket.close
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# A reply, and every element of an array's: its first line's type
|
|
137
|
+
# (its first byte), then what follows it.
|
|
138
|
+
def read
|
|
139
|
+
line = (@socket.gets or raise IOError, CLOSED)
|
|
140
|
+
line.chomp!
|
|
141
|
+
case line.getbyte(0)
|
|
142
|
+
when STATUS then line.byteslice(1..)
|
|
143
|
+
when BULK then bulk(number(line))
|
|
144
|
+
when INTEGER then number(line)
|
|
145
|
+
when ARRAY then array(number(line))
|
|
146
|
+
when ERROR then RuntimeError.new("Redis: #{line.byteslice(1..)}")
|
|
147
|
+
else
|
|
148
|
+
close
|
|
149
|
+
raise IOError, "Redis: a reply of a type the connection doesn't read (RESP2 only): #{line}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# A line's number, after its type: the type byte made a space rather
|
|
154
|
+
# than cut (which would copy the line), as Integer() skips one.
|
|
155
|
+
def number(line)
|
|
156
|
+
line.setbyte(0, 32)
|
|
157
|
+
Integer(line)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# A bulk string of +size+ bytes, and its CRLF (nil for size -1, a
|
|
161
|
+
# missing one).
|
|
162
|
+
def bulk(size)
|
|
163
|
+
return if size == -1
|
|
164
|
+
|
|
165
|
+
@socket.read(size + 2)&.delete_suffix!("\r\n") or raise IOError, CLOSED
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# An array of +size+ replies (nil for size -1, a missing one).
|
|
169
|
+
def array(size)
|
|
170
|
+
Array.new(size) { read } unless size == -1
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
data/sig/duel/redis.rbs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Duel
|
|
2
|
+
# A Redis server of each worker's own, and what it did for each
|
|
3
|
+
# benchmark (redis_cpu, redis_commands, redis_round_trips).
|
|
4
|
+
module Redis
|
|
5
|
+
VERSION: String
|
|
6
|
+
|
|
7
|
+
URL: String
|
|
8
|
+
|
|
9
|
+
# Every server's options: no TCP port, and nothing written to disk.
|
|
10
|
+
OPTIONS: Array[String]
|
|
11
|
+
|
|
12
|
+
METRICS: Hash[String, Metric::settings]
|
|
13
|
+
|
|
14
|
+
# Configures Duel for the plugin (c.use :redis): the server (+server+
|
|
15
|
+
# is the program, +args+ more of its options, +env+ the variables
|
|
16
|
+
# besides DUEL_REDIS_URL given its URL) and its metrics. Returns the
|
|
17
|
+
# plugin's own connection to it, a worker's.
|
|
18
|
+
def self.configure: (Config config, ?server: String, ?args: Array[String], ?env: Array[String]) -> InWorker[Connection]
|
|
19
|
+
|
|
20
|
+
# What the worker's server has done so far, read over +connection+.
|
|
21
|
+
def self.stats: (Connection connection) -> Hash[String, Integer]
|
|
22
|
+
|
|
23
|
+
# For benchmarks: the worker's server.
|
|
24
|
+
def self.url: () -> String
|
|
25
|
+
|
|
26
|
+
# Empties it, over a connection of its own: in setup or untimed blocks
|
|
27
|
+
# only (it fails while a benchmark is measured).
|
|
28
|
+
def self.flush: () -> String
|
|
29
|
+
|
|
30
|
+
# The least a connection needs to read a server's statistics.
|
|
31
|
+
class Connection
|
|
32
|
+
# A reply, as Connection#call returns it, or in an array (where an
|
|
33
|
+
# error is one too).
|
|
34
|
+
type reply = String | Integer | RuntimeError | Array[reply] | nil
|
|
35
|
+
|
|
36
|
+
CLOSED: String
|
|
37
|
+
|
|
38
|
+
# The first byte of a reply: its type, one of RESP2's.
|
|
39
|
+
STATUS: Integer
|
|
40
|
+
ERROR: Integer
|
|
41
|
+
INTEGER: Integer
|
|
42
|
+
BULK: Integer
|
|
43
|
+
ARRAY: Integer
|
|
44
|
+
|
|
45
|
+
@socket: UNIXSocket
|
|
46
|
+
|
|
47
|
+
def self.ping: (String path) -> bool
|
|
48
|
+
|
|
49
|
+
def initialize: (String url) -> void
|
|
50
|
+
|
|
51
|
+
# Sends a command; returns its reply (RESP2's, read whole): an error
|
|
52
|
+
# reply raises.
|
|
53
|
+
def call: (*String | Symbol | Integer args) -> (String | Integer | Array[reply] | nil)
|
|
54
|
+
|
|
55
|
+
def close: () -> void
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def read: () -> reply
|
|
60
|
+
|
|
61
|
+
def number: (String line) -> Integer
|
|
62
|
+
|
|
63
|
+
def bulk: (Integer size) -> String?
|
|
64
|
+
|
|
65
|
+
def array: (Integer size) -> Array[reply]?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: duel-redis
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Erik Berlin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: duel
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
description: 'A Duel plugin: each worker gets a Redis server of its own, and every
|
|
27
|
+
benchmark is judged on the Redis CPU time, commands and round trips it took too.'
|
|
28
|
+
email:
|
|
29
|
+
- sferik@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- CHANGELOG.md
|
|
35
|
+
- LICENSE
|
|
36
|
+
- README.md
|
|
37
|
+
- lib/duel/redis.rb
|
|
38
|
+
- lib/duel/redis/version.rb
|
|
39
|
+
- sig/duel/redis.rbs
|
|
40
|
+
homepage: https://github.com/sferik/duel-ruby/tree/main/plugins/redis
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata:
|
|
44
|
+
source_code_uri: https://github.com/sferik/duel-ruby/tree/main/plugins/redis
|
|
45
|
+
changelog_uri: https://github.com/sferik/duel-ruby/blob/main/plugins/redis/CHANGELOG.md
|
|
46
|
+
bug_tracker_uri: https://github.com/sferik/duel-ruby/issues
|
|
47
|
+
rubygems_mfa_required: 'true'
|
|
48
|
+
rdoc_options: []
|
|
49
|
+
require_paths:
|
|
50
|
+
- lib
|
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '3.4'
|
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
requirements: []
|
|
62
|
+
rubygems_version: 4.0.23
|
|
63
|
+
specification_version: 4
|
|
64
|
+
summary: A Redis server for each Duel worker, and what it did for each benchmark
|
|
65
|
+
test_files: []
|