emb 0.2.4 → 0.2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 361d0b6f4a7719829d4e87426227fc567af79bd16862019754c19977ddd7b11d
4
- data.tar.gz: a0d165464592eaf05555ef4c8fd8400be52db48353a5f57d9e13c0e7d9b6c26a
3
+ metadata.gz: 0465face51bde07116c17303587651ae8c947875b9702a587654ead5256c71da
4
+ data.tar.gz: b0e8cf2d155a51c9b65a2bb006eec6be4d7a60e932301bea426b2ae986a91015
5
5
  SHA512:
6
- metadata.gz: 39ad4fa8bcf1b35d5162c503c3cafcb8a94194550c370cc5135015e3285e20cead32492ddab35306ac086d8efa51b6ec52a984aeb5e18888391edfd4c10e0d8b
7
- data.tar.gz: 86105916d29f1a8386578e15ecfd4965eb7be8aefa00cf59c5160ca93f6bd0653aac71bc762ed10e678c8c7624d1512c9c211e31bd3f22fd127457faec108624
6
+ metadata.gz: 843e3cdb952a4a1ece48413aad778939d155f4e345c9ebbce3ccd18ce703e53183a099dcae3b815856d062a0d7a80b3d028587704c736402ce659133eb1d7b8a
7
+ data.tar.gz: 786734c2ac2765bde4810313f998f83550e0823cd47887e16ebf5b2014fe2c865e22b021286187cedaf105d2d5b818c8885d85acceab72ead5e07915da17d9f3
data/README.md CHANGED
@@ -36,8 +36,6 @@ Emb.setup(host: "localhost", port: 6379)
36
36
  Emb.setup
37
37
  ```
38
38
 
39
- `Emb.config` is an alias for `Emb.setup`.
40
-
41
39
  ### Configuration sources (priority order)
42
40
 
43
41
  1. Explicit `url:` or `host:`/`port:` arguments
@@ -160,7 +158,7 @@ Emb.setup
160
158
  Emb[:minilm]["hello"] # proxy access
161
159
  Emb.models # list models
162
160
  Emb.info(:minilm) # model info
163
- Emb.stats # server stats
161
+ Emb.stats # server stats (Hash of key => value)
164
162
  Emb.help # command reference
165
163
  Emb.ping # health check
166
164
  ```
@@ -169,6 +167,67 @@ These all delegate to a lazily-initialized default client. No explicit `setup` c
169
167
  is required for simple cases — the default client connects to `redis://localhost:6379`
170
168
  automatically.
171
169
 
170
+ ## Server info & config
171
+
172
+ The server exposes Redis-style `INFO` and `CONFIG` commands; the gem wraps them.
173
+
174
+ ### `Emb.stats` — server statistics as a hash
175
+
176
+ `EMB.STATS` is decoded into a Symbol-keyed Hash with values as the server sent them
177
+ (RESP integers stay Integer, everything else String):
178
+
179
+ ```ruby
180
+ Emb.stats
181
+ # => {uptime_secs: 3, total_requests: 0, active_requests: "0", total_tokens: 0,
182
+ # total_errors: 0, models_loaded: 1, per_model: "minilm: req=0 avg=0us tok=0 ...",
183
+ # cache_hits: 0, cache_misses: 0, cache_evictions: 0}
184
+ ```
185
+
186
+ > **Breaking change (gem ≥ next release):** `Emb.stats` used to return the raw
187
+ > RESP array (`["uptime_secs", 3, "total_requests", 0, ...]`). It now returns the
188
+ > Hash above. Callers using `stats.each_slice(2)` or array indexing must migrate.
189
+
190
+ ### `Emb.server_info` — sectioned INFO, parsed
191
+
192
+ The Redis-style `INFO` reply is parsed into a nested Hash. **No arguments = all
193
+ sections**; pass section names to filter (`:server`, `:cache`, `:keyspace`, `:stats`, `:clients`):
194
+
195
+ ```ruby
196
+ Emb.server_info
197
+ # => {Server: {redis_version: "0.2.4", emb_version: "0.2.4", uptime_secs: "7", ...},
198
+ # Cache: {cache_hits: 0, cache_misses: 0, cache_hit_rate: "0.0%", ...},
199
+ # Keyspace: {db0: "model=minilm,keys=0,hits=0,misses=0,hit_rate=0.0%"}, ...}
200
+
201
+ Emb.server_info(:server, :cache) # only those two sections
202
+ ```
203
+
204
+ ### `Emb.config` — hot config read & change
205
+
206
+ `Emb.config` is a Hash-like live view of the server's runtime configuration
207
+ (backed by `CONFIG GET` / `CONFIG SET`). **Note:** the former `Emb.config` alias
208
+ for `Emb.setup` is gone — use `Emb.setup` to configure the client.
209
+
210
+ ```ruby
211
+ Emb.config.to_h # all parameters
212
+ # => {"cache" => "auto", "cache_file" => "", "cache_save" => "", "listen" => ":6379",
213
+ # "password" => "", "models" => "minilm,bge", "tls_cert" => "", "tls_key" => ""}
214
+
215
+ Emb.config['cache'] # exact key => String value
216
+ # => "auto"
217
+ Emb.config['cache*'] # glob => Hash of matching parameters
218
+ Emb.config['listen'] # unknown key => nil
219
+
220
+ Emb.config['cache'] = '100MB' # live change; returns "OK"
221
+ Emb.config['cache_file'] = '/var/lib/emb/cache.rdb'
222
+ ```
223
+
224
+ Values are Strings (config is text, not metrics) and round-trip into writers
225
+ unchanged. `cache` resizes immediately, `cache_file`/`cache_save` apply at the
226
+ next snapshot save, `password` affects new connections. Errors surface as
227
+ exceptions (`RedisClient::CommandError`): read-only parameters (`listen`, `tls_*`,
228
+ `models`), invalid values, and `NOAUTH` on password-protected servers are not
229
+ swallowed.
230
+
172
231
  ## Usage
173
232
 
174
233
  ### Single text
data/lib/emb/batch.rb CHANGED
@@ -7,18 +7,24 @@ module Emb
7
7
 
8
8
  BATCH_BLOCK = lambda do |items, loader, _args|
9
9
  items.group_by(&:first).each do |client, client_items|
10
- pairs = client_items.flat_map { |_, model, text| Array(text).flat_map { |t| [model.to_s, t] } }
11
- results = Array(client.send_command('EMB.MULTI', *pairs))
12
-
13
- offset = 0
14
- client_items.each do |item|
15
- _, _, text = item
16
- texts = Array(text)
17
- values = results[offset, texts.size].map { |entry| entry&.unpack('e*') }
18
- offset += texts.size
19
-
20
- # eager Proxy#[] shape: vector for a single text, vectors for many
21
- loader.call(item, values.size == 1 ? values.first : values)
10
+ chunk = client.respond_to?(:batch_size) && client.batch_size ? client.batch_size : Emb.configuration.batch_size
11
+
12
+ # Chunk at batch_size pairs per EMB.MULTI so a single command stays well
13
+ # under the server's max_pairs cap and within client read timeouts.
14
+ client_items.each_slice(chunk) do |slice|
15
+ pairs = slice.flat_map { |_, model, text| Array(text).flat_map { |t| [model.to_s, t] } }
16
+ results = Array(client.send_command('EMB.MULTI', *pairs))
17
+
18
+ offset = 0
19
+ slice.each do |item|
20
+ _, _, text = item
21
+ texts = Array(text)
22
+ values = results[offset, texts.size].map { |entry| entry&.unpack('e*') }
23
+ offset += texts.size
24
+
25
+ # eager Proxy#[] shape: vector for a single text, vectors for many
26
+ loader.call(item, values.size == 1 ? values.first : values)
27
+ end
22
28
  end
23
29
  end
24
30
  end
data/lib/emb/client.rb CHANGED
@@ -5,11 +5,14 @@ require 'redis_client'
5
5
 
6
6
  module Emb
7
7
  class Client
8
- attr_reader :pool
8
+ include Commands
9
+
10
+ attr_reader :pool, :batch_size
9
11
 
10
12
  def initialize(pool: nil, batch: nil, **redis_options)
11
13
  cfg = Emb.configuration
12
14
  @batch_enabled = batch.nil? ? cfg.batch : batch
15
+ @batch_size = redis_options.delete(:batch_size) || cfg.batch_size
13
16
  size = pool.nil? ? cfg.pool : pool
14
17
  url = extract_url!(redis_options, cfg)
15
18
  redis_options = merged_redis_options(redis_options, cfg, url)
@@ -45,6 +48,11 @@ module Emb
45
48
  @batch_enabled
46
49
  end
47
50
 
51
+ # Live view of the server's runtime configuration (CONFIG GET/SET).
52
+ def config
53
+ @config ||= RuntimeConfig.new(self)
54
+ end
55
+
48
56
  def models
49
57
  raw = send_command('EMB.MODELS')
50
58
  return [] if raw.nil?
@@ -63,8 +71,6 @@ module Emb
63
71
  .to_h { |k, v| [k.to_sym, v] }
64
72
  end
65
73
 
66
- def stats = send_command('EMB.STATS')
67
-
68
74
  def help = send_command('EMB.HELP')
69
75
 
70
76
  def ping = send_command('PING')
@@ -99,7 +105,7 @@ module Emb
99
105
 
100
106
  def merged_redis_options(opts, cfg, url)
101
107
  defaults = cfg.to_h
102
- keys = defaults.keys - %i[url pool batch]
108
+ keys = defaults.keys - %i[url pool batch batch_size]
103
109
  keys -= %i[host port] if url
104
110
 
105
111
  keys.each do |key|
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emb
4
+ # Server-status and runtime-config command wrappers. They only depend on
5
+ # #send_command, so they live in this module (included into Emb::Client)
6
+ # rather than growing the client class.
7
+ module Commands
8
+ # EMB.STATS as a Symbol-keyed Hash. No client-side type layer: values are
9
+ # exactly what the RESP decoder returned (Integer where the server sends
10
+ # RESP integers, String otherwise).
11
+ def stats
12
+ raw = send_command('EMB.STATS')
13
+ raw.each_slice(2).to_h { |key, value| [key.to_sym, value] }
14
+ end
15
+
16
+ # Server-wide INFO (the Redis-style sectioned command). No sections =
17
+ # all sections; any number of sections filter the reply.
18
+ def server_info(*sections)
19
+ parse_info(send_command('INFO', *sections.map(&:to_s)))
20
+ end
21
+
22
+ private
23
+
24
+ # Parse Redis INFO section text into a nested Hash:
25
+ # {Server: {redis_version: "0.2.4", uptime_secs: "7"}, Cache: {…}, …}
26
+ # Section names and keys are Symbols; values pass through as the server
27
+ # sent them. Lines that don't fit the grammar are ignored.
28
+ def parse_info(text)
29
+ sections = {}
30
+ current = nil
31
+
32
+ text.split("\r\n").each do |line|
33
+ if line.start_with?('# ')
34
+ current = line[2..].to_sym
35
+ sections[current] ||= {}
36
+ elsif current && line.include?(':')
37
+ key, value = line.split(':', 2)
38
+ sections[current][key.to_sym] = value
39
+ end
40
+ end
41
+
42
+ sections
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,7 @@
3
3
  module Emb
4
4
  class Configuration
5
5
  OPTIONS = %i[
6
- host port url pool batch driver protocol
6
+ host port url pool batch batch_size driver protocol
7
7
  connect_timeout read_timeout write_timeout reconnect_attempts
8
8
  ].freeze
9
9
 
@@ -15,6 +15,7 @@ module Emb
15
15
  self.url = nil
16
16
  self.pool = 5
17
17
  self.batch = true
18
+ self.batch_size = 512
18
19
  self.driver = nil
19
20
  self.protocol = 2
20
21
  self.connect_timeout = nil
data/lib/emb/multi.rb CHANGED
@@ -12,11 +12,17 @@ module Emb
12
12
  end
13
13
 
14
14
  def run
15
- args = @pairs.flat_map { |pair| [pair[:model].to_s, pair[:text]] }
15
+ chunk = @client.respond_to?(:batch_size) && @client.batch_size ? @client.batch_size : Emb.configuration.batch_size
16
16
 
17
- @client
18
- .send_command('EMB.MULTI', *args)
19
- .map { |entry| entry.unpack('e*') }
17
+ # Chunk at batch_size pairs per EMB.MULTI so a single command stays well
18
+ # under the server's max_pairs cap and within client read timeouts.
19
+ @pairs.each_slice(chunk).flat_map do |slice|
20
+ args = slice.flat_map { |pair| [pair[:model].to_s, pair[:text]] }
21
+
22
+ @client
23
+ .send_command('EMB.MULTI', *args)
24
+ .map { |entry| entry&.unpack('e*') }
25
+ end
20
26
  end
21
27
 
22
28
  class PairCollector
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emb
4
+ # Hash-like live view of the server's runtime configuration, backed by
5
+ # CONFIG GET / CONFIG SET.
6
+ #
7
+ # config.to_h # => { "cache" => "auto", "listen" => ":6379", … }
8
+ # config['cache'] # => "auto" (scalar for an exact key)
9
+ # config['cache*'] # => { "cache" => …, "cache_file" => … } (glob)
10
+ # config['cache_file'] = '/var/lib/emb/cache.rdb' # => "OK"
11
+ #
12
+ # Values are Strings (config is text, not metrics) and round-trip into
13
+ # writers unchanged. Server errors (unknown/read-only parameter, invalid
14
+ # value, NOAUTH) raise RedisClient::CommandError.
15
+ class RuntimeConfig
16
+ def initialize(client)
17
+ @client = client
18
+ end
19
+
20
+ # All parameters as a String-keyed Hash (CONFIG GET).
21
+ def to_h
22
+ pairs(@client.send_command('CONFIG', 'GET'))
23
+ end
24
+
25
+ # Read one parameter or a glob. An exact key returns its String value
26
+ # (nil when the server has no such parameter); a glob that matches
27
+ # several parameters returns a Hash.
28
+ def [](key)
29
+ key = key.to_s
30
+ map = pairs(@client.send_command('CONFIG', 'GET', key))
31
+ return map[key] if map.key?(key)
32
+
33
+ map.empty? ? nil : map
34
+ end
35
+
36
+ # Write one parameter (CONFIG SET). Returns the server's reply ("OK").
37
+ def []=(key, value)
38
+ @client.send_command('CONFIG', 'SET', key.to_s, value.to_s)
39
+ end
40
+
41
+ private
42
+
43
+ def pairs(raw)
44
+ raw.each_slice(2).to_h { |k, v| [k, v] }
45
+ end
46
+ end
47
+ end
data/lib/emb.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative 'emb/version'
4
4
  require_relative 'emb/configuration'
5
+ require_relative 'emb/commands'
6
+ require_relative 'emb/runtime_config'
5
7
  require_relative 'emb/client'
6
8
  require_relative 'emb/proxy'
7
9
  require_relative 'emb/multi'
@@ -16,8 +18,6 @@ module Emb
16
18
  @default_client = Client.new(...)
17
19
  end
18
20
 
19
- alias config setup
20
-
21
21
  def configuration
22
22
  @configuration ||= Configuration.new
23
23
  end
@@ -32,6 +32,8 @@ module Emb
32
32
  def models = default_client.models
33
33
  def info(name) = default_client.info(name)
34
34
  def stats = default_client.stats
35
+ def server_info(*sections) = default_client.server_info(*sections)
36
+ def config = default_client.config
35
37
  def help = default_client.help
36
38
  def ping = default_client.ping
37
39
  def ready = default_client.ready
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - elcuervo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-26 00:00:00.000000000 Z
11
+ date: 2026-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: batch-loader
@@ -64,10 +64,12 @@ files:
64
64
  - lib/emb.rb
65
65
  - lib/emb/batch.rb
66
66
  - lib/emb/client.rb
67
+ - lib/emb/commands.rb
67
68
  - lib/emb/configuration.rb
68
69
  - lib/emb/middleware.rb
69
70
  - lib/emb/multi.rb
70
71
  - lib/emb/proxy.rb
72
+ - lib/emb/runtime_config.rb
71
73
  - lib/emb/version.rb
72
74
  homepage: https://github.com/elcuervo/emb
73
75
  licenses: