ffi-rzmq 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +92 -0
- data/README.rdoc +162 -0
- data/Rakefile +19 -0
- data/examples/local_lat.rb +43 -0
- data/examples/local_lat_zerocopy.rb +24 -0
- data/examples/publish_subscribe.rb +52 -0
- data/examples/remote_lat.rb +53 -0
- data/examples/remote_lat_zerocopy.rb +35 -0
- data/examples/reqrep_poll.rb +49 -0
- data/examples/request_response.rb +23 -0
- data/ffi-rzmq.gemspec +43 -0
- data/lib/ffi-rzmq.rb +71 -0
- data/lib/ffi-rzmq/context.rb +99 -0
- data/lib/ffi-rzmq/exceptions.rb +145 -0
- data/lib/ffi-rzmq/message.rb +210 -0
- data/lib/ffi-rzmq/poll.rb +186 -0
- data/lib/ffi-rzmq/poll_items.rb +98 -0
- data/lib/ffi-rzmq/socket.rb +344 -0
- data/lib/ffi-rzmq/wrapper.rb +120 -0
- data/lib/ffi-rzmq/zmq.rb +147 -0
- data/spec/context_spec.rb +96 -0
- data/spec/reqrep_spec.rb +56 -0
- data/spec/socket_spec.rb +111 -0
- data/spec/spec_helper.rb +38 -0
- data/version.txt +1 -0
- metadata +113 -0
data/History.txt
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
== 0.5.0 / 20100606
|
2
|
+
* Updated the bindings to conform to the 0mq 2.0.7 release.
|
3
|
+
Several parts of the API changed.
|
4
|
+
|
5
|
+
* Updated all examples to use the new Context api.
|
6
|
+
|
7
|
+
* Added Socket#getsockopt.
|
8
|
+
|
9
|
+
* Added a Socket#identity and Socket#identity= method pair to
|
10
|
+
allow for easy get/put on socket identities. Useful for async
|
11
|
+
request/reply using XREQ/XREP sockets.
|
12
|
+
|
13
|
+
* Added more specs (slowly but surely).
|
14
|
+
|
15
|
+
* Support multi-part messages (new as of 2.0.7). I am unsure how
|
16
|
+
to best support multi-part messages so the Message (and related)
|
17
|
+
API may change in the future. Added Socket#more_parts?.
|
18
|
+
|
19
|
+
* Lots of fixes. Many classes use finalizers to deallocate native
|
20
|
+
memory when they go out of scope; be sure to use JRuby 1.5.1 or
|
21
|
+
later to get important finalizer fixes.
|
22
|
+
|
23
|
+
== 0.4.1 / 20100511
|
24
|
+
* I was misusing all of the FFI memory allocator classes. I now
|
25
|
+
wrap libc and use malloc/free directly for creating buffers
|
26
|
+
used by libzmq.
|
27
|
+
|
28
|
+
== 0.4.0 / 20100510
|
29
|
+
* Changed the Socket#recv method signature to take an optional
|
30
|
+
message object as its first argument. This allows the library
|
31
|
+
user to allocate and pass in their own message object for the
|
32
|
+
purposes of zero-copy. Original behavior was for the library to
|
33
|
+
*always* allocate a new message object to receive a message into.
|
34
|
+
Hopefully this is the last change required.
|
35
|
+
|
36
|
+
* Modified the Socket constructor to take an optional hash as its
|
37
|
+
final argument. It honors two keys; :receiver_klass and
|
38
|
+
:sender_klass. Passing in a new constant for either (or both) keys
|
39
|
+
will override the class used by Socket for allocating new
|
40
|
+
Message objects.
|
41
|
+
|
42
|
+
== 0.3.1 / 20100509
|
43
|
+
* Modified ZMQ::Message so we have both an UnmanagedMessage where
|
44
|
+
memory management is manual via the #close method, and Message where
|
45
|
+
memory management is automated via a finalizer method run during
|
46
|
+
garbage collection.
|
47
|
+
|
48
|
+
* Updated ZMQ::Message docs to make it clearer how to use a subclass
|
49
|
+
and FFI::Struct to lazily access the message buffer. This gets us as
|
50
|
+
close to zero-copy as possible for performance.
|
51
|
+
|
52
|
+
* Fixed a memory leak in Message where the FFI::Struct backing the
|
53
|
+
C struct was not being freed.
|
54
|
+
|
55
|
+
* Tested the FFI code against MRI 1.8.x and 1.9.x. It works!
|
56
|
+
|
57
|
+
* Patched a potential problem in LibZMQ::MessageDeallocator. It was
|
58
|
+
crashing under MRI because it complained that FFI::Pointer did not
|
59
|
+
have a free method. It now checks for :free before calling it.
|
60
|
+
Need to investigate this further because it never happened under
|
61
|
+
JRuby.
|
62
|
+
|
63
|
+
* Modified the Socket constructor slightly to allow for using
|
64
|
+
unmanaged or managed messages.
|
65
|
+
|
66
|
+
* Changed the /examples to print a throughput (msgs/s) number upon
|
67
|
+
completion.
|
68
|
+
|
69
|
+
== 0.3.0 / 20100507
|
70
|
+
* ZMQ::Socket#send and ZMQ::Socket#recv semantics changed
|
71
|
+
* The official 0mq ruby bindings utilize strings for #send and #recv.
|
72
|
+
However, to do so requires lots of copying to and from buffers which
|
73
|
+
greatly impacts performance. These methods now return a ZMQ::Message
|
74
|
+
object which can be subclassed to do lazy evaluation of the buffer.
|
75
|
+
|
76
|
+
* Added ZMQ::Socket#send_string and ZMQ::Socket#recv_string. They
|
77
|
+
automatically convert the messages to strings just like the official
|
78
|
+
0mq ruby bindings.
|
79
|
+
|
80
|
+
* Fixed bug in ZMQ::Util#error_string
|
81
|
+
|
82
|
+
* Split the ZMQ::Message class into two classes. The base class called
|
83
|
+
UnmanagedMessage requires manual memory management. The Message
|
84
|
+
class (used by default by Socket) has a finalizer defined to
|
85
|
+
automatically release memory when the message object gets garbage
|
86
|
+
collected.
|
87
|
+
|
88
|
+
|
89
|
+
== 0.2.0 / 20100505
|
90
|
+
|
91
|
+
* 1 major enhancement
|
92
|
+
* Birthday!
|
data/README.rdoc
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
ffi-rzmq
|
2
|
+
by Chuck Remes
|
3
|
+
http://www.zeromq.org/bindings:ruby-ffi
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
|
8
|
+
function interface). It's a pure ruby wrapper so this gem can be loaded
|
9
|
+
and run by any ruby runtime that supports FFI. Right now that means
|
10
|
+
MRI 1.8.7, 1.9.x and JRuby.
|
11
|
+
|
12
|
+
The impetus behind this library was to provide support for ZeroMQ in
|
13
|
+
JRuby which has native threads. Unlike MRI, MacRuby, IronRuby and
|
14
|
+
Rubinius which all have a GIL, JRuby allows for threaded access to ruby
|
15
|
+
code from outside extensions. ZeroMQ is heavily threaded, so until the
|
16
|
+
other runtimes remove their GIL, JRuby will likely be the best
|
17
|
+
environment to run this library.
|
18
|
+
|
19
|
+
== PERFORMANCE
|
20
|
+
|
21
|
+
Using FFI introduces some minimal overhead. In my latest benchmarks,
|
22
|
+
I was unable to detect any measurable performance drop due to FFI
|
23
|
+
regardless of which ruby runtime was tested. JRuby had the best overall
|
24
|
+
performance (with --server) once it warmed up. MRI behaved quite well
|
25
|
+
too and has a much lower memory footprint than JRuby (use the trunk
|
26
|
+
version of the FFI bindings to fix several threading issues affecting
|
27
|
+
MRI).
|
28
|
+
|
29
|
+
Due to odd interactions between 0mq threads and the GIL (Giant Interpreter
|
30
|
+
Lock), I recommend using JRuby 1.5.1 or later. JRuby has no GIL.
|
31
|
+
|
32
|
+
The hope is that in a multi-threaded environment that JRuby's native
|
33
|
+
threads and lack of GIL will provide the best ZeroMQ performance using
|
34
|
+
the ruby language.
|
35
|
+
|
36
|
+
Unfortunately, there is really no reasonable way to support zero-copy
|
37
|
+
using Ruby. Any time data needs to be accessible by the Ruby runtime,
|
38
|
+
it must be copied out of native memory to the Ruby heap. The same is
|
39
|
+
true for the reverse. I am investigating ways to "pin" primitive arrays
|
40
|
+
in memory for Rubinius and JRuby to achieve zero-copy, but that is
|
41
|
+
a ways off.
|
42
|
+
|
43
|
+
== FEATURES/PROBLEMS:
|
44
|
+
|
45
|
+
This gem is brand new and has minimal tests. I'm certain there are a
|
46
|
+
ton of bugs, so please open issues for them here or fork this project,
|
47
|
+
fix them, and send me a pull request.
|
48
|
+
|
49
|
+
All features are implemented with the exception of the 0mq devices
|
50
|
+
(forwarder, queue, streamer).
|
51
|
+
|
52
|
+
== SYNOPSIS:
|
53
|
+
|
54
|
+
Client code:
|
55
|
+
|
56
|
+
require 'rubygems'
|
57
|
+
require 'ffi-rzmq'
|
58
|
+
|
59
|
+
if ARGV.length < 3
|
60
|
+
puts "usage: local_lat <connect-to> <message-size> <roundtrip-count>"
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
bind_to = ARGV[0]
|
65
|
+
message_size = ARGV[1].to_i
|
66
|
+
roundtrip_count = ARGV[2].to_i
|
67
|
+
|
68
|
+
ctx = ZMQ::Context.new 1
|
69
|
+
s = ctx.socket ZMQ::REP
|
70
|
+
s.setsockopt(ZMQ::HWM, 100)
|
71
|
+
s.bind(bind_to)
|
72
|
+
|
73
|
+
roundtrip_count.times do
|
74
|
+
msg = s.recv_string 0
|
75
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
76
|
+
s.send_string msg, 0
|
77
|
+
end
|
78
|
+
|
79
|
+
# give the lib time to flush any remaining messages
|
80
|
+
sleep 1
|
81
|
+
|
82
|
+
Server code:
|
83
|
+
|
84
|
+
require 'rubygems'
|
85
|
+
require 'ffi-rzmq'
|
86
|
+
|
87
|
+
if ARGV.length < 3
|
88
|
+
puts "usage: remote_lat <connect-to> <message-size> <roundtrip-count>"
|
89
|
+
exit
|
90
|
+
end
|
91
|
+
|
92
|
+
connect_to = ARGV[0]
|
93
|
+
message_size = ARGV[1].to_i
|
94
|
+
roundtrip_count = ARGV[2].to_i
|
95
|
+
|
96
|
+
ctx = ZMQ::Context.new 1
|
97
|
+
s = ctx.socket ZMQ::REQ
|
98
|
+
s.connect(connect_to)
|
99
|
+
|
100
|
+
msg = "#{ '3' * message_size }"
|
101
|
+
|
102
|
+
start_time = Time.now
|
103
|
+
|
104
|
+
roundtrip_count.times do
|
105
|
+
s.send_string msg, 0
|
106
|
+
msg = s.recv_string 0
|
107
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
108
|
+
end
|
109
|
+
|
110
|
+
== REQUIREMENTS:
|
111
|
+
|
112
|
+
* 0mq 2.0.7
|
113
|
+
|
114
|
+
The ZeroMQ library must be installed on your system in a well-known location
|
115
|
+
like /usr/local/lib. This is the default for new ZeroMQ installs.
|
116
|
+
|
117
|
+
Future releases may include the library as a C extension built at
|
118
|
+
time of installation.
|
119
|
+
|
120
|
+
* ffi (> 0.6.3)
|
121
|
+
|
122
|
+
Install the current master version of FFI that fixes several threading problems
|
123
|
+
under MRI 1.9.x. Unfortunately, MRI 1.8.x is irretrievably broken so it isn't
|
124
|
+
recommended if you plan to use threads or callbacks.
|
125
|
+
|
126
|
+
Code and installation instructions can be found on github:
|
127
|
+
|
128
|
+
http://github.com/ffi/ffi
|
129
|
+
|
130
|
+
== INSTALL:
|
131
|
+
|
132
|
+
Make sure the ZeroMQ library is already installed on your system. Secondly,
|
133
|
+
make sure the FFI gem is built from its project master (see Requirements).
|
134
|
+
|
135
|
+
% gem build ffi-rzmq.gemspec
|
136
|
+
% gem install ffi-rzmq-*.gem
|
137
|
+
|
138
|
+
|
139
|
+
== LICENSE:
|
140
|
+
|
141
|
+
(The MIT License)
|
142
|
+
|
143
|
+
Copyright (c) 2010 Chuck Remes
|
144
|
+
|
145
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
146
|
+
a copy of this software and associated documentation files (the
|
147
|
+
'Software'), to deal in the Software without restriction, including
|
148
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
149
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
150
|
+
permit persons to whom the Software is furnished to do so, subject to
|
151
|
+
the following conditions:
|
152
|
+
|
153
|
+
The above copyright notice and this permission notice shall be
|
154
|
+
included in all copies or substantial portions of the Software.
|
155
|
+
|
156
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
157
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
158
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
159
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
160
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
161
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
162
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones'
|
4
|
+
rescue LoadError
|
5
|
+
abort '### Please install the "bones" gem ###'
|
6
|
+
end
|
7
|
+
|
8
|
+
task :default => 'test:run'
|
9
|
+
task 'gem:release' => 'test:run'
|
10
|
+
|
11
|
+
Bones {
|
12
|
+
name 'ffi-rzmq'
|
13
|
+
authors 'Chuck Remes'
|
14
|
+
email 'cremes@mac.com'
|
15
|
+
url 'http://github.com/chuckremes/ffi-rzmq'
|
16
|
+
readme_file 'README.rdoc'
|
17
|
+
ruby_opts.clear # turn off warnings
|
18
|
+
}
|
19
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2007-2010 iMatix Corporation
|
3
|
+
#
|
4
|
+
# This file is part of 0MQ.
|
5
|
+
#
|
6
|
+
# 0MQ is free software; you can redistribute it and/or modify it under
|
7
|
+
# the terms of the Lesser GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# 0MQ is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# Lesser GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the Lesser GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'ffi-rzmq'
|
21
|
+
|
22
|
+
if ARGV.length < 3
|
23
|
+
puts "usage: local_lat <connect-to> <message-size> <roundtrip-count>"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
bind_to = ARGV[0]
|
28
|
+
message_size = ARGV[1].to_i
|
29
|
+
roundtrip_count = ARGV[2].to_i
|
30
|
+
|
31
|
+
ctx = ZMQ::Context.new 1
|
32
|
+
s = ctx.socket ZMQ::REP
|
33
|
+
s.setsockopt(ZMQ::HWM, 100)
|
34
|
+
s.bind(bind_to)
|
35
|
+
|
36
|
+
roundtrip_count.times do
|
37
|
+
msg = s.recv_string 0
|
38
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
39
|
+
s.send_string msg, 0
|
40
|
+
end
|
41
|
+
|
42
|
+
# give the lib time to flush any remaining messages
|
43
|
+
sleep 1
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
if ARGV.length < 3
|
5
|
+
puts "usage: local_lat <connect-to> <message-size> <roundtrip-count>"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
bind_to = ARGV[0]
|
10
|
+
message_size = ARGV[1].to_i
|
11
|
+
roundtrip_count = ARGV[2].to_i
|
12
|
+
|
13
|
+
ctx = ZMQ::Context.new 1
|
14
|
+
s = ZMQ::Socket.new ctx.context, ZMQ::REP
|
15
|
+
s.setsockopt ZMQ::HWM, 100
|
16
|
+
s.bind bind_to
|
17
|
+
|
18
|
+
msg = ZMQ::Message.new
|
19
|
+
|
20
|
+
roundtrip_count.times do
|
21
|
+
result = s.recv msg, 0
|
22
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
23
|
+
s.send msg, 0
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi-rzmq'
|
3
|
+
|
4
|
+
|
5
|
+
link = "tcp://127.0.0.1:5555"
|
6
|
+
|
7
|
+
ctx = ZMQ::Context.new 1
|
8
|
+
s1 = ctx.socket ZMQ::PUB
|
9
|
+
s2 = ctx.socket ZMQ::SUB
|
10
|
+
s3 = ctx.socket ZMQ::SUB
|
11
|
+
s4 = ctx.socket ZMQ::SUB
|
12
|
+
s5 = ctx.socket ZMQ::SUB
|
13
|
+
|
14
|
+
s2.setsockopt ZMQ::SUBSCRIBE, '' # receive all
|
15
|
+
s3.setsockopt ZMQ::SUBSCRIBE, 'animals' # receive any starting with this string
|
16
|
+
s4.setsockopt ZMQ::SUBSCRIBE, 'animals.dog'
|
17
|
+
s5.setsockopt ZMQ::SUBSCRIBE, 'animals.cat'
|
18
|
+
|
19
|
+
s1.bind link
|
20
|
+
s2.connect link
|
21
|
+
s3.connect link
|
22
|
+
s4.connect link
|
23
|
+
s5.connect link
|
24
|
+
|
25
|
+
sleep 1
|
26
|
+
|
27
|
+
topic = "animals.dog"
|
28
|
+
payload = "Animal crackers!"
|
29
|
+
|
30
|
+
s1.identity = "publisher-A"
|
31
|
+
puts "sending"
|
32
|
+
# use the new multi-part messaging support to
|
33
|
+
# automatically separate the topic from the body
|
34
|
+
s1.send_string topic, ZMQ::SNDMORE
|
35
|
+
s1.send_string payload, ZMQ::SNDMORE
|
36
|
+
s1.send_string s1.identity
|
37
|
+
|
38
|
+
topic = s2.recv_string
|
39
|
+
body = s2.recv_string if s2.more_parts?
|
40
|
+
identity = s2.recv_string if s2.more_parts?
|
41
|
+
puts "s2 received topic [#{topic}], body [#{body}], identity [#{identity}]"
|
42
|
+
|
43
|
+
topic = s3.recv_string
|
44
|
+
body = s3.recv_string if s3.more_parts?
|
45
|
+
puts "s3 received topic [#{topic}], body [#{body}]"
|
46
|
+
|
47
|
+
topic = s4.recv_string
|
48
|
+
body = s4.recv_string if s4.more_parts?
|
49
|
+
puts "s4 received topic [#{topic}], body [#{body}]"
|
50
|
+
|
51
|
+
s5_string = s5.recv_string ZMQ::NOBLOCK
|
52
|
+
puts(s5_string.nil? ? "s5 received no messages" : "s5 FAILED")
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2007-2010 iMatix Corporation
|
3
|
+
#
|
4
|
+
# This file is part of 0MQ.
|
5
|
+
#
|
6
|
+
# 0MQ is free software; you can redistribute it and/or modify it under
|
7
|
+
# the terms of the Lesser GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# 0MQ is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# Lesser GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the Lesser GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'ffi-rzmq'
|
21
|
+
|
22
|
+
if ARGV.length < 3
|
23
|
+
puts "usage: remote_lat <connect-to> <message-size> <roundtrip-count>"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
connect_to = ARGV[0]
|
28
|
+
message_size = ARGV[1].to_i
|
29
|
+
roundtrip_count = ARGV[2].to_i
|
30
|
+
|
31
|
+
ctx = ZMQ::Context.new 1
|
32
|
+
s = ctx.socket ZMQ::REQ
|
33
|
+
s.connect(connect_to)
|
34
|
+
|
35
|
+
msg = "#{ '3' * message_size }"
|
36
|
+
|
37
|
+
start_time = Time.now
|
38
|
+
|
39
|
+
roundtrip_count.times do
|
40
|
+
s.send_string msg, 0
|
41
|
+
msg = s.recv_string 0
|
42
|
+
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
|
43
|
+
end
|
44
|
+
|
45
|
+
end_time = Time.now
|
46
|
+
elapsed_secs = (end_time.to_f - start_time.to_f)
|
47
|
+
elapsed_usecs = elapsed_secs * 1000000
|
48
|
+
latency = elapsed_usecs / roundtrip_count / 2
|
49
|
+
|
50
|
+
puts "message size: %i [B]" % message_size
|
51
|
+
puts "roundtrip count: %i" % roundtrip_count
|
52
|
+
puts "throughput (msgs/s): %i" % (roundtrip_count / elapsed_secs)
|
53
|
+
puts "mean latency: %.3f [us]" % latency
|