ttcp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.jrubyrc +1 -0
- data/Gemfile +10 -0
- data/Guardfile +9 -0
- data/LICENSE +8 -0
- data/README.md +69 -0
- data/Rakefile +8 -0
- data/bin/ttcp +138 -0
- data/lib/ttcp.rb +321 -0
- data/lib/ttcp/version.rb +3 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/ttcp_options_spec.rb +42 -0
- data/spec/ttcp_spec.rb +219 -0
- data/ttcp.gemspec +27 -0
- metadata +108 -0
data/.gitignore
ADDED
data/.jrubyrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
compat.version=1.9
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :rspec, :cli => '--color --format doc' do
|
5
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
|
+
watch(%r{^spec/.+_spec\.rb$})
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
9
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2012 Matthew Connolly
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# ttcp (Test TCP) - Benchmarking Tool and Simple Network Traffic Generator
|
2
|
+
|
3
|
+
This is a ruby version of the original C ttcp program.
|
4
|
+
|
5
|
+
This tool aims to be a drop in replacement for the original C version, with interoperable networking and command line options. ie: you can have the C and ruby versions talk to each other.
|
6
|
+
|
7
|
+
http://www.pcausa.com/Utilities/pcattcp.htm
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Run `$ gem install ttcp`.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Similar to the original TTCP program, run a receiver on one machine and a transmitter on another, in that order:
|
16
|
+
```machine1$ ttcp -r [-p <port>]
|
17
|
+
```
|
18
|
+
|
19
|
+
And:
|
20
|
+
```machine2$ ttcp -t <machine1_ip_address> [-p port]
|
21
|
+
```
|
22
|
+
|
23
|
+
For more command line options run:
|
24
|
+
```machine1$ ttcp --help
|
25
|
+
```
|
26
|
+
|
27
|
+
## Dependencies
|
28
|
+
|
29
|
+
Runtime dependencies:
|
30
|
+
|
31
|
+
* only ruby std libraries
|
32
|
+
|
33
|
+
Development dependencies:
|
34
|
+
|
35
|
+
* bundler
|
36
|
+
* rake
|
37
|
+
* rpsec
|
38
|
+
* ci_reporter (for use with jenkins)
|
39
|
+
|
40
|
+
Optionally:
|
41
|
+
|
42
|
+
* guard
|
43
|
+
* guard-rspec
|
44
|
+
|
45
|
+
## Compatibility
|
46
|
+
|
47
|
+
* MRI ruby 1.8.7
|
48
|
+
* MRI ruby 1.9.2
|
49
|
+
* JRuby (tested in 1.9 compatibility mode)
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
1. Fork
|
54
|
+
2. Install dependencies by running `$ bundle install`
|
55
|
+
3. Write tests and code
|
56
|
+
4. Make sure the tests pass by running `$ rake test`
|
57
|
+
5. Push and send a pull request on GitHub
|
58
|
+
|
59
|
+
## Known issues
|
60
|
+
|
61
|
+
* Tests don't seem to run in JRuby 1.6.5, but the TTCP program works itself.
|
62
|
+
|
63
|
+
## Credits
|
64
|
+
|
65
|
+
Special thanks to the Mike Muuss and Terry Slattery and other contributors of the original TTCP program.
|
66
|
+
|
67
|
+
## Copyright
|
68
|
+
|
69
|
+
Copyright © 2012 Matt Connolly. Released under the MIT license. See LICENSE.
|
data/Rakefile
ADDED
data/bin/ttcp
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# ttcp
|
4
|
+
# ©2012 Matt Connolly
|
5
|
+
#
|
6
|
+
# This is a ruby implementation of the TTCP network test program.
|
7
|
+
#
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
require 'optparse'
|
12
|
+
require File.expand_path('../lib/ttcp', File.dirname(__FILE__))
|
13
|
+
|
14
|
+
=begin
|
15
|
+
Usage: ttcp -t [-options] host [ < in ]\n\
|
16
|
+
ttcp -r [-options > out]\n\
|
17
|
+
Common options:\n\
|
18
|
+
-l ## length of bufs read from or written to network (default 8192)\n\
|
19
|
+
-u use UDP instead of TCP\n\
|
20
|
+
-p ## port number to send to or listen at (default 5001)\n\
|
21
|
+
-s -t: source a pattern to network\n\
|
22
|
+
-r: sink (discard) all data from network\n\
|
23
|
+
-A align the start of buffers to this modulus (default 16384)\n\
|
24
|
+
-O start buffers at this offset from the modulus (default 0)\n\
|
25
|
+
-v verbose: print more statistics\n\
|
26
|
+
-d set SO_DEBUG socket option\n\
|
27
|
+
-b ## set socket buffer size (if supported)\n\
|
28
|
+
-f X format for rate: k,K = kilo{bit,byte}; m,M = mega; g,G = giga\n\
|
29
|
+
Options specific to -t:\n\
|
30
|
+
-n## number of source bufs written to network (default 2048)\n\
|
31
|
+
-D don't buffer TCP writes (sets TCP_NODELAY socket option)\n\
|
32
|
+
Options specific to -r:\n\
|
33
|
+
-B for -s, only output full blocks as specified by -l (for TAR)\n\
|
34
|
+
-T \"touch\": access each byte as it's read\n\
|
35
|
+
=end
|
36
|
+
|
37
|
+
#
|
38
|
+
# defaults:
|
39
|
+
#
|
40
|
+
|
41
|
+
options = TTCP::TTCP.default_options
|
42
|
+
|
43
|
+
optparse = OptionParser.new do |opts|
|
44
|
+
opts.banner = <<END
|
45
|
+
Usage: ttcp.rb -t [options] host [ < in ]
|
46
|
+
ttcp.rb -r [options > out]
|
47
|
+
END
|
48
|
+
|
49
|
+
opts.separator "Must choose one of:"
|
50
|
+
opts.on('-t', '--transmit', "Transmit data to another TTCP program") do
|
51
|
+
options[:transmit] = true
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('-r', '--receive', "Receive data from another TTCP program") do
|
55
|
+
options[:receive] = true
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.separator ""
|
59
|
+
opts.separator "Other options:"
|
60
|
+
opts.on('-l', '--length NUM', "Length of bufs read from or written to network (default #{options[:length]})") do |length|
|
61
|
+
options[:length] = length.to_i if length.to_i > 0
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on('-u', '--udp', 'Use UDP instead of default TCP') do
|
65
|
+
options[:udp] = true
|
66
|
+
options[:tcp] = false
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
opts.on('-s', '--sink',
|
71
|
+
"When transmitting, source pattern to network.",
|
72
|
+
"When receiving, discard all incoming data") do
|
73
|
+
options[:sink] = !options[:sink]
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on('-p', '--port PORT', "Receive on port PORT / Transmit to remote port PORT") do |port|
|
77
|
+
options[:port] = port.to_i if port.to_i > 0
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on('-f', '--format F', "Format: K = K bytes, M = M bytes, G = G Bytes, k = K bits, m = M bits, g = G bits. Default = K") do |fmt|
|
81
|
+
options[:format] = fmt
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on('-v', '--verbose', "Verbose output") { options[:verbose] = true }
|
85
|
+
opts.on('-T', '--touch', "Touch (access) all incoming data") { options[:touch] = true }
|
86
|
+
|
87
|
+
opts.on('--version', "Output the version of TTCP") do
|
88
|
+
puts TTCP::VERSION
|
89
|
+
exit(0)
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on('-n', '--numbufs NUM', "Set number of buffers to send / receive (default = #{options[:num_buffers]})") do |num|
|
93
|
+
options[:num_buffers] = num.to_i if num.to_i > 0
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
97
|
+
puts opts
|
98
|
+
exit(0)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
begin
|
107
|
+
optparse.parse!
|
108
|
+
|
109
|
+
if options[:transmit]
|
110
|
+
#we require one argument: the host to connect to.
|
111
|
+
if ARGV.length < 1
|
112
|
+
raise "Transmit option requires a command line argument specifying the host to connect and transmit to."
|
113
|
+
end
|
114
|
+
options[:host] = ARGV[0]
|
115
|
+
end
|
116
|
+
|
117
|
+
unless options[:transmit] || options[:receive]
|
118
|
+
raise "You must select either transmit or receive option"
|
119
|
+
end
|
120
|
+
|
121
|
+
if options[:transmit] && options[:receive]
|
122
|
+
raise "You cannot select both transmit and receive options."
|
123
|
+
end
|
124
|
+
|
125
|
+
rescue SystemExit
|
126
|
+
# let it go through, we've been told to exit
|
127
|
+
raise
|
128
|
+
rescue Exception => x
|
129
|
+
$stderr.puts optparse
|
130
|
+
$stderr.puts x
|
131
|
+
exit(1)
|
132
|
+
end
|
133
|
+
|
134
|
+
exit(0) if ENV['DRY_RUN']
|
135
|
+
|
136
|
+
ttcp = TTCP::TTCP.new options
|
137
|
+
ttcp.run
|
138
|
+
|
data/lib/ttcp.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
require File.expand_path("ttcp/version", File.dirname(__FILE__))
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module TTCP
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:transmit => false,
|
8
|
+
:receive => false,
|
9
|
+
:length => 8192,
|
10
|
+
:udp => false,
|
11
|
+
:tcp => true,
|
12
|
+
:port => 5001,
|
13
|
+
:verbose => false,
|
14
|
+
:socket_debug => false,
|
15
|
+
:format_rate => 'm',
|
16
|
+
:num_buffers => 2048,
|
17
|
+
:touch => false,
|
18
|
+
:sink => true,
|
19
|
+
:format => 'K',
|
20
|
+
}
|
21
|
+
|
22
|
+
def default_options
|
23
|
+
DEFAULT_OPTIONS
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class TTCP
|
28
|
+
|
29
|
+
attr_reader :options
|
30
|
+
attr_reader :bytes_received
|
31
|
+
attr_accessor :stdout, :stderr
|
32
|
+
|
33
|
+
def self.default_options
|
34
|
+
DEFAULT_OPTIONS
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Create a TTCP test program instance. All configuration is done via the
|
39
|
+
# options hash passed in here.
|
40
|
+
#
|
41
|
+
def initialize(options = {})
|
42
|
+
@options = TTCP.default_options.merge options
|
43
|
+
|
44
|
+
if @options[:udp]
|
45
|
+
# enforce buffer length to be more than udp sentinel size.
|
46
|
+
@options[:length] = [@options[:length], 5].max
|
47
|
+
end
|
48
|
+
|
49
|
+
@bytes_received = 0
|
50
|
+
|
51
|
+
# by default, use the stdout and stderr.
|
52
|
+
@stdout = $stdout
|
53
|
+
@stderr = $stderr
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Run the TTCP test program according to the options specified with the .new call
|
59
|
+
#
|
60
|
+
def run
|
61
|
+
begin
|
62
|
+
num_bytes = 0
|
63
|
+
num_calls = 0
|
64
|
+
|
65
|
+
if @options[:transmit]
|
66
|
+
message "buflen=%d, nbuf=%d, remote port=%d" % [@options[:length], @options[:num_buffers], @options[:port]]
|
67
|
+
|
68
|
+
buf = source_buffer
|
69
|
+
|
70
|
+
@start_time = Time.now
|
71
|
+
@start_cpu_time = get_cpu_time
|
72
|
+
|
73
|
+
socket.write "ttcp" if @options[:udp]
|
74
|
+
@options[:num_buffers].times do
|
75
|
+
socket.write buf
|
76
|
+
num_bytes += buf.length
|
77
|
+
num_calls += 1
|
78
|
+
end
|
79
|
+
socket.write "ttcp" if @options[:udp]
|
80
|
+
|
81
|
+
elsif @options[:receive]
|
82
|
+
message "buflen=%d, nbuf=%d, local port=%d" % [@options[:length], @options[:num_buffers], @options[:port]]
|
83
|
+
|
84
|
+
receiving = true
|
85
|
+
sentinel_count = 0
|
86
|
+
|
87
|
+
if @options[:tcp]
|
88
|
+
receiving_socket = socket.accept
|
89
|
+
remote_address =
|
90
|
+
if receiving_socket.respond_to? :remote_address
|
91
|
+
"#{receiving_socket.remote_address.ip_address}:#{receiving_socket.remote_address.ip_port}"
|
92
|
+
elsif receiving_socket.respond_to? :peeraddr # this is what JRuby 1.6.5 has instead
|
93
|
+
"#{receiving_socket.peeraddr[2]}:#{receiving_socket.peeraddr[1]}"
|
94
|
+
else
|
95
|
+
"<Unknown>"
|
96
|
+
end
|
97
|
+
message("accept from #{remote_address}")
|
98
|
+
else
|
99
|
+
receiving_socket = socket
|
100
|
+
end
|
101
|
+
|
102
|
+
@start_time = Time.now
|
103
|
+
@start_cpu_time = get_cpu_time
|
104
|
+
|
105
|
+
while receiving
|
106
|
+
|
107
|
+
buf = receiving_socket.recv @options[:length]
|
108
|
+
num_bytes += buf.length unless buf.nil?
|
109
|
+
num_calls += 1
|
110
|
+
|
111
|
+
if buf.nil? || (@options[:tcp] && buf.length == 0)
|
112
|
+
receiving = false
|
113
|
+
else
|
114
|
+
if @options[:udp]
|
115
|
+
if buf.length <= 4
|
116
|
+
sentinel_count += 1
|
117
|
+
if sentinel_count >= 2
|
118
|
+
receiving = false
|
119
|
+
end
|
120
|
+
else
|
121
|
+
sink buf
|
122
|
+
touch buf if @options[:touch]
|
123
|
+
@bytes_received += buf.length
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if @options[:tcp]
|
130
|
+
receiving_socket.close unless receiving_socket.closed?
|
131
|
+
receiving_socket = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
ensure
|
137
|
+
@finish_time = Time.now
|
138
|
+
@finish_cpu_time = get_cpu_time
|
139
|
+
close
|
140
|
+
end
|
141
|
+
|
142
|
+
message("%d bytes in %.3f real seconds = %s/sec +++" % [num_bytes, duration, format_rate(num_bytes, duration)])
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def duration
|
148
|
+
@finish_time - @start_time if @finish_time
|
149
|
+
end
|
150
|
+
|
151
|
+
def cpu_duration
|
152
|
+
@finish_cpu_time - @start_cpu_time if @finish_cpu_time
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# close any sockets
|
157
|
+
#
|
158
|
+
def close
|
159
|
+
unless @socket.nil?
|
160
|
+
begin
|
161
|
+
@socket.shutdown Socket::SHUT_RDWR
|
162
|
+
rescue
|
163
|
+
# ignore any errors closing the socket
|
164
|
+
ensure
|
165
|
+
@socket = nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
#
|
172
|
+
# set the stdout to point to nothing
|
173
|
+
#
|
174
|
+
def stdout_to_null
|
175
|
+
@stdout = File.open("/dev/null", "w")
|
176
|
+
@stderr = File.open("/dev/null", "w")
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# get the socket to be used for this ttcp run
|
182
|
+
def socket
|
183
|
+
|
184
|
+
if @socket.nil?
|
185
|
+
|
186
|
+
state = "socket"
|
187
|
+
|
188
|
+
begin
|
189
|
+
if @options[:transmit]
|
190
|
+
|
191
|
+
raise "Host not specified" unless @options[:host]
|
192
|
+
|
193
|
+
# create a socket to transmit to
|
194
|
+
if @options[:udp]
|
195
|
+
@socket = UDPSocket.new
|
196
|
+
message("socket")
|
197
|
+
state = "connect"
|
198
|
+
@socket.connect(@options[:host], @options[:port])
|
199
|
+
message("connect")
|
200
|
+
else
|
201
|
+
@socket = TCPSocket.new(@options[:host], @options[:port])
|
202
|
+
message("socket")
|
203
|
+
message("connect")
|
204
|
+
end
|
205
|
+
|
206
|
+
elsif @options[:receive]
|
207
|
+
|
208
|
+
# create a socket to receive from
|
209
|
+
if @options[:udp]
|
210
|
+
@socket = UDPSocket.new
|
211
|
+
message("socket")
|
212
|
+
state = "bind"
|
213
|
+
@socket.bind(@options[:host], @options[:port])
|
214
|
+
message("bind")
|
215
|
+
else
|
216
|
+
# create a TCPServer object
|
217
|
+
args = []
|
218
|
+
args << @options[:host] if @options[:host]
|
219
|
+
args << @options[:port]
|
220
|
+
@socket = TCPServer.new *args
|
221
|
+
message("socket")
|
222
|
+
state = "listen"
|
223
|
+
@socket.listen 0
|
224
|
+
message("listen")
|
225
|
+
end
|
226
|
+
|
227
|
+
else
|
228
|
+
raise "TTCP must be configured to transmit or receive"
|
229
|
+
end
|
230
|
+
|
231
|
+
rescue Exception => e
|
232
|
+
error(state, e)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@socket
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
def source_buffer
|
242
|
+
if @options[:sink]
|
243
|
+
# source for sink is random data
|
244
|
+
if RUBY_VERSION >= "1.9"
|
245
|
+
Random.new.bytes @options[:length]
|
246
|
+
else
|
247
|
+
source = ""
|
248
|
+
@options[:length].times { source << rand(255) }
|
249
|
+
source
|
250
|
+
end
|
251
|
+
else
|
252
|
+
# source for buffer is stdin
|
253
|
+
$stdin.read @options[:length]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
#
|
259
|
+
# send the received buffer to the appropriate place:stdout or the sink (null)
|
260
|
+
def sink(buffer)
|
261
|
+
if @options[:sink]
|
262
|
+
#nothing, the sink
|
263
|
+
else
|
264
|
+
puts buffer
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
#
|
270
|
+
# read all the data in the buffer
|
271
|
+
#
|
272
|
+
def touch(buffer)
|
273
|
+
sum = 0
|
274
|
+
buffer.each_byte { |x| sum += x }
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
#
|
279
|
+
# print output, like the `mes()` function in the original C ttcp program.
|
280
|
+
#
|
281
|
+
def message(str)
|
282
|
+
mode = @options[:transmit] ? "t" : "r"
|
283
|
+
@stdout.puts "ttcp-#{mode}: #{str}"
|
284
|
+
end
|
285
|
+
|
286
|
+
#
|
287
|
+
# print output to stderr, like the `err()` function in the original C ttcp program.
|
288
|
+
# instead of exiting, raise an exception and let the caller exit. If no exception is passed,
|
289
|
+
# the error message is raised.
|
290
|
+
def error(str, exception=nil)
|
291
|
+
mode = @options[:transmit] ? "t" : "r"
|
292
|
+
@stderr.puts "ttcp-#{mode}: #{str}"
|
293
|
+
raise exception || str
|
294
|
+
end
|
295
|
+
|
296
|
+
# return combined user and system cpu time used by this process so far.
|
297
|
+
def get_cpu_time
|
298
|
+
tms = Process.times
|
299
|
+
tms.utime + tms.stime
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
UNIT_FACTOR = {
|
304
|
+
:k => [8 * 1024, 'Kbit'],
|
305
|
+
:m => [8 * 1024**2, 'Mbit'],
|
306
|
+
:g => [8 * 1024**3, 'Gbit'],
|
307
|
+
:K => [1024, 'K'],
|
308
|
+
:M => [1024**2, 'M'],
|
309
|
+
:G => [1024**3, 'G']
|
310
|
+
}
|
311
|
+
|
312
|
+
# genrate a string representation of the date, according to the @options[:format] setting
|
313
|
+
def format_rate(bytes, duration)
|
314
|
+
d = [0.001, duration].max
|
315
|
+
rate = bytes / d
|
316
|
+
factor, unit = UNIT_FACTOR[@options[:format].to_sym]
|
317
|
+
rate /= factor
|
318
|
+
"%0.3f %s" % [rate, unit]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
data/lib/ttcp/version.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ttcp'
|
2
|
+
require 'ttcp/version'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
|
6
|
+
class Class
|
7
|
+
def publicize_methods
|
8
|
+
saved_private_instance_methods = self.private_instance_methods
|
9
|
+
self.class_eval { public *saved_private_instance_methods }
|
10
|
+
yield
|
11
|
+
self.class_eval { private *saved_private_instance_methods }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class ThreadSocket
|
17
|
+
|
18
|
+
attr_reader :socket
|
19
|
+
|
20
|
+
def initialize(&block)
|
21
|
+
@thread = Thread.new do
|
22
|
+
begin
|
23
|
+
@socket = nil
|
24
|
+
instance_eval(&block)
|
25
|
+
rescue Exception => x
|
26
|
+
puts "Exception in ThreadSocket: #{x}"
|
27
|
+
ensure
|
28
|
+
begin
|
29
|
+
@socket.shutdown(Socket::SHUT_RDWR) if @socket && @socket.is_a?(BasicSocket)
|
30
|
+
rescue
|
31
|
+
# ignore errors shutting down the socket
|
32
|
+
end
|
33
|
+
@socket = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# forward not implemented method calls to the thread
|
39
|
+
def method_missing(method_name, *args, &block)
|
40
|
+
if @thread && @thread.respond_to?(method_name)
|
41
|
+
@thread.__send__(method_name, *args, &block)
|
42
|
+
else
|
43
|
+
super(method_name, *args, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require 'spec_helper.rb'
|
3
|
+
|
4
|
+
TTCP_EXECUTABLE = File.expand_path('../../bin/ttcp', __FILE__)
|
5
|
+
|
6
|
+
|
7
|
+
def test_ttcp(options = '')
|
8
|
+
`DRY_RUN=YES #{TTCP_EXECUTABLE} #{options} 2>&1`
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Command Line Option Parsing" do
|
12
|
+
|
13
|
+
it "ttcp fails without -t or -r" do
|
14
|
+
test_ttcp
|
15
|
+
$?.should_not == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "ttcp fails with both -t and -r" do
|
19
|
+
test_ttcp '-t -r'
|
20
|
+
$?.should_not == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it "ttcp fails without a host with -t" do
|
24
|
+
test_ttcp '-t'
|
25
|
+
$?.should_not == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
it "ttcp is ok with a host and -t" do
|
29
|
+
test_ttcp '-t HOST'
|
30
|
+
$?.should == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it "ttcp is ok with -r and no host" do
|
34
|
+
test_ttcp '-r'
|
35
|
+
$?.should == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "ttcp prints out the version" do
|
39
|
+
version = test_ttcp '--version'
|
40
|
+
version.chomp.should == TTCP::VERSION
|
41
|
+
end
|
42
|
+
end
|
data/spec/ttcp_spec.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require 'spec_helper'
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
|
6
|
+
describe "TTCP Sockets" do
|
7
|
+
|
8
|
+
after() do
|
9
|
+
# ensure the ttcp instance is closed and destroyed
|
10
|
+
if @ttcp.is_a? TTCP::TTCP
|
11
|
+
@ttcp.close
|
12
|
+
end
|
13
|
+
@ttcp = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
## Setting up ttcp socket
|
18
|
+
##
|
19
|
+
|
20
|
+
specify "ttcp in transmit tcp makes a tcp socket" do
|
21
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :tcp => true, :host => 'www.google.com', :port => 80
|
22
|
+
@ttcp.stdout_to_null
|
23
|
+
TTCP::TTCP.publicize_methods do
|
24
|
+
@ttcp.socket.should be_a(TCPSocket)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "ttcp in transmit udp makes a udp socket" do
|
29
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :tcp => false, :udp => true, :host => 'localhost'
|
30
|
+
@ttcp.stdout_to_null
|
31
|
+
TTCP::TTCP.publicize_methods do
|
32
|
+
@ttcp.socket.should be_a(UDPSocket)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
specify "ttcp in receive tcp makes a tcp socket" do
|
37
|
+
@ttcp = TTCP::TTCP.new :receive => true, :tcp => true, :host => 'localhost'
|
38
|
+
@ttcp.stdout_to_null
|
39
|
+
TTCP::TTCP.publicize_methods do
|
40
|
+
@ttcp.socket.should be_a(TCPSocket)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "ttcp in receive udp makes a udp socket" do
|
45
|
+
@ttcp = TTCP::TTCP.new :receive => true, :tcp => false, :udp => true, :host => 'localhost'
|
46
|
+
@ttcp.stdout_to_null
|
47
|
+
TTCP::TTCP.publicize_methods do
|
48
|
+
@ttcp.socket.should be_a(UDPSocket)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
specify "ttcp in udp has a minimum buffer length greater than 4" do
|
53
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :udp => true, :host => 'localhost', :length => 3
|
54
|
+
@ttcp.stdout_to_null
|
55
|
+
@ttcp.options[:length].should > 4
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "ttcp without options raises exception because neither transmit nor receive is specified" do
|
59
|
+
lambda do
|
60
|
+
@ttcp = TTCP::TTCP.new
|
61
|
+
@ttcp.stdout_to_null
|
62
|
+
@ttcp.socket
|
63
|
+
end.should raise_error
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
describe "TTCP Transmitting" do
|
69
|
+
|
70
|
+
TEST_PORT = 5003
|
71
|
+
|
72
|
+
specify "TTCP transmit over TCP to no server fails" do
|
73
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :tcp => true, :host => 'localhost', :port => TEST_PORT+1
|
74
|
+
@ttcp.stdout_to_null
|
75
|
+
lambda { @ttcp.run }.should raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
specify "TTCP transmits over TCP, closing the connection when it's done" do
|
79
|
+
|
80
|
+
thread = ThreadSocket.new do
|
81
|
+
TCPServer.open TEST_PORT do |server|
|
82
|
+
@socket = server.accept
|
83
|
+
until @socket.eof?
|
84
|
+
@socket.read 1000
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
sleep(0.2)
|
90
|
+
|
91
|
+
thread.alive?.should be_true
|
92
|
+
thread.socket.should be_nil
|
93
|
+
|
94
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :tcp => true, :host => 'localhost', :port => TEST_PORT
|
95
|
+
@ttcp.stdout_to_null
|
96
|
+
lambda { @ttcp.run }.should_not raise_error
|
97
|
+
@ttcp.duration.should_not be_nil
|
98
|
+
@ttcp.duration.should > 0
|
99
|
+
|
100
|
+
thread.join
|
101
|
+
|
102
|
+
# connection has closed in server
|
103
|
+
thread.socket.should be_nil
|
104
|
+
|
105
|
+
# thread should have finished
|
106
|
+
thread.alive?.should be_false
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
specify "TTCP transmit over UDP" do
|
111
|
+
|
112
|
+
thread = ThreadSocket.new do
|
113
|
+
@socket = UDPSocket.new
|
114
|
+
@socket.bind('localhost', TEST_PORT)
|
115
|
+
sentinel_count = 0
|
116
|
+
until sentinel_count >= 2
|
117
|
+
b = @socket.recv 2000
|
118
|
+
sentinel_count += 1 if b.length <= 4
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
sleep(0.2)
|
123
|
+
|
124
|
+
thread.alive?.should be_true
|
125
|
+
thread.socket.should_not be_nil
|
126
|
+
|
127
|
+
@ttcp = TTCP::TTCP.new :transmit => true, :tcp => false, :udp => true, :host => 'localhost', :port => TEST_PORT
|
128
|
+
@ttcp.stdout_to_null
|
129
|
+
lambda { @ttcp.run }.should_not raise_error
|
130
|
+
@ttcp.duration.should_not be_nil
|
131
|
+
@ttcp.duration.should > 0
|
132
|
+
|
133
|
+
thread.join
|
134
|
+
|
135
|
+
# connection has closed in server
|
136
|
+
thread.socket.should be_nil
|
137
|
+
|
138
|
+
# thread should have finished
|
139
|
+
thread.alive?.should be_false
|
140
|
+
end
|
141
|
+
|
142
|
+
specify "TTCP receives over TCP from another TTCP sending to TCP" do
|
143
|
+
|
144
|
+
thread = Thread.new do
|
145
|
+
|
146
|
+
sleep 0.3
|
147
|
+
|
148
|
+
ttcp2 = TTCP::TTCP.new :transmit => true, :tcp => true, :host => 'localhost', :port =>TEST_PORT
|
149
|
+
ttcp2.stdout_to_null
|
150
|
+
ttcp2.run
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
sleep 0.1
|
155
|
+
|
156
|
+
thread.alive?.should be_true
|
157
|
+
|
158
|
+
@ttcp = TTCP::TTCP.new :receive=> true, :tcp => true, :host => 'localhost', :port =>TEST_PORT
|
159
|
+
@ttcp.stdout_to_null
|
160
|
+
@ttcp.run
|
161
|
+
|
162
|
+
@ttcp.duration.should_not be_nil
|
163
|
+
@ttcp.duration.should > 0
|
164
|
+
|
165
|
+
thread.join
|
166
|
+
thread.alive?.should be_false
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
specify "TTCP receives over UDP from another TTCP sending to UDP" do
|
171
|
+
|
172
|
+
thread = Thread.new do
|
173
|
+
|
174
|
+
sleep 0.3
|
175
|
+
|
176
|
+
ttcp2 = TTCP::TTCP.new :transmit => true, :tcp => false, :udp => true, :host => 'localhost', :port =>TEST_PORT
|
177
|
+
ttcp2.stdout_to_null
|
178
|
+
ttcp2.run
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
sleep 0.1
|
183
|
+
|
184
|
+
thread.alive?.should be_true
|
185
|
+
|
186
|
+
@ttcp = TTCP::TTCP.new :receive=> true, :tcp => false, :udp => true, :host => 'localhost', :port =>TEST_PORT
|
187
|
+
@ttcp.stdout_to_null
|
188
|
+
@ttcp.run
|
189
|
+
|
190
|
+
@ttcp.duration.should_not be_nil
|
191
|
+
@ttcp.duration.should > 0
|
192
|
+
|
193
|
+
thread.join
|
194
|
+
thread.alive?.should be_false
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "Output formatting" do
|
201
|
+
|
202
|
+
[
|
203
|
+
[ 'Kilobytes', 'K', 1.5*1024, 2.0, "0.750 K" ],
|
204
|
+
[ 'Kilobits', 'k', 16*1024, 2.0, "1.000 Kbit" ],
|
205
|
+
[ 'Megabytes', 'M', 1.5*1024**2, 2.0, "0.750 M" ],
|
206
|
+
[ 'Gigabytes', 'G', 4.5*1024**3, 2.0, "2.250 G" ],
|
207
|
+
[ 'Megabits', 'm', 16*1024**2, 2.0, "1.000 Mbit" ],
|
208
|
+
[ 'Gigabits', 'g', 16*1024**3, 2.0, "1.000 Gbit" ]
|
209
|
+
].each do |spec|
|
210
|
+
|
211
|
+
it "Calculates #{spec[0]} / sec correctly" do
|
212
|
+
@ttcp = TTCP::TTCP.new :format => spec[1]
|
213
|
+
result = @ttcp.instance_eval { format_rate(spec[2], spec[3]) }
|
214
|
+
result.should == spec[4]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
end
|
data/ttcp.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ttcp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ttcp"
|
7
|
+
s.version = TTCP::VERSION
|
8
|
+
s.authors = ["Matt Connolly"]
|
9
|
+
s.email = ["matt@soundevolution.com.au"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A ruby implementation of the TTCP network test program.}
|
12
|
+
s.description = %q{Based on the C command line tool at http://www.pcausa.com/Utilities/pcattcp.htm}
|
13
|
+
|
14
|
+
s.rubyforge_project = "ttcp-rb"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "bundler"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
s.add_development_dependency "ci_reporter"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ttcp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Connolly
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &70279976951600 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70279976951600
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70279976951160 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70279976951160
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70279976950640 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70279976950640
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ci_reporter
|
49
|
+
requirement: &70279984658600 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70279984658600
|
58
|
+
description: Based on the C command line tool at http://www.pcausa.com/Utilities/pcattcp.htm
|
59
|
+
email:
|
60
|
+
- matt@soundevolution.com.au
|
61
|
+
executables:
|
62
|
+
- ttcp
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- .jrubyrc
|
68
|
+
- Gemfile
|
69
|
+
- Guardfile
|
70
|
+
- LICENSE
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- bin/ttcp
|
74
|
+
- lib/ttcp.rb
|
75
|
+
- lib/ttcp/version.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
- spec/ttcp_options_spec.rb
|
78
|
+
- spec/ttcp_spec.rb
|
79
|
+
- ttcp.gemspec
|
80
|
+
homepage: ''
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project: ttcp-rb
|
100
|
+
rubygems_version: 1.8.10
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: A ruby implementation of the TTCP network test program.
|
104
|
+
test_files:
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/ttcp_options_spec.rb
|
107
|
+
- spec/ttcp_spec.rb
|
108
|
+
has_rdoc:
|