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.
@@ -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::Output
24
+ class Player::Stream < Player
25
25
 
26
- def result kronk, mutex=nil
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
- output = "#{"%X" % output.length}\r\n#{output}\r\n"
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
@@ -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::Output
6
+ class Player::Suite < Player
7
7
 
8
8
  def start
9
- @results = []
10
- msg = "Started"
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, mutex=nil
14
+ def result kronk
18
15
  status = "."
19
16
 
20
- @results <<
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, mutex
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, mutex=nil
47
+ def error err, kronk=nil
49
48
  status = "E"
50
- @results << [status, 0, error_text(err, kronk)]
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 completed
58
- player_time = (Time.now - @start_time).to_f
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 #{player_time} seconds.\n"
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: #{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
@@ -1,8 +1,7 @@
1
1
  class Kronk
2
2
 
3
3
  ##
4
- # A basic queue and input processor that supports both a multi-threaded and
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
- # # When running in evented mode, make sure this section is non-blocking.
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 = opts[:number]
69
- @concurrency = opts[:concurrency]
70
- @concurrency = 1 if !@concurrency || @concurrency <= 0
64
+ @number = opts[:number]
65
+ @count = 0
66
+ @queue = []
67
+ @threads = []
68
+ @rthreads = []
71
69
 
72
- @count = 0
73
- @queue = []
74
- @threads = []
70
+ @max_queue_size = 100
75
71
 
76
72
  @reader_thread = nil
77
73
 
78
74
  @triggers = {}
79
75
 
80
- @mutex = Mutex.new
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
- EM.stop if defined?(EM) && EM.reactor_running?
101
- @threads.each{|t| t.join}
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
- # Start processing the queue and reading from IO if available.
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 process_queue
137
- start_input!
138
- @count = 0
139
+ def concurrently concurrency=1, &block
140
+ @max_queue_size = concurrency * 2
139
141
 
140
- until finished?
141
- @threads.delete_if{|t| !t.alive? }
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
- @threads << Thread.new(@queue.shift) do |q_item|
149
- yield q_item if block_given?
150
- end
148
+ num_threads = smaller_count(concurrency - active_count)
151
149
 
152
- @count += 1
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
- # Start processing the queue and reading from IO if available.
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 process_queue_async &block
170
- # TODO: Make input use EM from QueueRunner and Player IO.
171
- require 'kronk/async' unless defined?(EM::HttpRequest)
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
- start_input!
175
-
176
- @count = 0
167
+ start = Time.now
177
168
 
178
- EM.run do
179
- EM.add_periodic_timer do
180
- if finished?
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
- if @queue.empty? || EM.connection_count >= @concurrency
187
- Thread.pass
188
- next
189
- end
173
+ if count < expected_count
174
+ num_threads = smaller_count(expected_count - count)
175
+ else
176
+ sleep period
177
+ end
190
178
 
191
- yield @queue.shift
192
- @count += 1
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
- # Runs the queue and reads from input until it's exhausted or
200
- # @number is reached. Yields a queue item and a mutex when to passed
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 run
216
- trap 'INT' do
191
+ def until_finished
192
+ old_trap = trap 'INT' do
193
+ @stop_time = Time.now
217
194
  kill
218
- (trigger(:interrupt) || exit(1))
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
- method = self.class.async ? :process_queue_async : :process_queue
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
- send method do |q_item|
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 >= max_queue_size
261
+ if max_queue && @queue.length >= max_queue
246
262
  Thread.pass
247
263
  next
248
264
  end
249
265
 
250
- while @queue.length < max_queue_size
251
- @queue << trigger(:input)
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