cztop 0.1.1 → 0.2.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
  SHA1:
3
- metadata.gz: a0a723ca2a3e3272f97e72dbf4b90ce750d56240
4
- data.tar.gz: 579445f67959d4cd7f8db0166adff18881d01273
3
+ metadata.gz: 38df565064ee6d04ad3eeaaf668e68574cb8e0cc
4
+ data.tar.gz: 62eb8513064a9546d4c04c57411314ffb8bdc162
5
5
  SHA512:
6
- metadata.gz: 0431bec1dd537a65ba20467226cb430a3affb229319da0aaefaf2d114e72a613a3f72fc579e43c8449ec0ccb3e473e7ef9e65b8b8ddb1e77d501b63fc9d4be8f
7
- data.tar.gz: 0ae5624063f633046af71f27123b789bf1d547f713f6c87a2050c752c49f722ad30b03867ea754bf46a1943df7b7d05ec2e0cf18155c84730fcb624ad875ebbb
6
+ metadata.gz: 028dbf962ccf069d64048d12107fcc322e66a5553c34e2c8081e15319e602eac13c88ca903a0290bb9fbd6dda22c398ea8e4e16d99fa01f0fab6cf049110df13
7
+ data.tar.gz: 7f6f6cdf10066b9a8ae4c26f5b3d0025da04a1a6aeb0caec8bc6ab550a16955057162543ac83e0ec0fd2535923637c5beafb6acf1a6247228492f97531862dc4
data/CHANGES.md CHANGED
@@ -1,3 +1,18 @@
1
+ 0.2.0 (xx/01/2016)
2
+ -----
3
+ * simplify CZTop::Z85::Padded
4
+ * no length encoding
5
+ * padding similar to PKCS#7
6
+ * add utilities `z85encode` and `z85decode`
7
+ * add CZTop::Z85::Pipe
8
+ * CZTop::SUB#subscribe: subscribe to everything if no parameter given
9
+
10
+ 0.1.1 (23/01/2016)
11
+ -----
12
+ * add support for Ruby 2.0
13
+ * improve documentation
14
+ * fix require()s in examples
15
+
1
16
  0.1.0 (23/01/2016)
2
17
  -----
3
18
  * first release
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Inline docs](http://inch-ci.org/github/paddor/cztop.svg?branch=master&style=shields)](http://inch-ci.org/github/paddor/cztop)
4
4
  [![Dependency Status](https://gemnasium.com/paddor/cztop.svg)](https://gemnasium.com/paddor/cztop)
5
5
  [![Coverage Status](https://coveralls.io/repos/paddor/cztop/badge.svg?branch=master&service=github)](https://coveralls.io/github/paddor/cztop?branch=master)
6
+ [![ISC License](https://img.shields.io/badge/license-ISC_License-blue.svg)](LICENSE)
6
7
 
7
8
  # CZTop
8
9
 
@@ -389,6 +390,10 @@ $
389
390
  * cannot handle CLIENT/SERVER sockets
390
391
  * there good timer gems for Ruby
391
392
  * Poller can be used to embed in an existing event loop (Celluloid), or make your own trivial one
393
+ * [x] provide `z85encode` and `z85decode` utilities
394
+ * can be used in a pipeline (limited memory usage)
395
+ * reusable interface: `Z85::Pipe`
396
+
392
397
 
393
398
  ## Contributing
394
399
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'cztop'
3
+ require 'optparse'
4
+ require 'benchmark'
5
+
6
+ options = { strategy: CZTop::Z85::Pipe::Strategy::Sequential }
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: #$0 [options]"
9
+
10
+ opts.on("-p", "--parallel", "read, decode, and write in parallel " +
11
+ "using 3 threads") do
12
+ options[:strategy] = CZTop::Z85::Pipe::Strategy::Parallel
13
+ end
14
+ opts.on("-v", "--verbose", "print some statistics afterwards") do
15
+ options[:verbose] = true
16
+ end
17
+ end.parse!
18
+
19
+ pipe = CZTop::Z85::Pipe.new(STDIN, STDOUT, strategy: options[:strategy])
20
+ bytes_decoded = nil
21
+ tms = Benchmark.measure { bytes_decoded = pipe.decode }
22
+ exit unless options[:verbose]
23
+
24
+ if tms.real < 0.1
25
+ warn "#{$0}: decoding took %.2f us." % (tms.real * 1_000_000)
26
+ else
27
+ warn "#{$0}: decoding took %.3f seconds." % tms.real
28
+ end
29
+
30
+ throughput = (bytes_decoded * 8 / tms.real) / 1_000_000
31
+ warn "#{$0}: mean throughput: %.3f [Mb/s]" % throughput
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'cztop'
3
+ require 'optparse'
4
+ require 'benchmark'
5
+
6
+ options = { strategy: CZTop::Z85::Pipe::Strategy::Sequential }
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: #$0 [options]"
9
+
10
+ opts.on("-p", "--parallel", "read, encode, and write in parallel " +
11
+ "using 3 threads") do
12
+ options[:strategy] = CZTop::Z85::Pipe::Strategy::Parallel
13
+ end
14
+ opts.on("-v", "--verbose", "print some statistics afterwards") do
15
+ options[:verbose] = true
16
+ end
17
+ end.parse!
18
+
19
+ pipe = CZTop::Z85::Pipe.new(STDIN, STDOUT, strategy: options[:strategy])
20
+ bytes_encoded = nil
21
+ tms = Benchmark.measure { bytes_encoded = pipe.encode }
22
+ exit unless options[:verbose]
23
+
24
+ if tms.real < 0.1
25
+ warn "#{$0}: encoding took %.2f us." % (tms.real * 1_000_000)
26
+ else
27
+ warn "#{$0}: encoding took %.3f seconds." % tms.real
28
+ end
29
+
30
+ throughput = (bytes_encoded * 8 / tms.real) / 1_000_000
31
+ warn "#{$0}: mean throughput: %.3f [Mb/s]" % throughput
@@ -31,6 +31,8 @@ require_relative 'cztop/config/traversing'
31
31
  require_relative 'cztop/config/serialization'
32
32
  require_relative 'cztop/message/frames'
33
33
  require_relative 'cztop/socket/types'
34
+ require_relative 'cztop/z85/padded'
35
+ require_relative 'cztop/z85/pipe'
34
36
 
35
37
 
36
38
  # make Ctrl-C work in case a low-level call hangs
@@ -30,57 +30,7 @@ module CZTop
30
30
  # ugly.
31
31
  #
32
32
  class Poller
33
- # CZTop's interface to the low-level +zmq_poll()+ function.
34
- module ZMQ
35
-
36
- POLL = 1
37
- POLLIN = 1
38
- POLLOUT = 2
39
- POLLERR = 4
40
-
41
- extend ::FFI::Library
42
- lib_name = 'libzmq'
43
- lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
44
- .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
45
- ffi_lib lib_paths + [lib_name]
46
-
47
- # Represents a struct of type +zmq_pollitem_t+.
48
- class PollItem < FFI::Struct
49
- ##
50
- # shamelessly taken from https://github.com/mtortonesi/ruby-czmq-ffi
51
- #
52
-
53
-
54
- FD_TYPE = if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE == 64
55
- # On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
56
- :uint64
57
- else
58
- :int
59
- end
60
-
61
- layout :socket, :pointer,
62
- :fd, FD_TYPE,
63
- :events, :short,
64
- :revents, :short
65
-
66
- # @return [Boolean] whether the socket is readable
67
- def readable?
68
- (self[:revents] & POLLIN) > 0
69
- end
70
-
71
- # @return [Boolean] whether the socket is writable
72
- def writable?
73
- (self[:revents] & POLLOUT) > 0
74
- end
75
- end
76
-
77
- opts = {
78
- blocking: true # only necessary on MRI to deal with the GIL.
79
- }
80
-
81
- #ZMQ_EXPORT int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
82
- attach_function :poll, :zmq_poll, [:pointer, :int, :long], :int, **opts
83
- end
33
+ include ::CZMQ::FFI
84
34
 
85
35
  # @param readers [Socket, Actor] sockets to poll for input
86
36
  def initialize(*readers)
@@ -108,7 +58,7 @@ module CZTop
108
58
  # @raise [ArgumentError] if it's not a socket
109
59
  def add_reader(socket)
110
60
  raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
111
- ptr = CZMQ::FFI::Zsock.resolve(socket) # get low-level handle
61
+ ptr = Zsock.resolve(socket) # get low-level handle
112
62
  @readers[ptr.to_i] = socket
113
63
  @rebuild_needed = true
114
64
  end
@@ -120,7 +70,7 @@ module CZTop
120
70
  # @raise [ArgumentError] if it's not a socket
121
71
  def remove_reader(socket)
122
72
  raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
123
- ptr = CZMQ::FFI::Zsock.resolve(socket) # get low-level handle
73
+ ptr = Zsock.resolve(socket) # get low-level handle
124
74
  @readers.delete(ptr.to_i) and @rebuild_needed = true
125
75
  end
126
76
 
@@ -130,7 +80,7 @@ module CZTop
130
80
  # @raise [ArgumentError] if it's not a socket
131
81
  def add_writer(socket)
132
82
  raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
133
- ptr = CZMQ::FFI::Zsock.resolve(socket) # get low-level handle
83
+ ptr = Zsock.resolve(socket) # get low-level handle
134
84
  @writers[ptr.to_i] = socket
135
85
  @rebuild_needed = true
136
86
  end
@@ -142,7 +92,7 @@ module CZTop
142
92
  # @raise [ArgumentError] if it's not a socket
143
93
  def remove_writer(socket)
144
94
  raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
145
- ptr = CZMQ::FFI::Zsock.resolve(socket) # get low-level handle
95
+ ptr = Zsock.resolve(socket) # get low-level handle
146
96
  @writers.delete(ptr.to_i) and @rebuild_needed = true
147
97
  end
148
98
 
@@ -212,13 +162,65 @@ module CZTop
212
162
  # @return [ZMQ::PollItem] a new item for
213
163
  def new_item(address, socket, events)
214
164
  item = ZMQ::PollItem.new(address)
215
- item[:socket] = CZMQ::FFI::Zsock.resolve(socket)
165
+ item[:socket] = Zsock.resolve(socket)
216
166
  item[:fd] = 0
217
167
  item[:events] = events
218
168
  item[:revents] = 0
219
169
  item
220
170
  end
221
171
 
172
+ # CZTop's interface to the low-level +zmq_poll()+ function.
173
+ module ZMQ
174
+
175
+ POLL = 1
176
+ POLLIN = 1
177
+ POLLOUT = 2
178
+ POLLERR = 4
179
+
180
+ extend ::FFI::Library
181
+ lib_name = 'libzmq'
182
+ lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
183
+ .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
184
+ ffi_lib lib_paths + [lib_name]
185
+
186
+ # Represents a struct of type +zmq_pollitem_t+.
187
+ class PollItem < FFI::Struct
188
+ ##
189
+ # shamelessly taken from https://github.com/mtortonesi/ruby-czmq-ffi
190
+ #
191
+
192
+
193
+ FD_TYPE = if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE == 64
194
+ # On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
195
+ :uint64
196
+ else
197
+ :int
198
+ end
199
+
200
+ layout :socket, :pointer,
201
+ :fd, FD_TYPE,
202
+ :events, :short,
203
+ :revents, :short
204
+
205
+ # @return [Boolean] whether the socket is readable
206
+ def readable?
207
+ (self[:revents] & POLLIN) > 0
208
+ end
209
+
210
+ # @return [Boolean] whether the socket is writable
211
+ def writable?
212
+ (self[:revents] & POLLOUT) > 0
213
+ end
214
+ end
215
+
216
+ opts = {
217
+ blocking: true # only necessary on MRI to deal with the GIL.
218
+ }
219
+
220
+ #ZMQ_EXPORT int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
221
+ attach_function :poll, :zmq_poll, [:pointer, :int, :long], :int, **opts
222
+ end
223
+
222
224
  # This is the trivial poller based on zpoller. It only supports polling
223
225
  # for reading, but it also supports doing that on CLIENT/SERVER sockets,
224
226
  # which is useful for {CZTop::Poller}.
@@ -134,10 +134,13 @@ module CZTop
134
134
  attach_ffi_delegate(Zsock.new_sub(endpoints, subscription))
135
135
  end
136
136
 
137
+ # @return [String] subscription prefix to subscribe to everything
138
+ EVERYTHING = ""
139
+
137
140
  # Subscribes to the given prefix string.
138
141
  # @param prefix [String] prefix string to subscribe to
139
142
  # @return [void]
140
- def subscribe(prefix)
143
+ def subscribe(prefix = EVERYTHING)
141
144
  ffi_delegate.set_subscribe(prefix)
142
145
  end
143
146
 
@@ -1,3 +1,3 @@
1
1
  module CZTop
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -7,6 +7,40 @@ module CZTop
7
7
  include HasFFIDelegate
8
8
  extend CZTop::HasFFIDelegate::ClassMethods
9
9
 
10
+ class << self
11
+ # Same as {Z85#encode}, but without the need to create an instance
12
+ # first.
13
+ #
14
+ # @param input [String] possibly binary input data
15
+ # @return [String] Z85 encoded data as ASCII string
16
+ # @raise [ArgumentError] if input length isn't divisible by 4 with no
17
+ # remainder
18
+ # @raise [SystemCallError] if this fails
19
+ def encode(input)
20
+ default.encode(input)
21
+ end
22
+
23
+ # Same as {Z85#decode}, but without the need to create an instance
24
+ # first.
25
+ #
26
+ # @param input [String] Z85 encoded data
27
+ # @return [String] original data as binary string
28
+ # @raise [ArgumentError] if input length isn't divisible by 5 with no
29
+ # remainder
30
+ # @raise [SystemCallError] if this fails
31
+ def decode(input)
32
+ default.decode(input)
33
+ end
34
+
35
+ private
36
+
37
+ # Default instance of {Z85}.
38
+ # @return [Z85] memoized default instance
39
+ def default
40
+ @default ||= Z85.new
41
+ end
42
+ end
43
+
10
44
  def initialize
11
45
  attach_ffi_delegate(CZMQ::FFI::Zarmour.new)
12
46
  ffi_delegate.set_mode(:mode_z85)
@@ -63,95 +97,5 @@ module CZTop
63
97
  size_ptr.read_uint32
64
98
  end
65
99
  end
66
-
67
- # Z85 with simple padding. This allows you to {#encode} input of any
68
- # length.
69
- #
70
- # = Encoding Procedure
71
- #
72
- # If the data to be encoded is empty (0 bytes), it is encoded to the empty
73
- # string, just like in Z85.
74
- #
75
- # Otherwise, a length information is prepended and, if needed, padding (1,
76
- # 2, or 3 NULL bytes) is appended to bring the resulting blob to
77
- # a multiple of 4 bytes.
78
- #
79
- # The length information is encoded similarly to lengths of messages
80
- # (frames) in ZMTP. Up to 127 bytes, the data's length is encoded with
81
- # a single byte (specifically, with the 7 least significant bits in it).
82
- #
83
- # +--------+-------------------------------+------------+
84
- # | length | data | padding |
85
- # | 1 byte | up to 127 bytes | 0-3 bytes |
86
- # +--------+-------------------------------+------------+
87
- #
88
- # If the data is 128 bytes or more, the most significant bit will be set
89
- # to indicate that fact, and a 64 bit unsigned integer in network byte
90
- # order is appended after this first byte to encode the length of the
91
- # data. This means that up to 16EiB (exbibytes) can be encoded, which
92
- # will be enough for the foreseeable future.
93
- #
94
- # +--------+-----------+----------------------------------+------------+
95
- # | big? | length | data | padding |
96
- # | 1 byte | 8 bytes | 128 bytes or much more | 0-3 bytes |
97
- # +--------+-----------+----------------------------------+------------+
98
- #
99
- # The resulting blob is encoded using {CZTop::Z85#encode}.
100
- # {CZTop::Z85#decode} does the inverse.
101
- #
102
- # @note Warning: This won't be compatible with other implementations of
103
- # Z85. Only use this if you really need padding, like when you can't
104
- # guarantee the input for {#encode} is always a multiple of 4 bytes.
105
- #
106
- class Padded < Z85
107
- # Encododes to Z85, with padding if needed.
108
- #
109
- # If input isn't empty, 8 additional bytes for the encoded length will
110
- # be prepended. If needed, 1 to 3 bytes of padding will be appended.
111
- #
112
- # If input is empty, returns the empty string.
113
- #
114
- # @param input [String] possibly binary input data
115
- # @return [String] Z85 encoded data as ASCII string, including encoded
116
- # length and padding
117
- # @raise [SystemCallError] if this fails
118
- def encode(input)
119
- return super if input.empty?
120
- length = input.bytesize
121
- if length < 1<<7 # up to 127 bytes
122
- encoded_length = [length].pack("C")
123
-
124
- else # larger input
125
- low = length & 0xFFFFFFFF
126
- high = (length >> 32) & 0xFFFFFFFF
127
- encoded_length = [ 1<<7, high, low ].pack("CNN")
128
- end
129
- padding = "\0" * ((4 - ((length+1) % 4)) % 4)
130
- super("#{encoded_length}#{input}#{padding}")
131
- end
132
-
133
- # Decodes from Z85 with padding.
134
- #
135
- # @param input [String] Z85 encoded data (including encoded length and
136
- # padding, or empty string)
137
- # @return [String] original data as binary string
138
- # @raise [ArgumentError] if input is invalid or truncated
139
- # @raise [SystemCallError] if this fails
140
- def decode(input)
141
- return super if input.empty?
142
- decoded = super
143
- length = decoded.byteslice(0, 1).unpack("C")[0]
144
- if (1<<7 & length).zero? # up to 127 bytes
145
- decoded = decoded.byteslice(1, length) # extract payload
146
-
147
- else # larger input
148
- length = decoded.byteslice(1, 8).unpack("NN")
149
- .inject(0) { |sum, i| (sum << 32) + i }
150
- decoded = decoded.byteslice(9, length) # extract payload
151
- end
152
- raise ArgumentError, "input truncated" if decoded.bytesize < length
153
- return decoded
154
- end
155
- end
156
100
  end
157
101
  end
@@ -0,0 +1,98 @@
1
+ # Z85 with simple padding. This allows you to {#encode} input of any
2
+ # length.
3
+ #
4
+ # = Padding Scheme
5
+ #
6
+ # If the data to be encoded is empty (0 bytes), it is encoded to the empty
7
+ # string, just like in Z85.
8
+ #
9
+ # Otherwise, a small padding sequence of 1 to 4 (identical) bytes is
10
+ # appended to the binary data. Its last byte denotes the number of padding
11
+ # bytes. This padding is done even if the length of the binary input is
12
+ # a multiple of 4 bytes. This is similar to PKCS#7 padding.
13
+ #
14
+ # +----------------------------------------+------------+
15
+ # | binary data | padding |
16
+ # | any number of bytes | 1-4 bytes |
17
+ # +----------------------------------------+------------+
18
+ #
19
+ # The resulting blob is encoded using {CZTop::Z85#encode}.
20
+ #
21
+ # When decoding, {CZTop::Z85#decode} does the inverse. After decoding, it
22
+ # checks the last byte to determine the number of bytes of padding used,
23
+ # and chops those off.
24
+ #
25
+ # @note Warning: Z85 doesn't have a standardized padding procedure. So
26
+ # other implementations won't automatically recognize and chop off the
27
+ # padding. Only use this if you really need padding, like when you can't
28
+ # guarantee the input for {#encode} is always a multiple of 4 bytes.
29
+ #
30
+ # @see https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
31
+ class CZTop::Z85::Padded < CZTop::Z85
32
+ class << self
33
+ # Same as {Z85::Padded#encode}, but without the need to create an
34
+ # instance first.
35
+ #
36
+ # @param input [String] possibly binary input data
37
+ # @return [String] Z85 encoded data as ASCII string, including encoded
38
+ # length and padding
39
+ # @raise [SystemCallError] if this fails
40
+ def encode(input)
41
+ default.encode(input)
42
+ end
43
+
44
+ # Same as {Z85::Padded#decode}, but without the need to create an
45
+ # instance first.
46
+ #
47
+ # @param input [String] Z85 encoded data (including padding, or empty
48
+ # string)
49
+ # @return [String] original data as binary string
50
+ # @raise [SystemCallError] if this fails
51
+ def decode(input)
52
+ default.decode(input)
53
+ end
54
+
55
+ private
56
+
57
+ # Default instance of {Z85::Padded}.
58
+ # @return [Z85::Padded] memoized default instance
59
+ def default
60
+ @default ||= CZTop::Z85::Padded.new
61
+ end
62
+ end
63
+
64
+ # Encododes to Z85, with padding if needed.
65
+ #
66
+ # @param input [String] possibly binary input data
67
+ # @return [String] Z85 encoded data as ASCII string, including padding
68
+ # @raise [SystemCallError] if this fails
69
+ def encode(input)
70
+ return super if input.empty?
71
+
72
+ padding_bytes = 4 - (input.bytesize % 4)
73
+
74
+ # if 0, make it 4. we MUST append padding.
75
+ padding_bytes = 4 if padding_bytes == 0
76
+
77
+ # generate and append padding
78
+ padding = [padding_bytes].pack("C") * padding_bytes
79
+
80
+ super("#{input}#{padding}")
81
+ end
82
+
83
+ # Decodes from Z85 with padding.
84
+ #
85
+ # @param input [String] Z85 encoded data (including padding, or empty
86
+ # string)
87
+ # @return [String] original data as binary string
88
+ # @raise [SystemCallError] if this fails
89
+ def decode(input)
90
+ return super if input.empty?
91
+ decoded = super
92
+
93
+ # last byte contains number of padding bytes
94
+ padding_bytes = decoded.byteslice(-1).ord
95
+
96
+ decoded.byteslice( 0 ... -padding_bytes )
97
+ end
98
+ end
@@ -0,0 +1,178 @@
1
+ # Can be used if you want to encode or decode data from one IO to another.
2
+ # It'll do so until it hits EOF in the source IO.
3
+ class CZTop::Z85::Pipe
4
+ # @param source [IO] where to read data
5
+ # @param sink [IO] where to write data
6
+ # @param strategy [Strategy] algorithm to use (pass the class itself,
7
+ # not an instance)
8
+ def initialize(source, sink, strategy: Strategy::Parallel)
9
+ @source, @sink = source, sink
10
+ @strategy = strategy
11
+ @bin_bytes = 0 # processed binary data (non-Z85)
12
+ end
13
+
14
+ # @return [Integer] size of chunks read when encoding
15
+ ENCODE_READ_SZ = (32 * 2**10) # 32 KiB (for full chunks read)
16
+
17
+ # @return [Integer] size of chunks read when decoding
18
+ DECODE_READ_SZ = ENCODE_READ_SZ / 4 * 5
19
+
20
+ # Encodes data from source and writes Z85 data to sink. This is done
21
+ # until EOF is hit on the source.
22
+ #
23
+ # @return [Integer] number of bytes read (binary data)
24
+ def encode
25
+ padded = false
26
+ @strategy.new(@source, @sink, ENCODE_READ_SZ) do |chunk, prev_chunk|
27
+ @bin_bytes += chunk.bytesize if chunk
28
+ if prev_chunk && chunk
29
+ CZTop::Z85.encode(prev_chunk)
30
+ elsif prev_chunk && chunk.nil? # last chunk
31
+ CZTop::Z85::Padded.encode(prev_chunk)
32
+ elsif prev_chunk.nil? && chunk.nil?
33
+ CZTop::Z85.encode("") # empty input
34
+ else
35
+ "" # very first chunk. don't encode anything yet...
36
+ end
37
+ end.execute
38
+ return @bin_bytes
39
+ end
40
+
41
+ # Decodes Z85 data from source and writes decoded data to sink. This is
42
+ # done until EOF is hit on the source.
43
+ #
44
+ # @return [Integer] number of bytes written (binary data)
45
+ def decode
46
+ @strategy.new(@source, @sink, DECODE_READ_SZ) do |chunk, prev_chunk|
47
+ if prev_chunk && chunk
48
+ CZTop::Z85.decode(prev_chunk)
49
+ elsif prev_chunk && chunk.nil?
50
+ CZTop::Z85::Padded.decode(prev_chunk)
51
+ elsif prev_chunk.nil? && chunk.nil?
52
+ CZTop::Z85.decode("") # empty input
53
+ else
54
+ "" # very first chunk. don't decode anything yet...
55
+ end
56
+ end.execute
57
+ return @bin_bytes
58
+ end
59
+
60
+ # @abstract
61
+ # Different encoding/decoding strategies (algorithms).
62
+ #
63
+ # This is mainly just for me to practice the GoF Strategy Pattern.
64
+ class Strategy
65
+ # @param source [IO] the source
66
+ # @param sink [IO] the sink
67
+ # @param read_sz [Integer] chunk size when reading from source
68
+ # @param xcode [Proc] block to encode or decode data
69
+ # @yieldparam chunk [String, nil] current chunk (or +nil+ after the
70
+ # last one)
71
+ # @yieldparam prev_chunk [String, nil] previous chunk (or +nil+ for
72
+ # the first time)
73
+ # @yieldreturn [String] encoded/decoded chunk to write to sink
74
+ def initialize(source, sink, read_sz, &xcode)
75
+ @source = source
76
+ @sink = sink
77
+ @read_sz = read_sz
78
+ @xcode = xcode
79
+ end
80
+
81
+ # @abstract
82
+ # Runs the algorithm.
83
+ # @raise [void]
84
+ def execute() raise NotImplementedError end
85
+
86
+ # A single thread that is either reading input, encoding/decoding, or
87
+ # writing output.
88
+ class Sequential < Strategy
89
+ # Runs the algorithm.
90
+ # @raise [void]
91
+ def execute
92
+ previous_chunk = nil
93
+ while true
94
+ chunk = @source.read(@read_sz)
95
+ @sink << @xcode.(chunk, previous_chunk)
96
+ break if chunk.nil?
97
+ previous_chunk = chunk
98
+ end
99
+ end
100
+ end
101
+
102
+ # Uses three threads:
103
+ #
104
+ # 1. reads from source
105
+ # 2. encodes/decodes
106
+ # 3. thread that writes to sink
107
+ #
108
+ # This might give a performance increase on truly parallel
109
+ # platforms such as Rubinius and JRuby (and multiple CPU cores).
110
+ #
111
+ class Parallel < Strategy
112
+ # Initializes the 2 sized queues used.
113
+ def initialize(*)
114
+ super
115
+ # @source
116
+ # |
117
+ # V
118
+ @source_queue = SizedQueue.new(20) # limit memory usage
119
+ # |
120
+ # V
121
+ # xcode
122
+ # |
123
+ # V
124
+ @sink_queue = SizedQueue.new(20) # limit memory usage
125
+ # |
126
+ # V
127
+ # @sink
128
+ end
129
+
130
+ # Runs the algorithm.
131
+ # @raise [void]
132
+ def execute
133
+ Thread.new { read }
134
+ Thread.new { xcode }
135
+ write
136
+ end
137
+
138
+ private
139
+
140
+ # Reads all chunks and pushes them into the source queue. Then
141
+ # pushes a +nil+ into the queue.
142
+ # @return [void]
143
+ def read
144
+ while chunk = @source.read(@read_sz)
145
+ @source_queue << chunk
146
+ end
147
+ @source_queue << nil
148
+ end
149
+
150
+ # Pops all chunks from the source queue, encodes or decodes them,
151
+ # and pushes the result into the sink queue. Then pushes a +nil+
152
+ # into the queue.
153
+ # @return [void]
154
+ def xcode
155
+ # Encode all but the last chunk with pure Z85.
156
+ previous_chunk = nil
157
+ while true
158
+ chunk = @source_queue.pop
159
+
160
+ # call @xcode for the trailing nil-chunk as well
161
+ @sink_queue << @xcode.(chunk, previous_chunk)
162
+
163
+ break if chunk.nil?
164
+ previous_chunk = chunk
165
+ end
166
+ @sink_queue << nil
167
+ end
168
+
169
+ # Pops all chunks from the sink queue and writes them to the sink.
170
+ # @return [void]
171
+ def write
172
+ while chunk = @sink_queue.pop
173
+ @sink << chunk
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cztop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-01-23 00:00:00.000000000 Z
11
+ date: 2016-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: czmq-ffi-gen
@@ -209,7 +209,9 @@ dependencies:
209
209
  description:
210
210
  email:
211
211
  - paddor@gmail.com
212
- executables: []
212
+ executables:
213
+ - z85decode
214
+ - z85encode
213
215
  extensions: []
214
216
  extra_rdoc_files: []
215
217
  files:
@@ -241,6 +243,8 @@ files:
241
243
  - examples/taxi_system/generate_keys.rb
242
244
  - examples/taxi_system/start_broker.sh
243
245
  - examples/taxi_system/start_clients.sh
246
+ - exe/z85decode
247
+ - exe/z85encode
244
248
  - lib/cztop.rb
245
249
  - lib/cztop/actor.rb
246
250
  - lib/cztop/authenticator.rb
@@ -263,6 +267,8 @@ files:
263
267
  - lib/cztop/socket/types.rb
264
268
  - lib/cztop/version.rb
265
269
  - lib/cztop/z85.rb
270
+ - lib/cztop/z85/padded.rb
271
+ - lib/cztop/z85/pipe.rb
266
272
  - lib/cztop/zsock_options.rb
267
273
  - perf/README.md
268
274
  - perf/inproc_lat.rb