amq-protocol 2.3.4 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90852fae30dd15d9f1f6ee5f9c0d98e856c3efcbd4c11e7e824caf63a01360f7
4
- data.tar.gz: a000c7b2f435fc08f2d9c90a765f81e0414ca1c3038fcab1915ff7bcd4bdb929
3
+ metadata.gz: 6d6619ef67f8b1e30048f898796d21854eae2a35dfdfe83286e0a5e894cbbf1c
4
+ data.tar.gz: 0d62824daba6da208b96b7681e4c1db4f5c32a93a7a8a7714b875b8ad8f603e2
5
5
  SHA512:
6
- metadata.gz: 718a613e5a2355791ef4d07bd38676b005cb00cd54969183a31eeba96cc3439ba223a8d9636050aa9aaf2ac5ca63150d3e3c53ce3e4638b53191097183b47ba3
7
- data.tar.gz: 598de750f0c0ba6c838f4d1f34b4fb24f05b0a580c6b8beb7804d4edae437c628c18ac56d8ef87afc78ad56876ca4baafcf051f90f1eea9bb0cf3d48e993ea66
6
+ metadata.gz: cac00e1594a26adcb0863b6505ba789e8fce9a1dee3a38584e58cf838479661ba857a37ac3d747c69e7751549e15877a6478dd51559a8003af70c68e856badb1
7
+ data.tar.gz: 040e923fd9dcf5931a157007835085ec30b7ac44897c5a76ee05545f14f76cf431a3611f73fddb562e6bf9230fb82151fc808bfed24bc2244f12c85164541b6e
data/ChangeLog.md CHANGED
@@ -1,7 +1,30 @@
1
- ## Changes between 2.3.4 and 2.3.5 (in development)
1
+ ## Changes between 2.4.0 and 2.5.0 (in development)
2
2
 
3
3
  No changes yet.
4
4
 
5
+ ## Changes between 2.3.4 and 2.4.0 (Dec 30, 2025)
6
+
7
+ ### Bug Fixes
8
+
9
+ * Fixed `BadResponseError` constructor which referenced undefined variable `data` instead of `actual`
10
+
11
+ ### Performance Improvements
12
+
13
+ Optimized encoding and decoding hot paths:
14
+
15
+ * Built-in `Q>`/`q>` packing/unpacking directives are 6-7x faster than the original implementation (that originally targeted Ruby 1.8.x)
16
+ * Switched to `unpack1` instead of `unpack().first` throughout
17
+ * Use `byteslice` instead of `slice` for binary string operations
18
+ * Use `getbyte` for single byte access (4x faster than alternatives)
19
+ * Adopted `frozen_string_literal` pragma
20
+
21
+ The improvements on Ruby 3.4 are very meaningful:
22
+
23
+ * `AMQ::Pack.pack_uint64_big_endian`: about 6.6x faster
24
+ * `AMQ::Pack.unpack_uint64_big_endian`: about 7.2x faster
25
+ * `Basic.Deliver.decode`: about 1.7x faster
26
+ * `Basic.Ack/Nack/Reject.encode`: about 2.5x faster
27
+
5
28
 
6
29
  ## Changes between 2.3.3 and 2.3.4 (May 12, 2025)
7
30
 
@@ -16,13 +39,13 @@ GitHub issue: [#80](https://github.com/ruby-amqp/amq-protocol/pull/80)
16
39
 
17
40
  ### Improved Compatibility with Ruby 3.4
18
41
 
19
- Contribiuted by @BenTalagan.
42
+ Contributed by @BenTalagan.
20
43
 
21
44
  GitHub issue: [#79](https://github.com/ruby-amqp/amq-protocol/pull/79)
22
45
 
23
46
  ### Support Binary-Encoded Frozen Strings for Payloads
24
47
 
25
- Contribiuted by @djrodgerspryor.
48
+ Contributed by @djrodgerspryor.
26
49
 
27
50
  GitHub issue: [#78](https://github.com/ruby-amqp/amq-protocol/pull/78)
28
51
 
data/Gemfile CHANGED
@@ -10,6 +10,11 @@ group :development do
10
10
  gem "rake"
11
11
  end
12
12
 
13
+ group :benchmark do
14
+ gem "benchmark-ips", "~> 2.12"
15
+ gem "benchmark-memory", "~> 0.2"
16
+ end
17
+
13
18
  group :test do
14
19
  gem "rspec", ">= 3.8.0"
15
20
  gem "rspec-its"
data/README.md CHANGED
@@ -9,6 +9,8 @@ needs for you, including RabbitMQ extensions to AMQP 0.9.1.
9
9
 
10
10
  ## Supported Ruby Versions
11
11
 
12
+ * amq-protocol `2.3.4` has fixes for Ruby 3.5 compatibility
13
+ * amq-protocol `2.3.3` has fixes for Ruby 3.4 compatibility
12
14
  * amq-protocol `2.3.0` only supports Ruby 2.2+.
13
15
  * amq-protocol `2.0.0` through `2.2.0` and later supports Ruby 2.0+.
14
16
  * amq-protocol `1.9.2` was the last version to support Ruby 1.8 and 1.9.
@@ -45,21 +47,14 @@ To run tests, use
45
47
  amq-protocol is maintained by [Michael Klishin](https://github.com/michaelklishin).
46
48
 
47
49
 
48
- ## CI Status
49
-
50
- [![Build Status](https://secure.travis-ci.org/ruby-amqp/amq-protocol.svg)](https://travis-ci.org/ruby-amqp/amq-protocol)
51
-
52
-
53
50
  ## Issues
54
51
 
55
52
  Please report any issues you may find to our [Issue tracker](http://github.com/ruby-amqp/amq-protocol/issues) on GitHub.
56
53
 
57
-
58
54
  ## Mailing List
59
55
 
60
56
  Any questions you may have should be sent to the [Ruby AMQP mailing list](http://groups.google.com/group/ruby-amqp).
61
57
 
62
-
63
58
  ## License
64
59
 
65
60
  MIT (see LICENSE in the repository root).
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
6
+
7
+ require "amq/protocol/client"
8
+ require "benchmark/ips"
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "Frame Encoding Benchmarks on #{RUBY_DESCRIPTION}"
13
+ puts "-" * 80
14
+
15
+ # Test data
16
+ SMALL_PAYLOAD = "x" * 100
17
+ MEDIUM_PAYLOAD = "x" * 1024
18
+ LARGE_PAYLOAD = "x" * 16384
19
+
20
+ CHANNEL = 1
21
+
22
+ Benchmark.ips do |x|
23
+ x.config(time: 5, warmup: 2)
24
+
25
+ x.report("Frame.encode(:method, small)") do
26
+ AMQ::Protocol::Frame.encode(:method, SMALL_PAYLOAD, CHANNEL)
27
+ end
28
+
29
+ x.report("Frame.encode(:method, medium)") do
30
+ AMQ::Protocol::Frame.encode(:method, MEDIUM_PAYLOAD, CHANNEL)
31
+ end
32
+
33
+ x.report("Frame.encode(:body, large)") do
34
+ AMQ::Protocol::Frame.encode(:body, LARGE_PAYLOAD, CHANNEL)
35
+ end
36
+
37
+ x.report("MethodFrame.new + encode") do
38
+ frame = AMQ::Protocol::MethodFrame.new(SMALL_PAYLOAD, CHANNEL)
39
+ frame.encode
40
+ end
41
+
42
+ x.report("BodyFrame.new + encode") do
43
+ frame = AMQ::Protocol::BodyFrame.new(MEDIUM_PAYLOAD, CHANNEL)
44
+ frame.encode
45
+ end
46
+
47
+ x.report("HeartbeatFrame.encode") do
48
+ AMQ::Protocol::HeartbeatFrame.encode
49
+ end
50
+
51
+ x.compare!
52
+ end
53
+
54
+ puts
55
+ puts "-" * 80
56
+ puts "Frame Header Decoding"
57
+ puts "-" * 80
58
+
59
+ # Encoded frame headers
60
+ METHOD_HEADER = [1, 0, 1, 0, 0, 0, 100].pack("CnN") # type=1, channel=1, size=100
61
+ BODY_HEADER = [3, 0, 5, 0, 0, 16, 0].pack("CnN") # type=3, channel=5, size=4096
62
+
63
+ Benchmark.ips do |x|
64
+ x.config(time: 5, warmup: 2)
65
+
66
+ x.report("Frame.decode_header (method)") do
67
+ AMQ::Protocol::Frame.decode_header(METHOD_HEADER)
68
+ end
69
+
70
+ x.report("Frame.decode_header (body)") do
71
+ AMQ::Protocol::Frame.decode_header(BODY_HEADER)
72
+ end
73
+
74
+ x.compare!
75
+ end
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
6
+
7
+ require "amq/protocol/client"
8
+ require "benchmark/ips"
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "AMQP Method Encoding/Decoding Benchmarks on #{RUBY_DESCRIPTION}"
13
+ puts "-" * 80
14
+
15
+ FRAME_SIZE = 131072 # 128KB, typical default
16
+
17
+ # Common message properties
18
+ BASIC_PROPERTIES = {
19
+ content_type: "application/json",
20
+ delivery_mode: 2,
21
+ priority: 0,
22
+ headers: { "x-custom" => "value" }
23
+ }.freeze
24
+
25
+ MINIMAL_PROPERTIES = {
26
+ delivery_mode: 2
27
+ }.freeze
28
+
29
+ # Payloads
30
+ SMALL_BODY = '{"id":1}'.freeze
31
+ MEDIUM_BODY = ('x' * 1024).freeze
32
+ LARGE_BODY = ('x' * 65536).freeze
33
+
34
+ puts "=== Basic.Publish (Full Message Encoding) ==="
35
+ puts "This is the critical hot path for publishing messages"
36
+ puts
37
+
38
+ Benchmark.ips do |x|
39
+ x.config(time: 5, warmup: 2)
40
+
41
+ x.report("Publish small (8B) + minimal props") do
42
+ AMQ::Protocol::Basic::Publish.encode(
43
+ 1, # channel
44
+ SMALL_BODY, # payload
45
+ MINIMAL_PROPERTIES,
46
+ "", # exchange
47
+ "test.queue", # routing_key
48
+ false, # mandatory
49
+ false, # immediate
50
+ FRAME_SIZE
51
+ )
52
+ end
53
+
54
+ x.report("Publish small (8B) + full props") do
55
+ AMQ::Protocol::Basic::Publish.encode(
56
+ 1,
57
+ SMALL_BODY,
58
+ BASIC_PROPERTIES,
59
+ "",
60
+ "test.queue",
61
+ false,
62
+ false,
63
+ FRAME_SIZE
64
+ )
65
+ end
66
+
67
+ x.report("Publish medium (1KB) + full props") do
68
+ AMQ::Protocol::Basic::Publish.encode(
69
+ 1,
70
+ MEDIUM_BODY,
71
+ BASIC_PROPERTIES,
72
+ "",
73
+ "test.queue",
74
+ false,
75
+ false,
76
+ FRAME_SIZE
77
+ )
78
+ end
79
+
80
+ x.report("Publish large (64KB) + full props") do
81
+ AMQ::Protocol::Basic::Publish.encode(
82
+ 1,
83
+ LARGE_BODY,
84
+ BASIC_PROPERTIES,
85
+ "",
86
+ "test.queue",
87
+ false,
88
+ false,
89
+ FRAME_SIZE
90
+ )
91
+ end
92
+
93
+ x.compare!
94
+ end
95
+
96
+ # Create sample encoded methods for decoding benchmarks
97
+ puts
98
+ puts "=== Method Decoding ==="
99
+
100
+ # Simulate encoded Basic.Deliver frame payload (after class/method ID)
101
+ # Basic.Deliver: consumer_tag(shortstr), delivery_tag(longlong), redelivered(bit), exchange(shortstr), routing_key(shortstr)
102
+ def make_deliver_payload(consumer_tag, delivery_tag, exchange, routing_key)
103
+ buffer = String.new(encoding: 'BINARY')
104
+ buffer << consumer_tag.bytesize.chr
105
+ buffer << consumer_tag
106
+ buffer << AMQ::Pack.pack_uint64_big_endian(delivery_tag)
107
+ buffer << "\x00" # redelivered = false
108
+ buffer << exchange.bytesize.chr
109
+ buffer << exchange
110
+ buffer << routing_key.bytesize.chr
111
+ buffer << routing_key
112
+ buffer
113
+ end
114
+
115
+ DELIVER_PAYLOAD_SHORT = make_deliver_payload("ctag", 1, "", "q")
116
+ DELIVER_PAYLOAD_TYPICAL = make_deliver_payload("bunny-consumer-12345", 999999, "amq.topic", "events.user.created")
117
+
118
+ Benchmark.ips do |x|
119
+ x.config(time: 5, warmup: 2)
120
+
121
+ x.report("Basic.Deliver.decode (short)") do
122
+ AMQ::Protocol::Basic::Deliver.decode(DELIVER_PAYLOAD_SHORT)
123
+ end
124
+
125
+ x.report("Basic.Deliver.decode (typical)") do
126
+ AMQ::Protocol::Basic::Deliver.decode(DELIVER_PAYLOAD_TYPICAL)
127
+ end
128
+
129
+ x.compare!
130
+ end
131
+
132
+ puts
133
+ puts "=== Properties Encoding/Decoding ==="
134
+
135
+ Benchmark.ips do |x|
136
+ x.config(time: 5, warmup: 2)
137
+
138
+ x.report("encode_properties (minimal)") do
139
+ AMQ::Protocol::Basic.encode_properties(100, MINIMAL_PROPERTIES)
140
+ end
141
+
142
+ x.report("encode_properties (full)") do
143
+ AMQ::Protocol::Basic.encode_properties(1024, BASIC_PROPERTIES)
144
+ end
145
+
146
+ x.compare!
147
+ end
148
+
149
+ # Create encoded properties for decode benchmark
150
+ ENCODED_MINIMAL_PROPS = AMQ::Protocol::Basic.encode_properties(100, MINIMAL_PROPERTIES)
151
+ ENCODED_FULL_PROPS = AMQ::Protocol::Basic.encode_properties(1024, BASIC_PROPERTIES)
152
+
153
+ # Skip the first 12 bytes (class_id, weight, body_size)
154
+ PROPS_DATA_MINIMAL = ENCODED_MINIMAL_PROPS[12..-1]
155
+ PROPS_DATA_FULL = ENCODED_FULL_PROPS[12..-1]
156
+
157
+ Benchmark.ips do |x|
158
+ x.config(time: 5, warmup: 2)
159
+
160
+ x.report("decode_properties (minimal)") do
161
+ AMQ::Protocol::Basic.decode_properties(PROPS_DATA_MINIMAL)
162
+ end
163
+
164
+ x.report("decode_properties (full)") do
165
+ AMQ::Protocol::Basic.decode_properties(PROPS_DATA_FULL)
166
+ end
167
+
168
+ x.compare!
169
+ end
170
+
171
+ puts
172
+ puts "=== Other Common Methods ==="
173
+
174
+ Benchmark.ips do |x|
175
+ x.config(time: 5, warmup: 2)
176
+
177
+ x.report("Basic.Ack.encode") do
178
+ AMQ::Protocol::Basic::Ack.encode(1, 12345, false)
179
+ end
180
+
181
+ x.report("Basic.Nack.encode") do
182
+ AMQ::Protocol::Basic::Nack.encode(1, 12345, false, true)
183
+ end
184
+
185
+ x.report("Basic.Reject.encode") do
186
+ AMQ::Protocol::Basic::Reject.encode(1, 12345, true)
187
+ end
188
+
189
+ x.report("Queue.Declare.encode") do
190
+ AMQ::Protocol::Queue::Declare.encode(1, "test.queue", false, true, false, false, false, {})
191
+ end
192
+
193
+ x.report("Exchange.Declare.encode") do
194
+ AMQ::Protocol::Exchange::Declare.encode(1, "test.exchange", "topic", false, true, false, false, false, {})
195
+ end
196
+
197
+ x.compare!
198
+ end
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
6
+
7
+ require "amq/protocol/client"
8
+ require "benchmark/ips"
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "Pack/Unpack Micro-benchmarks on #{RUBY_DESCRIPTION}"
13
+ puts "-" * 80
14
+
15
+ # Test data
16
+ UINT64_VALUE = 0x123456789ABCDEF0
17
+ UINT32_VALUE = 0x12345678
18
+ UINT16_VALUE = 0x1234
19
+
20
+ PACKED_UINT64_BE = (+"\x12\x34\x56\x78\x9A\xBC\xDE\xF0").force_encoding('BINARY').freeze
21
+ PACKED_UINT32_BE = (+"\x12\x34\x56\x78").force_encoding('BINARY').freeze
22
+ PACKED_UINT16_BE = (+"\x12\x34").force_encoding('BINARY').freeze
23
+
24
+ puts "=== Pack Operations ==="
25
+
26
+ Benchmark.ips do |x|
27
+ x.config(time: 5, warmup: 2)
28
+
29
+ x.report("AMQ::Pack.pack_uint64_big_endian") do
30
+ AMQ::Pack.pack_uint64_big_endian(UINT64_VALUE)
31
+ end
32
+
33
+ # Alternative: direct pack with 'Q>' directive (Ruby 1.9.3+)
34
+ x.report("[val].pack('Q>')") do
35
+ [UINT64_VALUE].pack('Q>')
36
+ end
37
+
38
+ x.report("[val].pack('N') uint32") do
39
+ [UINT32_VALUE].pack('N')
40
+ end
41
+
42
+ x.report("[val].pack('n') uint16") do
43
+ [UINT16_VALUE].pack('n')
44
+ end
45
+
46
+ x.compare!
47
+ end
48
+
49
+ puts
50
+ puts "=== Unpack Operations ==="
51
+
52
+ Benchmark.ips do |x|
53
+ x.config(time: 5, warmup: 2)
54
+
55
+ x.report("AMQ::Pack.unpack_uint64_big_endian") do
56
+ AMQ::Pack.unpack_uint64_big_endian(PACKED_UINT64_BE)
57
+ end
58
+
59
+ # Alternative: direct unpack with 'Q>' directive
60
+ x.report("data.unpack('Q>')") do
61
+ PACKED_UINT64_BE.unpack('Q>')
62
+ end
63
+
64
+ x.report("data.unpack1('Q>')") do
65
+ PACKED_UINT64_BE.unpack1('Q>')
66
+ end
67
+
68
+ x.report("data.unpack('N').first") do
69
+ PACKED_UINT32_BE.unpack('N').first
70
+ end
71
+
72
+ x.report("data.unpack1('N')") do
73
+ PACKED_UINT32_BE.unpack1('N')
74
+ end
75
+
76
+ x.compare!
77
+ end
78
+
79
+ puts
80
+ puts "=== String Slicing ==="
81
+
82
+ DATA = ("x" * 1000).force_encoding('BINARY').freeze
83
+
84
+ Benchmark.ips do |x|
85
+ x.config(time: 5, warmup: 2)
86
+
87
+ x.report("data[offset, length]") do
88
+ DATA[100, 50]
89
+ end
90
+
91
+ x.report("data.byteslice(offset, length)") do
92
+ DATA.byteslice(100, 50)
93
+ end
94
+
95
+ x.report("data.slice(offset, length)") do
96
+ DATA.slice(100, 50)
97
+ end
98
+
99
+ x.compare!
100
+ end
101
+
102
+ puts
103
+ puts "=== Single Byte Access ==="
104
+
105
+ Benchmark.ips do |x|
106
+ x.config(time: 5, warmup: 2)
107
+
108
+ x.report("data[0, 1].unpack('C').first") do
109
+ DATA[0, 1].unpack('C').first
110
+ end
111
+
112
+ x.report("data.getbyte(0)") do
113
+ DATA.getbyte(0)
114
+ end
115
+
116
+ x.report("data[0].ord") do
117
+ DATA[0].ord
118
+ end
119
+
120
+ x.report("data.unpack1('C')") do
121
+ DATA.unpack1('C')
122
+ end
123
+
124
+ x.compare!
125
+ end
126
+
127
+ puts
128
+ puts "=== Buffer Building ==="
129
+
130
+ Benchmark.ips do |x|
131
+ x.config(time: 5, warmup: 2)
132
+
133
+ x.report("String.new + <<") do
134
+ buf = String.new
135
+ buf << "hello"
136
+ buf << "world"
137
+ buf << [1234].pack('N')
138
+ buf
139
+ end
140
+
141
+ x.report("+'' + <<") do
142
+ buf = +''
143
+ buf << "hello"
144
+ buf << "world"
145
+ buf << [1234].pack('N')
146
+ buf
147
+ end
148
+
149
+ x.report("Array#join") do
150
+ parts = []
151
+ parts << "hello"
152
+ parts << "world"
153
+ parts << [1234].pack('N')
154
+ parts.join
155
+ end
156
+
157
+ x.compare!
158
+ end
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ # Master benchmark runner
6
+ # Usage: ruby benchmarks/run_all.rb
7
+
8
+ require 'fileutils'
9
+
10
+ BENCHMARK_DIR = File.dirname(__FILE__)
11
+ RESULTS_DIR = File.join(BENCHMARK_DIR, "results")
12
+
13
+ FileUtils.mkdir_p(RESULTS_DIR)
14
+
15
+ benchmarks = %w[
16
+ pack_unpack.rb
17
+ frame_encoding.rb
18
+ table_encoding.rb
19
+ method_encoding.rb
20
+ ]
21
+
22
+ timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
23
+ ruby_version = RUBY_VERSION.gsub('.', '_')
24
+ results_file = File.join(RESULTS_DIR, "benchmark_#{ruby_version}_#{timestamp}.txt")
25
+
26
+ puts "=" * 80
27
+ puts "AMQ-Protocol Benchmark Suite"
28
+ puts "=" * 80
29
+ puts "Ruby: #{RUBY_DESCRIPTION}"
30
+ puts "Time: #{Time.now}"
31
+ puts "Results will be saved to: #{results_file}"
32
+ puts "=" * 80
33
+ puts
34
+
35
+ File.open(results_file, 'w') do |f|
36
+ f.puts "AMQ-Protocol Benchmark Results"
37
+ f.puts "Ruby: #{RUBY_DESCRIPTION}"
38
+ f.puts "Time: #{Time.now}"
39
+ f.puts "=" * 80
40
+ f.puts
41
+
42
+ benchmarks.each do |benchmark|
43
+ benchmark_path = File.join(BENCHMARK_DIR, benchmark)
44
+
45
+ if File.exist?(benchmark_path)
46
+ puts "\n>>> Running #{benchmark}..."
47
+ puts
48
+
49
+ output = `ruby #{benchmark_path} 2>&1`
50
+ puts output
51
+
52
+ f.puts ">>> #{benchmark}"
53
+ f.puts output
54
+ f.puts
55
+ else
56
+ puts "Warning: #{benchmark_path} not found, skipping..."
57
+ end
58
+ end
59
+ end
60
+
61
+ puts
62
+ puts "=" * 80
63
+ puts "Benchmark complete! Results saved to: #{results_file}"
64
+ puts "=" * 80
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
6
+
7
+ require "amq/protocol/client"
8
+ require "benchmark/ips"
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "Table Encoding/Decoding Benchmarks on #{RUBY_DESCRIPTION}"
13
+ puts "-" * 80
14
+
15
+ # Test data - various table sizes and types
16
+ EMPTY_TABLE = {}
17
+
18
+ SIMPLE_TABLE = {
19
+ "key1" => "value1",
20
+ "key2" => 42,
21
+ "key3" => true
22
+ }.freeze
23
+
24
+ TYPICAL_HEADERS = {
25
+ "content_type" => "application/json",
26
+ "content_encoding" => "utf-8",
27
+ "x-custom-header" => "some-value",
28
+ "x-retry-count" => 3,
29
+ "x-timestamp" => Time.now.to_i
30
+ }.freeze
31
+
32
+ COMPLEX_TABLE = {
33
+ "string" => "hello world",
34
+ "integer" => 123456789,
35
+ "float" => 3.14159,
36
+ "boolean_true" => true,
37
+ "boolean_false" => false,
38
+ "nested" => {
39
+ "inner_key" => "inner_value",
40
+ "inner_number" => 999
41
+ },
42
+ "array" => [1, 2, 3, "four", true]
43
+ }.freeze
44
+
45
+ LARGE_TABLE = (1..50).to_h { |i| ["key_#{i}", "value_#{i}"] }.freeze
46
+
47
+ # Pre-encode tables for decode benchmarks
48
+ ENCODED_EMPTY = AMQ::Protocol::Table.encode(EMPTY_TABLE)
49
+ ENCODED_SIMPLE = AMQ::Protocol::Table.encode(SIMPLE_TABLE)
50
+ ENCODED_TYPICAL = AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
51
+ ENCODED_COMPLEX = AMQ::Protocol::Table.encode(COMPLEX_TABLE)
52
+ ENCODED_LARGE = AMQ::Protocol::Table.encode(LARGE_TABLE)
53
+
54
+ puts "Table sizes (bytes): empty=#{ENCODED_EMPTY.bytesize}, simple=#{ENCODED_SIMPLE.bytesize}, typical=#{ENCODED_TYPICAL.bytesize}, complex=#{ENCODED_COMPLEX.bytesize}, large=#{ENCODED_LARGE.bytesize}"
55
+ puts
56
+
57
+ puts "=== Table Encoding ==="
58
+ Benchmark.ips do |x|
59
+ x.config(time: 5, warmup: 2)
60
+
61
+ x.report("encode empty") do
62
+ AMQ::Protocol::Table.encode(EMPTY_TABLE)
63
+ end
64
+
65
+ x.report("encode simple (3 keys)") do
66
+ AMQ::Protocol::Table.encode(SIMPLE_TABLE)
67
+ end
68
+
69
+ x.report("encode typical headers (5 keys)") do
70
+ AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
71
+ end
72
+
73
+ x.report("encode complex (nested/array)") do
74
+ AMQ::Protocol::Table.encode(COMPLEX_TABLE)
75
+ end
76
+
77
+ x.report("encode large (50 keys)") do
78
+ AMQ::Protocol::Table.encode(LARGE_TABLE)
79
+ end
80
+
81
+ x.compare!
82
+ end
83
+
84
+ puts
85
+ puts "=== Table Decoding ==="
86
+ Benchmark.ips do |x|
87
+ x.config(time: 5, warmup: 2)
88
+
89
+ x.report("decode empty") do
90
+ AMQ::Protocol::Table.decode(ENCODED_EMPTY)
91
+ end
92
+
93
+ x.report("decode simple (3 keys)") do
94
+ AMQ::Protocol::Table.decode(ENCODED_SIMPLE)
95
+ end
96
+
97
+ x.report("decode typical headers (5 keys)") do
98
+ AMQ::Protocol::Table.decode(ENCODED_TYPICAL)
99
+ end
100
+
101
+ x.report("decode complex (nested/array)") do
102
+ AMQ::Protocol::Table.decode(ENCODED_COMPLEX)
103
+ end
104
+
105
+ x.report("decode large (50 keys)") do
106
+ AMQ::Protocol::Table.decode(ENCODED_LARGE)
107
+ end
108
+
109
+ x.compare!
110
+ end