amq-protocol 1.5.0 → 1.6.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.
@@ -0,0 +1,22 @@
1
+ module AMQ
2
+ module Protocol
3
+ TLS_PORT = 5671
4
+ SSL_PORT = 5671
5
+
6
+ # caching
7
+ EMPTY_STRING = "".freeze
8
+
9
+ PACK_INT8 = 'c'.freeze
10
+ PACK_CHAR = 'C'.freeze
11
+ PACK_UINT16 = 'n'.freeze
12
+ PACK_UINT16_X2 = 'n2'.freeze
13
+ PACK_UINT32 = 'N'.freeze
14
+ PACK_UINT32_X2 = 'N2'.freeze
15
+ PACK_INT64 = 'q'.freeze
16
+ PACK_UCHAR_UINT32 = 'CN'.freeze
17
+ PACK_CHAR_UINT16_UINT32 = 'cnN'.freeze
18
+
19
+ PACK_32BIT_FLOAT = 'f'.freeze
20
+ PACK_64BIT_FLOAT = 'G'.freeze
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ module AMQ
2
+ module Protocol
3
+ class Error < StandardError
4
+ DEFAULT_MESSAGE = "AMQP error".freeze
5
+
6
+ def self.inherited(subclass)
7
+ @_subclasses ||= []
8
+ @_subclasses << subclass
9
+ end # self.inherited(subclazz)
10
+
11
+ def self.subclasses_with_values
12
+ @_subclasses.select{ |k| defined?(k::VALUE) }
13
+ end # self.subclasses_with_values
14
+
15
+ def self.[](code)
16
+ if result = subclasses_with_values.detect { |klass| klass::VALUE == code }
17
+ result
18
+ else
19
+ raise "No such exception class for code #{code}" unless result
20
+ end # if
21
+ end # self.[]
22
+
23
+ def initialize(message = self.class::DEFAULT_MESSAGE)
24
+ super(message)
25
+ end
26
+ end
27
+
28
+ class FrameTypeError < Protocol::Error
29
+ def initialize(types)
30
+ super("Must be one of #{types.inspect}")
31
+ end
32
+ end
33
+
34
+ class EmptyResponseError < Protocol::Error
35
+ DEFAULT_MESSAGE = "Empty response received from the server."
36
+
37
+ def initialize(message = self.class::DEFAULT_MESSAGE)
38
+ super(message)
39
+ end
40
+ end
41
+
42
+ class BadResponseError < Protocol::Error
43
+ def initialize(argument, expected, actual)
44
+ super("Argument #{argument} has to be #{expected.inspect}, was #{data.inspect}")
45
+ end
46
+ end
47
+
48
+ class SoftError < Protocol::Error
49
+ def self.inherited(subclass)
50
+ Error.inherited(subclass)
51
+ end # self.inherited(subclass)
52
+ end
53
+
54
+ class HardError < Protocol::Error
55
+ def self.inherited(subclass)
56
+ Error.inherited(subclass)
57
+ end # self.inherited(subclass)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ module AMQ
2
+ module Protocol
3
+ # Allows distinguishing between 32-bit and 64-bit floats in Ruby.
4
+ # Useful in cases when RabbitMQ plugins encode
5
+ # values as 32 bit numbers.
6
+ class Float32Bit
7
+ attr_reader :value
8
+
9
+ def initialize(value)
10
+ @value = value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,9 +5,6 @@ require "amq/protocol/type_constants"
5
5
  require "amq/protocol/table_value_encoder"
6
6
  require "amq/protocol/table_value_decoder"
7
7
 
8
- # We will need to introduce concept of mappings, because
9
- # AMQP 0.9, 0.9.1 and RabbitMQ uses different letters for entities
10
- # http://dev.rabbitmq.com/wiki/Amqp091Errata#section_3
11
8
  module AMQ
12
9
  module Protocol
13
10
  class Table
@@ -4,6 +4,7 @@ require "amq/endianness"
4
4
  require "amq/protocol/client"
5
5
  require "amq/protocol/type_constants"
6
6
  require "amq/protocol/table"
7
+ require "amq/protocol/float_32bit"
7
8
 
8
9
  module AMQ
9
10
  module Protocol
@@ -5,6 +5,8 @@ require "amq/protocol/type_constants"
5
5
  require "amq/protocol/table"
6
6
  require "date"
7
7
 
8
+ require "amq/protocol/float_32bit"
9
+
8
10
  module AMQ
9
11
  module Protocol
10
12
 
@@ -36,6 +38,9 @@ module AMQ
36
38
  when Integer then
37
39
  accumulator << TYPE_INTEGER
38
40
  accumulator << [value].pack(PACK_UINT32)
41
+ when AMQ::Protocol::Float32Bit then
42
+ accumulator << TYPE_32BIT_FLOAT
43
+ accumulator << [value.value].pack(PACK_32BIT_FLOAT)
39
44
  when Float then
40
45
  accumulator << TYPE_64BIT_FLOAT
41
46
  accumulator << [value].pack(PACK_64BIT_FLOAT)
@@ -1,5 +1,5 @@
1
1
  module AMQ
2
2
  module Protocol
3
- VERSION = "1.5.0"
3
+ VERSION = "1.6.0"
4
4
  end # Protocol
5
5
  end # AMQ
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib"))
5
+
6
+ require "amq/protocol/client"
7
+
8
+ FRAME_SIZE = 128 * 1024
9
+
10
+ puts
11
+ puts "-" * 80
12
+ puts "Profiling on #{RUBY_DESCRIPTION}"
13
+
14
+ n = 250_000
15
+
16
+ # warm up the JIT, etc
17
+ puts "Doing a warmup run..."
18
+ 15_000.times { AMQ::Protocol::Method.encode_body("ab" * 1024, 1, FRAME_SIZE) }
19
+
20
+ require 'ruby-prof'
21
+
22
+ # preallocate
23
+ ary = Array.new(n) { "ab" * 1024 }
24
+
25
+ puts "Doing main run..."
26
+ result = RubyProf.profile do
27
+ n.times { |i| AMQ::Protocol::Method.encode_body(ary[i], 1, FRAME_SIZE) }
28
+ end
29
+
30
+ printer = RubyProf::FlatPrinter.new(result)
31
+ printer.print(STDOUT, {})
32
+
@@ -60,7 +60,13 @@ module AMQ
60
60
  value.should == input2
61
61
  end
62
62
 
63
+ it "is capable of decoding 32 bit float values" do
64
+ input = Float32Bit.new(10.0)
65
+ data = TableValueEncoder.encode(input)
63
66
 
67
+ value, offset = described_class.decode_32bit_float(data, 1)
68
+ value.should == 10.0
69
+ end
64
70
  end
65
71
  end
66
72
  end
@@ -3,6 +3,7 @@ require File.expand_path('../../../spec_helper', __FILE__)
3
3
 
4
4
  require 'time'
5
5
  require "amq/protocol/table_value_encoder"
6
+ require "amq/protocol/float_32bit"
6
7
 
7
8
  module AMQ
8
9
  module Protocol
@@ -33,6 +34,11 @@ module AMQ
33
34
  described_class.encode(120000.0).bytesize.should == 9
34
35
  end
35
36
 
37
+ it "calculates size of float field values (boxed as 32-bit)" do
38
+ described_class.encode(AMQ::Protocol::Float32Bit.new(10.0)).bytesize.should == 5
39
+ described_class.encode(AMQ::Protocol::Float32Bit.new(120000.0)).bytesize.should == 5
40
+ end
41
+
36
42
  it "calculates size of boolean field values" do
37
43
  described_class.field_value_size(true).should == 2
38
44
  described_class.encode(true).bytesize.should == 2
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amq-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Jakub Stastny
@@ -11,15 +12,16 @@ authors:
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2013-05-06 00:00:00.000000000 Z
15
+ date: 2013-05-14 00:00:00.000000000 Z
15
16
  dependencies: []
16
- description: |2
17
- amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not an
18
- AMQP client: amq-protocol only handles serialization and deserialization.
19
- If you want to write your own AMQP client, this gem can help you with that.
17
+ description: ! " amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It
18
+ is not an\n AMQP client: amq-protocol only handles serialization and deserialization.\n
19
+ \ If you want to write your own AMQP client, this gem can help you with that.\n"
20
20
  email:
21
- - michael@novemberain.com
22
- - stastny@101ideas.cz
21
+ - !binary |-
22
+ bWljaGFlbEBub3ZlbWJlcmFpbi5jb20=
23
+ - !binary |-
24
+ c3Rhc3RueUAxMDFpZGVhcy5jeg==
23
25
  executables: []
24
26
  extensions: []
25
27
  extra_rdoc_files:
@@ -32,9 +34,11 @@ files:
32
34
  - ChangeLog.md
33
35
  - Gemfile
34
36
  - LICENSE
35
- - PROFILING.md
36
37
  - README.md
38
+ - Rakefile
37
39
  - amq-protocol.gemspec
40
+ - benchmarks/pure/body_framing_with_256k_payload.rb
41
+ - benchmarks/pure/body_framing_with_2k_payload.rb
38
42
  - codegen/__init__.py
39
43
  - codegen/amqp_0.9.1_changes.json
40
44
  - codegen/codegen.py
@@ -43,10 +47,13 @@ files:
43
47
  - generate.rb
44
48
  - lib/amq/bit_set.rb
45
49
  - lib/amq/endianness.rb
46
- - lib/amq/hacks.rb
47
50
  - lib/amq/int_allocator.rb
51
+ - lib/amq/pack.rb
48
52
  - lib/amq/protocol.rb
49
53
  - lib/amq/protocol/client.rb
54
+ - lib/amq/protocol/constants.rb
55
+ - lib/amq/protocol/exceptions.rb
56
+ - lib/amq/protocol/float_32bit.rb
50
57
  - lib/amq/protocol/frame.rb
51
58
  - lib/amq/protocol/table.rb
52
59
  - lib/amq/protocol/table_value_decoder.rb
@@ -54,6 +61,7 @@ files:
54
61
  - lib/amq/protocol/type_constants.rb
55
62
  - lib/amq/protocol/version.rb
56
63
  - lib/amq/settings.rb
64
+ - profiling/pure/body_framing_with_2k_payload.rb
57
65
  - spec/amq/bit_set_spec.rb
58
66
  - spec/amq/hacks_spec.rb
59
67
  - spec/amq/int_allocator_spec.rb
@@ -77,26 +85,27 @@ files:
77
85
  homepage: http://github.com/ruby-amqp/amq-protocol
78
86
  licenses:
79
87
  - MIT
80
- metadata: {}
81
88
  post_install_message:
82
89
  rdoc_options: []
83
90
  require_paths:
84
91
  - lib
85
92
  required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
86
94
  requirements:
87
- - - '>='
95
+ - - ! '>='
88
96
  - !ruby/object:Gem::Version
89
97
  version: '0'
90
98
  required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
91
100
  requirements:
92
- - - '>='
101
+ - - ! '>='
93
102
  - !ruby/object:Gem::Version
94
103
  version: '0'
95
104
  requirements: []
96
105
  rubyforge_project: amq-protocol
97
- rubygems_version: 2.0.3
106
+ rubygems_version: 1.8.25
98
107
  signing_key:
99
- specification_version: 4
108
+ specification_version: 3
100
109
  summary: AMQP 0.9.1 encoder & decoder.
101
110
  test_files: []
102
111
  has_rdoc:
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 4fa26c5e78c043c383954d7b7f67ae8d0bbeb09a
4
- data.tar.gz: ce7b5322fe94fc597c4160ac63544fc8e9e66b32
5
- SHA512:
6
- metadata.gz: e456da4a6eb6407df3cfff9fe36f8d7f61706734d309992c416c8500dad0683c6d82ff00e8bf6a9f99436364955e3bc6526380eef7248247bb30e6582d6072de
7
- data.tar.gz: 9913720373247017750e250836cf7c3f01ee66570105e4082de150de7534515ed1d03de02f44a9e7344eeed5e3059d6f48b14818c6fc1d2486e1dc2459da56f7
data/PROFILING.md DELETED
@@ -1,81 +0,0 @@
1
- # Profiling
2
-
3
- To profile the encoding and decoding code use Aman Gupta's perftools.rb (https://github.com/tmm1/perftools.rb). It's really easy to use once you've set it up, and it generates graphs that make it pretty obvious where the hotspots are.
4
-
5
- ## Setup
6
-
7
- Start by installing perftools.rb:
8
-
9
- gem install perftools.rb
10
-
11
- I have it installed in my global gemset, which is handy since you can profile projects that uses any other gemset without having to add perftools.rb to its Gemfile:
12
-
13
- rvm gemset use global
14
- gem install perftools.rb
15
-
16
- If you look at the README for perftools.rb the way to use it seems really icky, having to set environment variables and all that. I have the following script saved as `~/bin/profile` to encapsulate all that:
17
-
18
- #!/bin/bash
19
-
20
- profile_id=$(date +'%Y%m%d%H%M%S')
21
- CPUPROFILE=/tmp/$profile_id RUBYOPT="-r`gem which perftools | tail -1`" $@
22
- pprof.rb --pdf /tmp/$profile_id > /tmp/$profile_id.pdf
23
- open /tmp/$profile_id.pdf
24
-
25
- With this script you can prefix any command with "profile" and perftools.rb will be enabed, and a nice graph will be opened in Preview as soon as the program exits. This only works on OS X, obviously, but I'm sure it can be made to work on Linux too.
26
-
27
- ## How to profile the code
28
-
29
- Most of the time you want to profile a specific method or piece of code. The easiest thing to do is write a short script that runs that code over and over again. This will make sure that the profile graph isn't swamped by noise from other parts of the code, and running lots of iterations means that the numbers will be more stable. Perhaps most importantly it will also show the impact of the GC.
30
-
31
- 100000.times do
32
- strs.each do |str|
33
- AMQ::Client::Framing::String::Frame.decode(str)
34
- end
35
- end
36
-
37
- In the code above the `strs` variable holds a list of strings I've stolen from the amqp-benchmarks repository. I call the `decode` method of the `AMQ::Client::Framing::String::Frame` class over and over again and after 100K iterations (this takes around ten seconds to run) I get a graph that shows me which parts of the code take most time.
38
-
39
- This is how you run the script above (assuming you fill in the `strs` list and add the necessary require's -- and that you've followed the instructions at the top of this document -- and that you've named it "benchmark_script.rb"):
40
-
41
- profile ruby benchmark_script.rb
42
-
43
- It will run for a few seconds and then open a graph. Next I'll explain how to interpret that graph.
44
-
45
- ## Interpreting the profile graph
46
-
47
- The graph generated by perftools.rb shows all the methods that have run, which methods they call, how much time was spent in each method, and a lot of other very interesting things.
48
-
49
- At the top left of the picture it says mow many samples were taken. perftools.rb samples 100 times per second, so 1257 samples means that it ran for 12.57 seconds. The number of samples per second can be tweaked, but I've never had any reason to. The next three numbers just tell you that there are methods that were called not very often, and that these are not shown in the graph, to make it easier to read.
50
-
51
- There are usually two, and sometimes three, nodes which don't have any arrows pointing into them. The first is the entry point into your application, the second the GC, and sometimes there is a third which usually is RubyGems or Bundler. There is no reason there can't be more top level nodes, but those are the ones I've seen.
52
-
53
- Each node represents a method. The label is the class and method name, in classic Ruby style: ClassName#instance_method_name. Unfortunately it doesn't handle class methods very well, so all end up ass Class#method_name. This is bad for us, since most methods in amq-protocol are class methods. It also means that all class methods of the same name, regardless of which class then belong are represented as one node. That's really bad for us, since a lot of methods that we want to profile are called "decode" and "encode".
54
-
55
- The numbers below the label are the self time and cumulative time measurements, first as samples and then, in parentheses as percentages of the whole. Self time means the time spent in the method, excluding other methods, and cumulative time means the total time spent in the method, and all methods below it in the call stack. In other words, if method a calls b and c, the cumulative time for a is its self time, plus the cumulative time of b and c (which is the self time of each of those, plus the cumulative time of the methods they call, and so on).
56
-
57
- The number by the arrow from one node to another is the number of samples that saw the source node call the destination node. Looking at the numbers by the arrows into a node you can see roughly which method calls the method represented by that node the most.
58
-
59
- That's more or less it. I find the percentages most useful to work with, as the number of samples can vary a bit from time to time, its basically the same problem as using `time` to benchmark. Percentages are often much more stable.
60
-
61
- In the next section I'll explain how to act on the information that the graph gives.
62
-
63
- ## Optimizing
64
-
65
- The first thing to look at is the GC node. It will always be high (this is Ruby after all), but if it's above 20% this is what you should start with. The reason the GC takes so much time is because the code creates too many objects. It's not always obvious what is creating so many objects, but read the next section for how to remove the worst offenders. When you've picked the low hanging fruit and still have a high GC percentage, go over your code and try to see if there are places where you can memoize, cache, etc. If your objects are immutable you should never have to create two identical objects (on the other hand, it's easy to leak memory if you cache too much).
66
-
67
- The next thing to look at is the node with the highest self time percentage. This is your first hotspot. Apply all the tricks in the next section and see if it makes any difference. Sometimes hotspots are hotspots because of design rather than slow code. If a method is called many, many times you may not be able to optimize it further (but try, each tiny thing will make a huge difference).
68
-
69
- The third thing to look for are methods will high cumulative time. If the methods they call don't look too bad and can be optimized first, you may be able to rewrite the method to call them fewer times by caching values, for example.
70
-
71
- While optimizing run the profiler for each change to make sure you're going in the right direction. It's not uncommon for something that you think should be faster to be slower because of something you couldn't foresee or some unexpected side effect. When the method you're working on is no longer the worst offender, move on to the next, and iterate.
72
-
73
- ## Tips
74
-
75
- These are some general tips I've gathered while optimizing Ruby code:
76
-
77
- * Avoid string literals. In contrast to Java, strings in Ruby are mutable, so each literal string is a separate object (otherwise 'a'.upcase! would have very strange side effects). Move string literals to constants.
78
- * Avoid range literals. Just as with strings, ranges are objects and they are created every time they are encountered. Ranges are most often used to extract parts of a string, like `str[1..5]`. This can always be rewritten as a slice instead: `str[1, 4]` (the second argument is a length, not an index, so make sure you get the right value). Code like `data[offset..(offset + 4)]` is especially wasteful since it would be much clearer with the slice syntax: `data[offset, 5]`.
79
- * Avoid regular expressions. They are convenient, but for code that will be called thousands of times you need to rewrite them using String#index and String#slice/[]. Make sure that your rewrite doesn't create a lot of temporary string objects.
80
- * When reading files don't read line by line, and absolutely never byte by byte. It's much faster to read a big chunk (as in megabytes) and use String#split to split into lines. The same goes for writing. Buffer the lines and write them in one big chunk. Avoid IO#tell and #seek unless you're trying to read a few bytes out of a gigabyte file.
81
- * Never ever, ever, use Date or DateTime, or require 'date' (to get Time.parse, for example). Date is so slow that it's not even funny. If you use Date.parse you can be sure that whatever else you do that method will account for at least half of the running time. If you really, really need date parsing and date perform date calculations, use the home_run gem, but for almost everything you can use the basic methods on Time (but those aren't cheap either).