omq-cli 0.16.2 → 0.17.1

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: 65853ffed26725eec94127f166458f4358e3bc0217ac608f729361f76bf81595
4
- data.tar.gz: 85378ff431328f8e8c0ca03c7f9ea561771d598fa4c25c2824c3f8e4aa8b310d
3
+ metadata.gz: 9328ae563951fd25f16f512790bdcc70cf041c8ffabae24622d6ab3ffa1dfffe
4
+ data.tar.gz: 933eb13378689905ffe1b908b7ca4f5dae8a3b678832aad20e29de37d0d6748d
5
5
  SHA512:
6
- metadata.gz: 4d5f29b3fe396efb10031ff2d98ba97424798a3345c13be6f8c419affcce467a3c1196faa6b31a9c741ce3577328e9e9e2df86a031cd1fb2e9865cf162945ce4
7
- data.tar.gz: 95e41fe15805c6d123f60cbe8fe451749b21e8666165ec5390553bdf141c398a51706351bb387e46ac797d71fdf984b87187c72cdf4095aefb980ffbdaf69734
6
+ metadata.gz: 60e516ab7792ecef5f47d18b84bac5b25f5a4287152d54ed06a649fd6f1b190c7bef32ae833dd92b7c832037bef473691942104d00d1ff6d511d84133c455cca
7
+ data.tar.gz: e6ff35c82124cc0791703248a50b85bfc9f744971777dc485089d9aa47f7598e43646e7fd450b9293fc64ad4ef2479dc8fcb1ba2e0fb6bb4cbd85b2f6036d3f0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.1 — 2026-05-04
4
+
5
+ - bump deps
6
+
7
+ ## 0.17.0 — 2026-04-25
8
+
9
+ - **LZ4 compression via `--lz4`.** Complements Zstd with a
10
+ lower-CPU, lower-memory codec (no entropy stage, ~16 KiB of
11
+ encoder state per connection). Uses the `lz4+tcp://` transport
12
+ from `omq-lz4`. The existing `--compress` flag now accepts a
13
+ codec-aware `SPEC`:
14
+ ```
15
+ --compress=zstd # zstd at default level
16
+ --compress=zstd:3 # zstd level 3
17
+ --compress=zstd:-1 # zstd level -1
18
+ --compress=lz4 # lz4 (no level)
19
+ --compress=19 # back-compat: zstd level 19
20
+ ```
21
+ `Config.compress` becomes a tri-state symbol
22
+ (`nil` / `:zstd` / `:lz4`); `SocketSetup.compress_url` picks
23
+ `zstd+tcp://` or `lz4+tcp://` accordingly. Adds `omq-lz4` as a
24
+ runtime dependency.
25
+ - **`omq pipe`: `-z` / `-Z` / `--compress` are modal with
26
+ `--in` / `--out`.** Placing a compression flag after `--in`
27
+ compresses only the PULL side; after `--out` only the PUSH
28
+ side. A flag before any `--in`/`--out` still applies globally
29
+ (backwards compatible). New `Config` fields
30
+ `in_compress` / `in_compress_level` /
31
+ `out_compress` / `out_compress_level` carry the per-side
32
+ state; `SocketSetup.resolve_compress(config, side)` is the
33
+ single resolution point, and `SocketSetup.attach_endpoints`
34
+ takes a `side:` kwarg.
35
+ - **Bind-mode senders with piped stdin now wait for a peer.**
36
+ `seq 10 | omq push -b tcp://…` previously bound, drained all
37
+ messages into HWM, then exited before any consumer connected.
38
+ Non-tty stdin now counts as a bounded send plan in
39
+ `BaseRunner#bounded_or_scheduled_send?`, matching the existing
40
+ behaviour for `-d` / `-F` / `-E`. Interactive-tty senders still
41
+ go through unwaited so typing isn't gated on a peer.
42
+
43
+ ## 0.16.4 — 2026-04-20
44
+
45
+ - **Use omq ~> v0.26.2
46
+
47
+ ## 0.16.3 — 2026-04-20
48
+
49
+ - **Use omq ~> v0.26.1
50
+
3
51
  ## 0.16.2 — 2026-04-18
4
52
 
5
53
  - **Use omq ~> v0.24.0 and omq-ffi >= v0.3.1**
@@ -155,7 +155,11 @@ module OMQ
155
155
 
156
156
 
157
157
  def bounded_or_scheduled_send?
158
- config.interval || config.data || config.file || @send_eval_proc
158
+ return true if config.interval || config.data || config.file || @send_eval_proc
159
+ # Piped stdin (non-tty) is also a bounded send plan: we read
160
+ # until EOF and exit. Without a peer wait, a bind-mode sender
161
+ # dumps straight into HWM and closes before any peer connects.
162
+ !config.stdin_is_tty
159
163
  end
160
164
 
161
165
 
@@ -533,7 +537,9 @@ module OMQ
533
537
  def set_process_title(endpoints: nil)
534
538
  eps = endpoints || config.endpoints
535
539
  title = ["omq", config.type_name]
536
- title << "-z" if config.compress
540
+ if (flag = SocketSetup.compress_flag(config))
541
+ title << flag
542
+ end
537
543
  title << "-P#{config.parallel}" if config.parallel
538
544
  eps.each do |ep|
539
545
  title << (ep.respond_to?(:url) ? ep.url : ep.to_s)
@@ -144,12 +144,26 @@ module OMQ
144
144
 
145
145
  -- Compression ----------------------------------------------
146
146
 
147
- # -z rewrites tcp:// endpoints to zstd+tcp:// (the dedicated
148
- # compressed TCP transport from omq-zstd). Both peers must
149
- # pass -z; non-TCP endpoints are unaffected.
147
+ # -z / -Z / --lz4 rewrite tcp:// endpoints to zstd+tcp:// or
148
+ # lz4+tcp:// (dedicated compressed transports from omq-zstd
149
+ # and omq-lz4). Both peers must agree on the codec; non-TCP
150
+ # endpoints are unaffected.
150
151
  omq pull --bind tcp://:5557 -z &
151
152
  echo "compressible data" | omq push --connect tcp://localhost:5557 -z
152
153
 
154
+ # LZ4: no entropy stage, ~4–8× faster encode than zstd,
155
+ # ~3× less memory. Pick it when CPU or RAM is tighter than
156
+ # bandwidth.
157
+ omq pull --bind tcp://:5557 --lz4 &
158
+ echo "hello" | omq push --connect tcp://localhost:5557 --lz4
159
+
160
+ # Explicit spec form (same for -e, -f, or --compress):
161
+ omq pull -b tcp://:5557 --compress=zstd:3
162
+ omq pull -b tcp://:5557 --compress=lz4
163
+
164
+ # Pipe with asymmetric codecs (decompresses lz4 in, re-emits zstd out):
165
+ omq pipe --in --lz4 -c tcp://src:5555 --out -z -c tcp://dst:6666
166
+
153
167
  -- CURVE Encryption -----------------------------------------
154
168
 
155
169
  # server (prints OMQ_SERVER_KEY=...)
@@ -245,8 +259,12 @@ module OMQ
245
259
  sndbuf: nil,
246
260
  rcvbuf: nil,
247
261
  conflate: false,
248
- compress: false,
249
- compress_level: nil,
262
+ compress: nil, # nil | :zstd | :lz4
263
+ compress_level: nil, # Integer (zstd only)
264
+ in_compress: nil,
265
+ in_compress_level: nil,
266
+ out_compress: nil,
267
+ out_compress_level: nil,
250
268
  send_expr: nil,
251
269
  recv_expr: nil,
252
270
  parallel: nil,
@@ -405,18 +423,26 @@ module OMQ
405
423
  o.separator "\nDelivery:"
406
424
  o.on("--conflate", "Keep only last message per subscriber (PUB/RADIO)") { opts[:conflate] = true }
407
425
 
408
- o.separator "\nCompression:"
409
- o.on("-z", "Zstd compression (level -3, fast)") do
410
- opts[:compress] = true
411
- opts[:compress_level] = -3
412
- end
413
- o.on("-Z", "Zstd compression (level 3, better ratio)") do
414
- opts[:compress] = true
415
- opts[:compress_level] = 3
426
+ o.separator "\nCompression (modal with --in/--out for pipe):"
427
+ set_compress = lambda do |codec, level|
428
+ case pipe_side
429
+ when :in
430
+ opts[:in_compress] = codec
431
+ opts[:in_compress_level] = level
432
+ when :out
433
+ opts[:out_compress] = codec
434
+ opts[:out_compress_level] = level
435
+ else
436
+ opts[:compress] = codec
437
+ opts[:compress_level] = level
438
+ end
416
439
  end
417
- o.on("--compress=LEVEL", Integer, "Zstd compression with custom level (e.g. 19, -1)") do |v|
418
- opts[:compress] = true
419
- opts[:compress_level] = v
440
+ o.on("-z", "Zstd compression (level -3, fast)") { set_compress.call(:zstd, -3) }
441
+ o.on("-Z", "Zstd compression (level 3, better ratio)") { set_compress.call(:zstd, 3) }
442
+ o.on("--lz4", "LZ4 compression (FAST)") { set_compress.call(:lz4, nil) }
443
+ o.on("--compress=SPEC", "Explicit codec[:level], e.g. zstd, zstd:3, zstd:-1, lz4") do |v|
444
+ codec, level = parse_compress_spec(v)
445
+ set_compress.call(codec, level)
420
446
  end
421
447
 
422
448
  o.separator "\nProcessing (-e = incoming, -E = outgoing):"
@@ -499,6 +525,30 @@ module OMQ
499
525
  end
500
526
 
501
527
 
528
+ # Parses a --compress SPEC string.
529
+ #
530
+ # Accepted forms:
531
+ # "zstd" → [:zstd, nil] (SocketSetup picks the default)
532
+ # "zstd:3" → [:zstd, 3]
533
+ # "zstd:-1" → [:zstd, -1]
534
+ # "lz4" → [:lz4, nil]
535
+ # "-1" | "19" → [:zstd, N] (back-compat with 0.16.x --compress=LEVEL)
536
+ #
537
+ # Aborts on anything else (including "lz4:N", since the lz4+tcp
538
+ # transport has no compression-level knob).
539
+ #
540
+ def parse_compress_spec(str)
541
+ case str
542
+ when /\Azstd\z/ then [:zstd, nil]
543
+ when /\Azstd:(-?\d+)\z/ then [:zstd, $1.to_i]
544
+ when /\Alz4\z/ then [:lz4, nil]
545
+ when /\A-?\d+\z/ then [:zstd, str.to_i]
546
+ else
547
+ abort "invalid --compress spec: #{str} (use zstd, zstd:LEVEL, or lz4)"
548
+ end
549
+ end
550
+
551
+
502
552
  # Parses a byte size string with an optional K/M/G suffix (binary,
503
553
  # i.e. 1K = 1024 bytes).
504
554
  #
@@ -547,7 +597,7 @@ module OMQ
547
597
  abort "pipe requires exactly 2 endpoints (pull-side and push-side), or use --in/--out" if opts[:endpoints].size != 2
548
598
  end
549
599
  else
550
- abort "--in/--out are only valid for pipe" if opts[:in_endpoints].any? || opts[:out_endpoints].any?
600
+ abort "--in/--out are only valid for pipe" if opts[:in_endpoints].any? || opts[:out_endpoints].any? || opts[:in_compress] || opts[:out_compress]
551
601
  abort "At least one --connect or --bind is required" if opts[:connects].empty? && opts[:binds].empty?
552
602
  end
553
603
  abort "--data and --file are mutually exclusive" if opts[:data] && opts[:file]
@@ -45,6 +45,10 @@ module OMQ
45
45
  :conflate,
46
46
  :compress,
47
47
  :compress_level,
48
+ :in_compress,
49
+ :in_compress_level,
50
+ :out_compress,
51
+ :out_compress_level,
48
52
  :send_expr,
49
53
  :recv_expr,
50
54
  :parallel,
data/lib/omq/cli/pipe.rb CHANGED
@@ -91,7 +91,8 @@ module OMQ
91
91
  SocketSetup.attach_endpoints sock, in_eps,
92
92
  config: config,
93
93
  verbose: config.verbose,
94
- timestamps: config.timestamps
94
+ timestamps: config.timestamps,
95
+ side: :in
95
96
  end
96
97
 
97
98
  push = OMQ::PUSH.new(**kwargs).tap do |sock|
@@ -99,7 +100,8 @@ module OMQ
99
100
  SocketSetup.attach_endpoints sock, out_eps,
100
101
  config: config,
101
102
  verbose: config.verbose,
102
- timestamps: config.timestamps
103
+ timestamps: config.timestamps,
104
+ side: :out
103
105
  end
104
106
 
105
107
  [pull, push]
@@ -186,12 +188,22 @@ module OMQ
186
188
 
187
189
  def set_pipe_process_title
188
190
  in_eps, out_eps = resolve_endpoints
191
+ in_flag = SocketSetup.compress_flag(config, side: :in)
192
+ out_flag = SocketSetup.compress_flag(config, side: :out)
189
193
  title = ["omq pipe"]
190
- title << "-z" if config.compress
191
194
  title << "-P#{config.parallel}" if config.parallel
192
- title.concat(in_eps.map(&:url))
193
- title << "->"
194
- title.concat(out_eps.map(&:url))
195
+ if in_flag && in_flag == out_flag
196
+ title << in_flag
197
+ title.concat(in_eps.map(&:url))
198
+ title << "->"
199
+ title.concat(out_eps.map(&:url))
200
+ else
201
+ title << "--in" << in_flag if in_flag
202
+ title.concat(in_eps.map(&:url))
203
+ title << "->"
204
+ title << out_flag if out_flag
205
+ title.concat(out_eps.map(&:url))
206
+ end
195
207
 
196
208
  Process.setproctitle(title.join(" "))
197
209
  end
@@ -44,8 +44,8 @@ module OMQ
44
44
  OMQ::CLI::SocketSetup.apply_options(@pull, @config)
45
45
  OMQ::CLI::SocketSetup.apply_options(@push, @config)
46
46
  OMQ::CLI::SocketSetup.apply_recv_maxsz(@pull, @config)
47
- OMQ::CLI::SocketSetup.attach_endpoints(@pull, @in_eps, config: @config, verbose: 0)
48
- OMQ::CLI::SocketSetup.attach_endpoints(@push, @out_eps, config: @config, verbose: 0)
47
+ OMQ::CLI::SocketSetup.attach_endpoints(@pull, @in_eps, config: @config, verbose: 0, side: :in)
48
+ OMQ::CLI::SocketSetup.attach_endpoints(@push, @out_eps, config: @config, verbose: 0, side: :out)
49
49
  end
50
50
 
51
51
 
@@ -22,23 +22,67 @@ module OMQ
22
22
  # it entirely with --recv-maxsz 0.
23
23
  DEFAULT_RECV_MAXSZ = 1 << 20
24
24
 
25
- # Upgrades a +tcp://+ URL to +zstd+tcp://+ when compression is enabled.
26
- # Returns the URL unchanged for non-TCP or when compression is off.
25
+ # Default zstd level when the user picks zstd without specifying one
26
+ # (e.g. `--compress=zstd`). Matches the `-z` shortcut.
27
+ DEFAULT_ZSTD_LEVEL = -3
28
+
29
+ # Resolves compression state for a given pipe +side+ (:in, :out, or nil).
30
+ # Per-side settings (in_compress / out_compress) override the global
31
+ # +config.compress+. Returns +[codec, level]+ where codec is one of
32
+ # +nil+ (off), +:zstd+, or +:lz4+.
27
33
  #
28
- def self.compress_url(url, config)
29
- return url unless config.compress
34
+ def self.resolve_compress(config, side)
35
+ scoped =
36
+ case side
37
+ when :in then !config.in_compress.nil?
38
+ when :out then !config.out_compress.nil?
39
+ end
40
+
41
+ if scoped && side == :in
42
+ [config.in_compress, config.in_compress_level]
43
+ elsif scoped && side == :out
44
+ [config.out_compress, config.out_compress_level]
45
+ else
46
+ [config.compress, config.compress_level]
47
+ end
48
+ end
49
+
50
+
51
+ # Upgrades a +tcp://+ URL to the codec-specific variant
52
+ # (+zstd+tcp://+ or +lz4+tcp://+) when compression is enabled
53
+ # for the given +side+. Returns the URL unchanged for non-TCP or
54
+ # when compression is off.
55
+ #
56
+ def self.compress_url(url, config, side: nil)
57
+ codec, _ = resolve_compress(config, side)
58
+ return url if codec.nil?
30
59
  return url unless url.start_with?("tcp://")
31
60
 
32
- url.sub("tcp://", "zstd+tcp://")
61
+ url.sub("tcp://", "#{codec}+tcp://")
33
62
  end
34
63
 
35
64
 
36
- # Returns bind/connect kwargs for compression (level:) when enabled.
65
+ # Returns bind/connect kwargs for the chosen codec on the given
66
+ # +side+. Zstd accepts a +level:+; LZ4 takes no tuning knobs.
37
67
  #
38
- def self.compress_opts(config)
39
- return {} unless config.compress
68
+ def self.compress_opts(config, side: nil)
69
+ codec, level = resolve_compress(config, side)
70
+ case codec
71
+ when :zstd then { level: level || DEFAULT_ZSTD_LEVEL }
72
+ else {}
73
+ end
74
+ end
40
75
 
41
- { level: config.compress_level || -3 }
76
+
77
+ # Human-readable CLI flag for a resolved compress state, suitable
78
+ # for process titles and proctitle-like log lines.
79
+ #
80
+ def self.compress_flag(config, side: nil)
81
+ codec, level = resolve_compress(config, side)
82
+ case codec
83
+ when :zstd then level == 3 ? "-Z" : "-z"
84
+ when :lz4 then "--lz4"
85
+ end
42
86
  end
43
87
 
44
88
 
@@ -103,12 +147,14 @@ module OMQ
103
147
  # Bind/connect +sock+ from an Array of Endpoint objects.
104
148
  # Used by PipeRunner, which works with structured endpoint lists.
105
149
  # +verbose+ gates logging (>= 1), +timestamps+ controls prefix.
150
+ # +side+ selects per-socket compression (:in for PULL, :out for
151
+ # PUSH in a pipe); nil falls back to global compress settings.
106
152
  #
107
- def self.attach_endpoints(sock, endpoints, config: nil, verbose: 0, timestamps: nil)
108
- opts = config ? compress_opts(config) : {}
153
+ def self.attach_endpoints(sock, endpoints, config: nil, verbose: 0, timestamps: nil, side: nil)
154
+ opts = config ? compress_opts(config, side: side) : {}
109
155
 
110
156
  endpoints.each do |ep|
111
- url = config ? compress_url(ep.url, config) : ep.url
157
+ url = config ? compress_url(ep.url, config, side: side) : ep.url
112
158
 
113
159
  if ep.bind?
114
160
  uri = sock.bind(url, **opts)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module CLI
5
- VERSION = "0.16.2"
5
+ VERSION = "0.17.1"
6
6
  end
7
7
  end
data/lib/omq/cli.rb CHANGED
@@ -207,6 +207,7 @@ module OMQ
207
207
  require "omq/channel"
208
208
  require "omq/peer"
209
209
  require "omq/zstd"
210
+ require "omq/lz4"
210
211
  require "async"
211
212
  require "json"
212
213
  require "console"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -15,34 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.24'
18
+ version: '0.27'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.24'
25
+ version: '0.27'
26
26
  - !ruby/object:Gem::Dependency
27
- name: omq-ffi
27
+ name: ffi
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '0.3'
33
30
  - - ">="
34
31
  - !ruby/object:Gem::Version
35
- version: 0.3.1
32
+ version: '0'
36
33
  type: :runtime
37
34
  prerelease: false
38
35
  version_requirements: !ruby/object:Gem::Requirement
39
36
  requirements:
40
- - - "~>"
41
- - !ruby/object:Gem::Version
42
- version: '0.3'
43
37
  - - ">="
44
38
  - !ruby/object:Gem::Version
45
- version: 0.3.1
39
+ version: '0'
46
40
  - !ruby/object:Gem::Dependency
47
41
  name: omq-zstd
48
42
  requirement: !ruby/object:Gem::Requirement
@@ -57,6 +51,20 @@ dependencies:
57
51
  - - "~>"
58
52
  - !ruby/object:Gem::Version
59
53
  version: '0.4'
54
+ - !ruby/object:Gem::Dependency
55
+ name: omq-lz4
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.2'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.2'
60
68
  - !ruby/object:Gem::Dependency
61
69
  name: msgpack
62
70
  requirement: !ruby/object:Gem::Requirement
@@ -88,8 +96,8 @@ dependencies:
88
96
  description: Command-line tool for sending and receiving ZeroMQ messages on any socket
89
97
  type (REQ/REP, PUB/SUB, PUSH/PULL, DEALER/ROUTER, and all draft types). Supports
90
98
  Ruby eval (-e/-E), script handlers (-r), pipe virtual socket with Ractor parallelism,
91
- multiple formats (ASCII, JSON Lines, msgpack, Marshal), Zstd compression, and CURVE
92
- encryption. Like nngcat from libnng, but with Ruby superpowers.
99
+ multiple formats (ASCII, JSON Lines, msgpack, Marshal), Zstd/LZ4 compression, and
100
+ CURVE encryption. Like nngcat from libnng, but with Ruby superpowers.
93
101
  email:
94
102
  - paddor@gmail.com
95
103
  executables: