kronk 1.7.8 → 1.8.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.
- data/History.rdoc +32 -0
- data/Manifest.txt +5 -5
- data/README.rdoc +5 -3
- data/Rakefile +3 -4
- data/TODO.rdoc +22 -6
- data/lib/kronk.rb +13 -7
- data/lib/kronk/buffered_io.rb +26 -0
- data/lib/kronk/cmd.rb +58 -44
- data/lib/kronk/constants.rb +5 -6
- data/lib/kronk/diff.rb +12 -13
- data/lib/kronk/diff/output.rb +1 -1
- data/lib/kronk/http.rb +105 -0
- data/lib/kronk/player.rb +74 -82
- data/lib/kronk/player/benchmark.rb +34 -35
- data/lib/kronk/player/stream.rb +6 -6
- data/lib/kronk/player/suite.rb +17 -16
- data/lib/kronk/player/tsv.rb +51 -0
- data/lib/kronk/queue_runner.rb +126 -102
- data/lib/kronk/request.rb +144 -82
- data/lib/kronk/response.rb +520 -202
- data/lib/kronk/test/helper_methods.rb +1 -1
- data/test/mocks/200_gzip.txt +0 -0
- data/test/mocks/200_inflate.txt +0 -0
- data/test/test_assertions.rb +1 -1
- data/test/test_cmd.rb +18 -11
- data/test/test_diff.rb +39 -0
- data/test/test_helper.rb +18 -14
- data/test/test_helper_methods.rb +5 -4
- data/test/test_kronk.rb +8 -11
- data/test/test_player.rb +67 -123
- data/test/test_request.rb +8 -14
- data/test/test_response.rb +228 -60
- metadata +20 -31
- data/lib/kronk/async.rb +0 -118
- data/lib/kronk/async/em_ext.rb +0 -34
- data/lib/kronk/async/request.rb +0 -73
- data/lib/kronk/async/response.rb +0 -70
- data/lib/kronk/player/output.rb +0 -49
data/lib/kronk/player/stream.rb
CHANGED
@@ -21,9 +21,9 @@ class Kronk
|
|
21
21
|
#
|
22
22
|
# Note: This output class will not render errors.
|
23
23
|
|
24
|
-
class Player::Stream < Player
|
24
|
+
class Player::Stream < Player
|
25
25
|
|
26
|
-
def result kronk
|
26
|
+
def result kronk
|
27
27
|
output =
|
28
28
|
if kronk.diff
|
29
29
|
kronk.diff.formatted
|
@@ -32,12 +32,12 @@ class Kronk
|
|
32
32
|
kronk.response.stringify
|
33
33
|
end
|
34
34
|
|
35
|
-
return unless output
|
35
|
+
return unless output && !output.empty?
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
mutex.synchronize do
|
37
|
+
@mutex.synchronize do
|
38
|
+
$stdout << "#{"%X" % output.length}\r\n"
|
40
39
|
$stdout << output
|
40
|
+
$stdout << "\r\n"
|
41
41
|
end
|
42
42
|
|
43
43
|
output
|
data/lib/kronk/player/suite.rb
CHANGED
@@ -3,21 +3,18 @@ class Kronk
|
|
3
3
|
##
|
4
4
|
# Outputs Player requests and results in a test-suite like format.
|
5
5
|
|
6
|
-
class Player::Suite < Player
|
6
|
+
class Player::Suite < Player
|
7
7
|
|
8
8
|
def start
|
9
|
-
@results
|
10
|
-
|
11
|
-
msg << (Player.async ? " (async)" : " (threaded)")
|
12
|
-
$stdout.puts msg
|
13
|
-
super
|
9
|
+
@results = []
|
10
|
+
$stdout.puts "Started"
|
14
11
|
end
|
15
12
|
|
16
13
|
|
17
|
-
def result kronk
|
14
|
+
def result kronk
|
18
15
|
status = "."
|
19
16
|
|
20
|
-
|
17
|
+
result =
|
21
18
|
if kronk.diff
|
22
19
|
status = "F" if kronk.diff.any?
|
23
20
|
text = diff_text kronk if status == "F"
|
@@ -31,7 +28,7 @@ class Kronk
|
|
31
28
|
# Make sure response is parsable
|
32
29
|
kronk.response.parsed_body if kronk.response.parser
|
33
30
|
rescue => e
|
34
|
-
error e, kronk
|
31
|
+
error e, kronk
|
35
32
|
return
|
36
33
|
end if kronk.response.success?
|
37
34
|
|
@@ -40,22 +37,26 @@ class Kronk
|
|
40
37
|
[status, kronk.response.time, text]
|
41
38
|
end
|
42
39
|
|
40
|
+
@mutex.synchronize{ @results << result }
|
41
|
+
|
43
42
|
$stdout << status
|
44
43
|
$stdout.flush
|
45
44
|
end
|
46
45
|
|
47
46
|
|
48
|
-
def error err, kronk=nil
|
47
|
+
def error err, kronk=nil
|
49
48
|
status = "E"
|
50
|
-
|
49
|
+
result = [status, 0, error_text(err, kronk)]
|
50
|
+
@mutex.synchronize{ @results << result }
|
51
51
|
|
52
52
|
$stdout << status
|
53
53
|
$stdout.flush
|
54
54
|
end
|
55
55
|
|
56
56
|
|
57
|
-
def
|
58
|
-
|
57
|
+
def complete
|
58
|
+
suite_time = Time.now - @start_time
|
59
|
+
player_time = @stop_time - @start_time
|
59
60
|
total_time = 0
|
60
61
|
bad_count = 0
|
61
62
|
failure_count = 0
|
@@ -85,13 +86,13 @@ class Kronk
|
|
85
86
|
avg_time = non_error_count > 0 ? total_time / non_error_count : "n/a"
|
86
87
|
avg_qps = non_error_count > 0 ? non_error_count / player_time : "n/a"
|
87
88
|
|
88
|
-
$stdout.puts "\nFinished in #{
|
89
|
+
$stdout.puts "\nFinished in #{suite_time} seconds.\n"
|
89
90
|
$stderr.puts err_buffer unless err_buffer.empty?
|
90
91
|
$stdout.puts "\n#{@results.length} cases, " +
|
91
92
|
"#{failure_count} failures, #{error_count} errors"
|
92
93
|
|
93
|
-
$stdout.puts "Avg Time: #{avg_time}"
|
94
|
-
$stdout.puts "Avg QPS:
|
94
|
+
$stdout.puts "Avg Time: #{(avg_time * 1000).round 3}ms"
|
95
|
+
$stdout.puts "Avg QPS: #{avg_qps.round 3}"
|
95
96
|
|
96
97
|
return bad_count == 0
|
97
98
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Kronk
|
2
|
+
|
3
|
+
##
|
4
|
+
# Outputs player request and response metrics as a tab seperated values.
|
5
|
+
|
6
|
+
class Player::TSV < Player
|
7
|
+
|
8
|
+
def start
|
9
|
+
@total_bytes = 0
|
10
|
+
|
11
|
+
$stdout.puts %w{
|
12
|
+
timestamp(ms)
|
13
|
+
resp_time(ms)
|
14
|
+
bytes
|
15
|
+
bps
|
16
|
+
qps
|
17
|
+
code
|
18
|
+
scheme
|
19
|
+
host
|
20
|
+
port
|
21
|
+
path
|
22
|
+
}.join("\t")
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def result kronk
|
27
|
+
suite_time = Time.now - @start_time
|
28
|
+
qps = (@count / suite_time).round(3)
|
29
|
+
|
30
|
+
kronk.responses.each do |resp|
|
31
|
+
@mutex.synchronize do
|
32
|
+
@total_bytes += resp.total_bytes
|
33
|
+
req_time = ((Time.now - resp.time).to_f * 1000).to_i
|
34
|
+
|
35
|
+
$stdout.puts [
|
36
|
+
req_time,
|
37
|
+
(resp.time * 1000).round,
|
38
|
+
resp.bytes,
|
39
|
+
(@total_bytes / suite_time).round,
|
40
|
+
qps,
|
41
|
+
resp.code,
|
42
|
+
resp.uri.scheme,
|
43
|
+
resp.uri.host,
|
44
|
+
resp.uri.port,
|
45
|
+
resp.uri.path
|
46
|
+
].join("\t")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/kronk/queue_runner.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
class Kronk
|
2
2
|
|
3
3
|
##
|
4
|
-
# A basic queue and input processor that
|
5
|
-
# evented backend (using EventMachine).
|
4
|
+
# A basic queue and input processor that runs multi-threaded.
|
6
5
|
#
|
7
6
|
# Input is optional and specified by creating an input trigger
|
8
7
|
# (passing a block to on(:input)).
|
@@ -32,52 +31,49 @@ class Kronk
|
|
32
31
|
# # as optional second argument.
|
33
32
|
# qrunner.run do |queue_item|
|
34
33
|
# # Do something with item.
|
35
|
-
#
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Additionally, the :interrupt trigger may be used to handle behavior when
|
37
|
+
# SIGINT is sent to the process.
|
38
|
+
#
|
39
|
+
# qrunner.on :interrupt do
|
40
|
+
# qrunner.kill
|
41
|
+
# puts "Caught SIGINT"
|
42
|
+
# exit 1
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# The :result trigger may also be used to
|
46
|
+
# perform actions with the return value of the block given to QueueRunner#run.
|
47
|
+
# This is useful for post-processing data without affecting concurrency as
|
48
|
+
# it will be run in a separate thread.
|
49
|
+
#
|
50
|
+
# qrunner.on :result do |result|
|
51
|
+
# p result
|
36
52
|
# end
|
37
53
|
|
38
54
|
class QueueRunner
|
39
55
|
|
40
|
-
|
41
|
-
# Define whether to use the EventMachine or the threaded behavior.
|
42
|
-
|
43
|
-
def self.async= value
|
44
|
-
@async = !!value
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
|
-
##
|
49
|
-
# Returns true if EventMachine is enabled
|
50
|
-
|
51
|
-
def self.async
|
52
|
-
@async
|
53
|
-
end
|
54
|
-
|
55
|
-
self.async = false
|
56
|
-
|
57
|
-
|
58
|
-
attr_accessor :number, :concurrency, :queue, :count,
|
59
|
-
:mutex, :threads, :reader_thread
|
56
|
+
attr_accessor :number, :queue, :count, :threads, :reader_thread
|
60
57
|
|
61
58
|
##
|
62
59
|
# Create a new QueueRunner for batch multi-threaded processing.
|
63
60
|
# Supported options are:
|
64
|
-
# :concurrency:: Fixnum - Maximum number of concurrent items to process
|
65
61
|
# :number:: Fixnum - Total number of items to process
|
66
62
|
|
67
63
|
def initialize opts={}
|
68
|
-
@number
|
69
|
-
@
|
70
|
-
@
|
64
|
+
@number = opts[:number]
|
65
|
+
@count = 0
|
66
|
+
@queue = []
|
67
|
+
@threads = []
|
68
|
+
@rthreads = []
|
71
69
|
|
72
|
-
@
|
73
|
-
@queue = []
|
74
|
-
@threads = []
|
70
|
+
@max_queue_size = 100
|
75
71
|
|
76
72
|
@reader_thread = nil
|
77
73
|
|
78
74
|
@triggers = {}
|
79
75
|
|
80
|
-
@
|
76
|
+
@qmutex = Mutex.new
|
81
77
|
end
|
82
78
|
|
83
79
|
|
@@ -97,9 +93,15 @@ class Kronk
|
|
97
93
|
|
98
94
|
def finish
|
99
95
|
stop_input!
|
100
|
-
|
101
|
-
@threads.each
|
96
|
+
|
97
|
+
@threads.each do |t|
|
98
|
+
@rthreads << Thread.new(t.value){|value| trigger :result, value }
|
99
|
+
end
|
100
|
+
|
101
|
+
@rthreads.each(&:join)
|
102
|
+
|
102
103
|
@threads.clear
|
104
|
+
@rthreads.clear
|
103
105
|
end
|
104
106
|
|
105
107
|
|
@@ -108,9 +110,10 @@ class Kronk
|
|
108
110
|
|
109
111
|
def kill
|
110
112
|
stop_input!
|
111
|
-
EM.stop if defined?(EM) && EM.reactor_running?
|
112
113
|
@threads.each{|t| t.kill}
|
114
|
+
@rthreads.each{|t| t.kill}
|
113
115
|
@threads.clear
|
116
|
+
@rthreads.clear
|
114
117
|
end
|
115
118
|
|
116
119
|
|
@@ -128,127 +131,141 @@ class Kronk
|
|
128
131
|
|
129
132
|
|
130
133
|
##
|
131
|
-
#
|
134
|
+
# Process the queue and read from IO if available.
|
132
135
|
#
|
133
136
|
# Yields queue item until queue and io (if available) are empty and the
|
134
137
|
# totaly number of requests to run is met (if number is set).
|
135
138
|
|
136
|
-
def
|
137
|
-
|
138
|
-
@count = 0
|
139
|
+
def concurrently concurrency=1, &block
|
140
|
+
@max_queue_size = concurrency * 2
|
139
141
|
|
140
|
-
|
141
|
-
@
|
142
|
-
|
143
|
-
if @threads.length >= @concurrency || @queue.empty?
|
142
|
+
until_finished do |count, active_count|
|
143
|
+
if active_count >= concurrency || @queue.empty?
|
144
144
|
Thread.pass
|
145
145
|
next
|
146
146
|
end
|
147
147
|
|
148
|
-
|
149
|
-
yield q_item if block_given?
|
150
|
-
end
|
148
|
+
num_threads = smaller_count(concurrency - active_count)
|
151
149
|
|
152
|
-
|
150
|
+
num_threads.times do
|
151
|
+
yield_queue_item(&block)
|
152
|
+
end
|
153
153
|
end
|
154
|
-
|
155
|
-
finish
|
156
154
|
end
|
157
155
|
|
158
156
|
|
159
157
|
##
|
160
|
-
#
|
158
|
+
# Process the queue with periodic timer and a given period in seconds.
|
161
159
|
#
|
162
160
|
# Yields queue item until queue and io (if available) are empty and the
|
163
161
|
# totaly number of requests to run is met (if number is set).
|
164
|
-
#
|
165
|
-
# Uses EventMachine to run asynchronously.
|
166
|
-
#
|
167
|
-
# Note: If the block given doesn't use EM, it will be blocking.
|
168
162
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
Cmd.verbose "Running async"
|
163
|
+
def periodically period=0.01, &block
|
164
|
+
@max_queue_size = 0.5 / period
|
165
|
+
@max_queue_size = 2 if @max_queue_size < 2
|
173
166
|
|
174
|
-
|
175
|
-
|
176
|
-
@count = 0
|
167
|
+
start = Time.now
|
177
168
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
next if EM.connection_count > 0
|
182
|
-
finish
|
183
|
-
next
|
184
|
-
end
|
169
|
+
until_finished do |count, active_count|
|
170
|
+
num_threads = 1
|
171
|
+
expected_count = ((Time.now - start) / period).ceil
|
185
172
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
173
|
+
if count < expected_count
|
174
|
+
num_threads = smaller_count(expected_count - count)
|
175
|
+
else
|
176
|
+
sleep period
|
177
|
+
end
|
190
178
|
|
191
|
-
|
192
|
-
|
179
|
+
num_threads.times do
|
180
|
+
yield_queue_item(&block)
|
193
181
|
end
|
194
182
|
end
|
195
183
|
end
|
196
184
|
|
197
185
|
|
186
|
+
|
198
187
|
##
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# block:
|
202
|
-
#
|
203
|
-
# runner = QueueRunner.new :concurrency => 10
|
204
|
-
# runner.queue.concat %w{item1 item2 item3}
|
205
|
-
#
|
206
|
-
# runner.run do |q_item, mutex|
|
207
|
-
# # This block is run in its own thread.
|
208
|
-
# mutex.synchronize{ do_something_with q_item }
|
209
|
-
# end
|
210
|
-
#
|
211
|
-
# Calls the :start trigger before execution begins, calls :complete
|
212
|
-
# when the execution has ended or is interrupted, also calls :interrupt
|
213
|
-
# when execution is interrupted.
|
188
|
+
# Loop and read from input continually until finished.
|
189
|
+
# Yields total_count and active_count if passed a block.
|
214
190
|
|
215
|
-
def
|
216
|
-
trap 'INT' do
|
191
|
+
def until_finished
|
192
|
+
old_trap = trap 'INT' do
|
193
|
+
@stop_time = Time.now
|
217
194
|
kill
|
218
|
-
|
195
|
+
trigger(:interrupt)
|
196
|
+
trap 'INT', old_trap
|
197
|
+
Process.kill 'INT', Process.pid
|
219
198
|
end
|
220
199
|
|
200
|
+
@start_time = Time.now
|
201
|
+
|
221
202
|
trigger :start
|
222
203
|
|
223
|
-
|
204
|
+
start_input!
|
205
|
+
@count = 0
|
206
|
+
|
207
|
+
until finished?
|
208
|
+
@rthreads.delete_if{|t| !t.alive? && t.join }
|
209
|
+
|
210
|
+
results = []
|
211
|
+
@threads.delete_if do |t|
|
212
|
+
!t.alive? &&
|
213
|
+
results << t.value
|
214
|
+
end
|
215
|
+
|
216
|
+
@rthreads << Thread.new(results) do |values|
|
217
|
+
values.each{|value| trigger :result, value }
|
218
|
+
end unless results.empty?
|
224
219
|
|
225
|
-
|
226
|
-
yield q_item, @mutex if block_given?
|
220
|
+
yield @count, @threads.count if block_given?
|
227
221
|
end
|
228
222
|
|
223
|
+
@stop_time = Time.now
|
224
|
+
|
225
|
+
finish
|
226
|
+
|
229
227
|
trigger :complete
|
230
228
|
end
|
231
229
|
|
232
230
|
|
231
|
+
##
|
232
|
+
# Shifts one item off the queue and yields it to the given block.
|
233
|
+
|
234
|
+
def yield_queue_item
|
235
|
+
until item = @qmutex.synchronize{ @queue.shift } or !@reader_thread.alive?
|
236
|
+
Thread.pass
|
237
|
+
end
|
238
|
+
|
239
|
+
return unless item
|
240
|
+
|
241
|
+
@threads << Thread.new(item) do |q_item|
|
242
|
+
yield q_item if block_given?
|
243
|
+
end
|
244
|
+
|
245
|
+
@threads.last.abort_on_exception = true
|
246
|
+
|
247
|
+
@count += 1
|
248
|
+
end
|
249
|
+
|
250
|
+
|
233
251
|
##
|
234
252
|
# Attempt to fill the queue by reading from the IO instance.
|
235
253
|
# Starts a new thread and returns the thread instance.
|
236
254
|
|
237
|
-
def start_input!
|
255
|
+
def start_input! max_queue=@max_queue_size
|
238
256
|
return unless @triggers[:input]
|
239
257
|
|
240
|
-
max_queue_size = @concurrency * 2
|
241
|
-
|
242
258
|
@reader_thread = Thread.new do
|
243
259
|
begin
|
244
260
|
loop do
|
245
|
-
if @queue.length >=
|
261
|
+
if max_queue && @queue.length >= max_queue
|
246
262
|
Thread.pass
|
247
263
|
next
|
248
264
|
end
|
249
265
|
|
250
|
-
while @queue.length <
|
251
|
-
|
266
|
+
while !max_queue || @queue.length < max_queue
|
267
|
+
item = trigger(:input)
|
268
|
+
@qmutex.synchronize{ @queue << item }
|
252
269
|
end
|
253
270
|
Thread.pass
|
254
271
|
end
|
@@ -273,9 +290,16 @@ class Kronk
|
|
273
290
|
##
|
274
291
|
# Run a previously defined callback. See QueueRunner#on.
|
275
292
|
|
276
|
-
def trigger name
|
293
|
+
def trigger name, *args
|
277
294
|
t = @triggers[name]
|
278
|
-
t && t.call
|
295
|
+
t && t.call(*args)
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
private
|
300
|
+
|
301
|
+
def smaller_count num
|
302
|
+
@number && (@number - count < num) ? (@number - count) : num
|
279
303
|
end
|
280
304
|
end
|
281
305
|
end
|