cztop 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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