kronk 1.6.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/History.rdoc +29 -1
  2. data/Manifest.txt +6 -1
  3. data/README.rdoc +74 -28
  4. data/Rakefile +4 -3
  5. data/TODO.rdoc +7 -5
  6. data/bin/kronk +2 -11
  7. data/lib/kronk/async/em_ext.rb +34 -0
  8. data/lib/kronk/async/request.rb +73 -0
  9. data/lib/kronk/async/response.rb +70 -0
  10. data/lib/kronk/async.rb +118 -0
  11. data/lib/kronk/cmd.rb +111 -43
  12. data/lib/kronk/constants.rb +1 -0
  13. data/lib/kronk/core_ext.rb +1 -1
  14. data/lib/kronk/data_string.rb +251 -0
  15. data/lib/kronk/diff/output.rb +132 -100
  16. data/lib/kronk/diff.rb +20 -24
  17. data/lib/kronk/path/matcher.rb +8 -4
  18. data/lib/kronk/path/path_match.rb +48 -4
  19. data/lib/kronk/path/transaction.rb +74 -53
  20. data/lib/kronk/path.rb +11 -6
  21. data/lib/kronk/player/benchmark.rb +11 -12
  22. data/lib/kronk/player/input_reader.rb +40 -3
  23. data/lib/kronk/player/request_parser.rb +4 -1
  24. data/lib/kronk/player/stream.rb +2 -2
  25. data/lib/kronk/player/suite.rb +16 -9
  26. data/lib/kronk/player.rb +93 -143
  27. data/lib/kronk/queue_runner.rb +238 -0
  28. data/lib/kronk/request.rb +25 -20
  29. data/lib/kronk/response.rb +39 -10
  30. data/lib/kronk/test/assertions.rb +2 -2
  31. data/lib/kronk/test/helper_methods.rb +1 -1
  32. data/lib/kronk.rb +56 -24
  33. data/test/test_assertions.rb +4 -4
  34. data/test/test_cmd.rb +38 -10
  35. data/test/test_data_string.rb +242 -1
  36. data/test/test_diff.rb +8 -303
  37. data/test/test_helper.rb +1 -1
  38. data/test/test_kronk.rb +21 -28
  39. data/test/test_path.rb +29 -0
  40. data/test/test_path_match.rb +47 -2
  41. data/test/test_path_matcher.rb +42 -1
  42. data/test/test_player.rb +71 -72
  43. data/test/test_request.rb +31 -6
  44. data/test/test_request_parser.rb +7 -1
  45. data/test/test_response.rb +1 -1
  46. data/test/test_transaction.rb +78 -30
  47. metadata +64 -8
  48. data/lib/kronk/data_renderer.rb +0 -219
@@ -11,9 +11,12 @@
11
11
  # {:name => "Tory", :id => "12345"},
12
12
  # ]
13
13
  #
14
- # # Select all element names but delete the one at index 2
14
+ # # Select all element names, delete the one at index 2,
15
+ # # and move the element with the value "Tory" to the same path but
16
+ # # with the key renamed to "boo"
15
17
  # Transaction.run data do |t|
16
18
  # t.select "*/name"
19
+ # t.move "**=Tory" => "%%/boo"
17
20
  # t.delete "2"
18
21
  # end
19
22
  #
@@ -21,7 +24,7 @@
21
24
  # # {:name => "Jamie"},
22
25
  # # {:name => "Adam"},
23
26
  # # {:name => "Grant"},
24
- # # {:name => "Tory"},
27
+ # # {"boo" => "Tory"},
25
28
  # # ]
26
29
 
27
30
  class Kronk::Path::Transaction
@@ -36,6 +39,8 @@ class Kronk::Path::Transaction
36
39
  end
37
40
 
38
41
 
42
+ attr_accessor :actions
43
+
39
44
  ##
40
45
  # Create a new Transaction instance with a the data object to perform
41
46
  # operations on.
@@ -43,12 +48,7 @@ class Kronk::Path::Transaction
43
48
  def initialize data
44
49
  @data = data
45
50
  @new_data = nil
46
- @actions = {
47
- :select => [],
48
- :delete => [],
49
- :move => {},
50
- :map => {}
51
- }
51
+ @actions = []
52
52
 
53
53
  @make_array = {}
54
54
  end
@@ -71,10 +71,14 @@ class Kronk::Path::Transaction
71
71
  # pass the :keep_indicies => true option.
72
72
 
73
73
  def results opts={}
74
- new_data = transaction_select @data, *@actions[:select]
75
- new_data = transaction_map @data, @actions[:map]
76
- new_data = transaction_move @data, @actions[:move]
77
- new_data = transaction_delete @data, *@actions[:delete]
74
+ new_data = @data
75
+ prev_type = nil
76
+ prev_data = nil
77
+
78
+ @actions.each do |type, paths|
79
+ new_data = send("transaction_#{type}", new_data, *paths)
80
+ end
81
+
78
82
  remake_arrays new_data, opts[:keep_indicies]
79
83
  end
80
84
 
@@ -104,9 +108,22 @@ class Kronk::Path::Transaction
104
108
  end
105
109
 
106
110
 
111
+ def remap_make_arrays new_path, old_path # :nodoc:
112
+ @make_array[new_path] = true and return if @make_array[old_path]
113
+
114
+ @make_array.keys.each do |path|
115
+ if path[0...old_path.length] == old_path
116
+ path[0...old_path.length] = new_path
117
+ end
118
+ end
119
+ end
120
+
121
+
107
122
  def transaction_select data, *data_paths # :nodoc:
108
- transaction data, data_paths, true do |new_curr_data, curr_data, key|
109
- new_curr_data[key] = curr_data[key]
123
+ return data if data_paths.empty?
124
+
125
+ transaction data, data_paths, true do |sdata, cdata, key, path, tpath|
126
+ sdata[key] = cdata[key]
110
127
  end
111
128
  end
112
129
 
@@ -118,41 +135,31 @@ class Kronk::Path::Transaction
118
135
  end
119
136
 
120
137
 
121
- def transaction_move data, match_target_hash # :nodoc:
122
- return data if match_target_hash.empty?
138
+ def transaction_move data, *path_pairs # :nodoc:
139
+ return data if path_pairs.empty?
123
140
  path_val_hash = {}
124
141
 
125
- match_target_hash.each do |data_path, path_map|
126
- transaction data, [data_path] do |new_curr_data, cdata, key, path|
127
- mapped_path = path.make_path path_map
128
- path_val_hash[mapped_path] = new_curr_data.delete key
129
- if @make_array[path]
130
- @make_array.delete path
131
- @make_array[mapped_path] = true
132
- end
142
+ new_data =
143
+ transaction data, path_pairs do |sdata, cdata, key, path, tpath|
144
+ path_val_hash[tpath] = sdata.delete key
145
+ remap_make_arrays(tpath, path)
133
146
  end
134
- end
135
147
 
136
- force_assign_paths @new_data, path_val_hash
148
+ force_assign_paths new_data, path_val_hash
137
149
  end
138
150
 
139
151
 
140
- def transaction_map data, match_target_hash # :nodoc:
141
- return data if match_target_hash.empty?
152
+ def transaction_map data, *path_pairs # :nodoc:
153
+ return data if path_pairs.empty?
142
154
  path_val_hash = {}
143
155
 
144
- match_target_hash.each do |data_path, path_map|
145
- Kronk::Path.find data_path, data do |sdata, key, spath|
146
- mapped_path = spath.make_path path_map
147
- path_val_hash[mapped_path] = sdata[key]
148
- if @make_array[spath]
149
- @make_array.delete spath
150
- @make_array[mapped_path] = true
151
- end
152
- end
156
+ transaction data, path_pairs do |sdata, cdata, key, path, tpath|
157
+ tpath ||= path
158
+ path_val_hash[tpath] = sdata.delete key
159
+ remap_make_arrays(tpath, path)
153
160
  end
154
161
 
155
- force_assign_paths @new_data, path_val_hash
162
+ force_assign_paths data.class.new, path_val_hash
156
163
  end
157
164
 
158
165
 
@@ -160,13 +167,17 @@ class Kronk::Path::Transaction
160
167
  data_paths = data_paths.compact
161
168
  return @new_data || data if data_paths.empty?
162
169
 
163
- @new_data ||= create_empty ? Hash.new : data.dup
170
+ @new_data = create_empty ? Hash.new : data.dup
164
171
 
165
172
  if Array === @new_data
166
173
  @new_data = ary_to_hash @new_data
167
174
  end
168
175
 
169
176
  data_paths.each do |data_path|
177
+ # If data_path is an array, the second element is the path where the value
178
+ # should be mapped to.
179
+ data_path, target_path = data_path
180
+
170
181
  Kronk::Path.find data_path, data do |obj, k, path|
171
182
  curr_data = data
172
183
  new_curr_data = @new_data
@@ -175,7 +186,8 @@ class Kronk::Path::Transaction
175
186
  break unless new_curr_data
176
187
 
177
188
  if i == path.length - 1
178
- yield new_curr_data, curr_data, key, path if block_given?
189
+ tpath = path.make_path target_path if target_path
190
+ yield new_curr_data, curr_data, key, path, tpath if block_given?
179
191
 
180
192
  else
181
193
  if create_empty
@@ -202,7 +214,7 @@ class Kronk::Path::Transaction
202
214
 
203
215
  def force_assign_paths data, path_val_hash # :nodoc:
204
216
  return data if path_val_hash.empty?
205
- @new_data ||= (data.dup rescue [])
217
+ @new_data = (data.dup rescue [])
206
218
 
207
219
  path_val_hash.each do |path, value|
208
220
  curr_data = data
@@ -216,7 +228,7 @@ class Kronk::Path::Transaction
216
228
  new_curr_data = ary_to_hash new_curr_data
217
229
  prev_data[prev_key] = new_curr_data if prev_data
218
230
  @new_data = new_curr_data if i == 0
219
- @make_array[prev_path] = true
231
+ @make_array[prev_path] = true if i == 0
220
232
  end
221
233
 
222
234
  last = i == path.length - 1
@@ -226,7 +238,7 @@ class Kronk::Path::Transaction
226
238
 
227
239
  # new_curr_data is a hash from here on
228
240
 
229
- @make_array.delete prev_path unless Integer === key
241
+ @make_array.delete prev_path unless is_integer?(key)
230
242
 
231
243
  new_curr_data[key] = value and break if last
232
244
 
@@ -234,7 +246,7 @@ class Kronk::Path::Transaction
234
246
  new_curr_data[key] ||= curr_data[key]
235
247
 
236
248
  elsif !ary_or_hash?(new_curr_data[key])
237
- new_curr_data[key] = Integer === next_key ? [] : {}
249
+ new_curr_data[key] = is_integer?(next_key) ? [] : {}
238
250
  end
239
251
 
240
252
  @make_array[curr_path] = true if Array === new_curr_data[key]
@@ -250,6 +262,11 @@ class Kronk::Path::Transaction
250
262
  end
251
263
 
252
264
 
265
+ def is_integer? item # :nodoc:
266
+ item.to_s.to_i.to_s == item.to_s
267
+ end
268
+
269
+
253
270
  def ary_or_hash? obj # :nodoc:
254
271
  Array === obj || Hash === obj
255
272
  end
@@ -272,7 +289,7 @@ class Kronk::Path::Transaction
272
289
 
273
290
  def clear
274
291
  @new_data = nil
275
- @actions.each{|k,v| v.clear}
292
+ @actions.clear
276
293
  @make_array.clear
277
294
  end
278
295
 
@@ -281,7 +298,11 @@ class Kronk::Path::Transaction
281
298
  # Queues path selects for transaction.
282
299
 
283
300
  def select *paths
284
- @actions[:select].concat paths
301
+ if @actions.last && @actions.last[0] == :select
302
+ @actions.last[1].concat paths
303
+ else
304
+ @actions << [:select, paths]
305
+ end
285
306
  end
286
307
 
287
308
 
@@ -289,7 +310,7 @@ class Kronk::Path::Transaction
289
310
  # Queues path deletes for transaction.
290
311
 
291
312
  def delete *paths
292
- @actions[:delete].concat paths
313
+ @actions << [:delete, paths]
293
314
  end
294
315
 
295
316
 
@@ -297,21 +318,21 @@ class Kronk::Path::Transaction
297
318
  # Queues path moving for transaction. Moving a path will attempt to
298
319
  # keep the original data structure and only affect the given paths.
299
320
  # Empty hashes or arrays after a move are deleted.
300
- # t.move "my/path/1..4/key" => "new_path/%d/key",
301
- # "other/path/*" => "moved/%d"
321
+ # t.move "my/path/1..4/key" => "new_path/%1/key",
322
+ # "other/path/*" => "moved/%1"
302
323
 
303
324
  def move path_maps
304
- @actions[:move].merge! path_maps
325
+ @actions << [:move, Array(path_maps)]
305
326
  end
306
327
 
307
328
 
308
329
  ##
309
330
  # Queues path mapping for transaction. Mapping a path will only keep the
310
331
  # mapped values, completely replacing the original data structure.
311
- # t.move "my/path/1..4/key" => "new_path/%d/key",
312
- # "other/path/*" => "moved/%d"
332
+ # t.map "my/path/1..4/key" => "new_path/%1/key",
333
+ # "other/path/*" => "moved/%1"
313
334
 
314
335
  def map path_maps
315
- @actions[:map].merge! path_maps
336
+ @actions << [:map, Array(path_maps)]
316
337
  end
317
338
  end
data/lib/kronk/path.rb CHANGED
@@ -136,16 +136,20 @@ class Kronk
136
136
  end
137
137
 
138
138
 
139
+ SPECIAL_CHARS = "*?()|/."
140
+ R_SPECIAL_CHARS = /[\0#{Regexp.escape SPECIAL_CHARS}]/u
141
+
139
142
  ##
140
143
  # Joins an Array into a path String.
141
144
 
142
145
  def self.join path_arr, escape=true
143
- path_esc = "*?()|/."
144
- path_arr.map! do |k|
145
- k.to_s.gsub(/([#{path_esc}])/){|m| "\\#{m}"}
146
- end if escape
147
-
148
- path_arr.join(DCH)
146
+ path_str = path_arr.join("\0")
147
+ if escape
148
+ path_str.gsub!(R_SPECIAL_CHARS){|c| c == "\0" ? DCH : "\\#{c}"}
149
+ else
150
+ path_str.gsub! "\0", DCH
151
+ end
152
+ path_str
149
153
  end
150
154
 
151
155
 
@@ -282,6 +286,7 @@ class Kronk
282
286
  end
283
287
  end
284
288
 
289
+ # Make sure we're not trying to access /./thing
285
290
  unless key =~ /^\.?$/ && !value
286
291
  matcher = Matcher.new :key => key,
287
292
  :value => value,
@@ -17,10 +17,10 @@ class Kronk
17
17
 
18
18
  class ResultSet
19
19
 
20
- attr_reader :byterate, :count, :fastest, :hostname, :precision,
20
+ attr_reader :byterate, :count, :fastest, :precision,
21
21
  :slowest, :total_bytes
22
22
 
23
- def initialize uri, start_time
23
+ def initialize start_time
24
24
  @times = Hash.new(0)
25
25
  @count = 0
26
26
  @r5XX = 0
@@ -38,8 +38,6 @@ class Kronk
38
38
 
39
39
  @start_time = start_time
40
40
  @total_time = 0
41
-
42
- @hostname = "#{uri.scheme}://#{uri.host}:#{uri.port}" if uri
43
41
  end
44
42
 
45
43
 
@@ -55,7 +53,7 @@ class Kronk
55
53
  @slowest = time if !@slowest || @slowest < time
56
54
  @fastest = time if !@fastest || @fastest > time
57
55
 
58
- log_path resp.uri.path, time if resp.uri
56
+ log_path resp.uri.to_s, time if resp.uri
59
57
 
60
58
  @total_bytes += resp.raw.bytes.count
61
59
 
@@ -139,14 +137,13 @@ class Kronk
139
137
  end
140
138
 
141
139
 
142
- def slowest_paths
143
- @paths.to_a.sort{|x,y| y[1] <=> x[1]}[0..9]
140
+ def slowest_reqs
141
+ @slowest_reqs ||= @paths.to_a.sort{|x,y| y[1] <=> x[1]}[0..9]
144
142
  end
145
143
 
146
144
 
147
145
  def to_s
148
146
  out = <<-STR
149
- Host: #{@hostname || "<IO>"}
150
147
  Completed: #{@count}
151
148
  400s: #{@r4XX}
152
149
  500s: #{@r5XX}
@@ -173,9 +170,11 @@ Request Percentages (ms)
173
170
  100% #{self.percentages[100]} (longest request)
174
171
  STR
175
172
 
176
- out << "
177
- Avg. Slowest Paths (ms, #)
178
- #{slowest_paths.map{|arr| " #{(arr[1])} #{arr[0]}"}.join "\n" }" if @hostname
173
+ unless slowest_reqs.empty?
174
+ out << "
175
+ Avg. Slowest Requests (ms, #)
176
+ #{slowest_reqs.map{|arr| " #{arr[1]} #{arr[0]}"}.join "\n" }"
177
+ end
179
178
 
180
179
  out
181
180
  end
@@ -203,7 +202,7 @@ Avg. Slowest Paths (ms, #)
203
202
  kronk.responses.each_with_index do |resp, i|
204
203
  mutex.synchronize do
205
204
  @count += 1
206
- @results[i] ||= ResultSet.new(resp.uri, @start_time)
205
+ @results[i] ||= ResultSet.new(@start_time)
207
206
  @results[i].add_result resp
208
207
 
209
208
  puts "#{@count} requests" if @count % @div == 0
@@ -12,6 +12,7 @@ class Kronk
12
12
  attr_accessor :io, :parser, :buffer
13
13
 
14
14
  def initialize string_or_io, parser=nil
15
+ @io_buf = ""
15
16
  @buffer = []
16
17
  @parser = parser || Kronk::Player::RequestParser
17
18
  @io = string_or_io
@@ -25,10 +26,10 @@ class Kronk
25
26
  def get_next
26
27
  return if eof?
27
28
 
28
- @buffer << @io.gets if @buffer.empty?
29
+ @buffer << gets if @buffer.empty?
29
30
 
30
- until @io.eof?
31
- line = @io.gets
31
+ until @io.eof? && @io_buf.empty?
32
+ line = gets
32
33
  next unless line
33
34
 
34
35
  if @parser.start_new?(line) || @buffer.empty?
@@ -44,6 +45,42 @@ class Kronk
44
45
  end
45
46
 
46
47
 
48
+ ##
49
+ # Read one line from @io, thread-non-blocking.
50
+
51
+ def gets
52
+ return @io.gets if StringIO === @io
53
+
54
+ next_line = io_buf_line
55
+ return next_line if next_line
56
+
57
+ until @io.eof?
58
+ selected, = select [@io], nil, nil, 0.05
59
+
60
+ if selected.nil? || selected.empty?
61
+ Thread.pass
62
+ next
63
+ end
64
+
65
+ @io_buf << @io.readpartial(1024)
66
+
67
+ next_line = io_buf_line
68
+ return next_line if next_line
69
+ end
70
+ end
71
+
72
+
73
+ ##
74
+ # Get the first line of the io buffer.
75
+
76
+ def io_buf_line
77
+ index = @io_buf.index "\n"
78
+ return unless index
79
+
80
+ @io_buf.slice!(0..index)
81
+ end
82
+
83
+
47
84
  ##
48
85
  # Returns true if there is no more input to read from.
49
86
 
@@ -7,15 +7,18 @@ class Kronk
7
7
  class Player::RequestParser
8
8
 
9
9
  ##
10
- # Returns true-ish if the line given is the start of a new request.
10
+ # Returns true if the line given is the start of a new request.
11
11
 
12
12
  def self.start_new? line
13
13
  line =~ Request::REQUEST_LINE_MATCHER
14
+ # Make sure we have a host ($2) or path ($3 || $4) before proceeding.
15
+ !!($2 || $3 || $4)
14
16
  end
15
17
 
16
18
 
17
19
  ##
18
20
  # Parse a single http request kronk options hash.
21
+ # See Kronk::Request.parse for more about request parsing.
19
22
 
20
23
  def self.parse string
21
24
  Kronk::Request.parse_to_hash string
@@ -11,11 +11,11 @@ class Kronk
11
11
  # io2 = StringIO.new "this is the rest"
12
12
  #
13
13
  # kronk = Kronk.new
14
- # kronk.retrieve io1
14
+ # kronk.request io1
15
15
  # out.result kronk
16
16
  # #=> "23\r\nthis is the first chunk\r\n"
17
17
  #
18
- # kronk.retrieve io2
18
+ # kronk.request io2
19
19
  # out.result kronk
20
20
  # #=> "16\r\nthis is the rest\r\n"
21
21
  #
@@ -7,7 +7,9 @@ class Kronk
7
7
 
8
8
  def start
9
9
  @results = []
10
- $stdout.puts "Started"
10
+ msg = "Started"
11
+ msg << (Player.async ? " (async)" : " (threaded)")
12
+ $stdout.puts msg
11
13
  super
12
14
  end
13
15
 
@@ -17,7 +19,7 @@ class Kronk
17
19
 
18
20
  @results <<
19
21
  if kronk.diff
20
- status = "F" if kronk.diff.count > 0
22
+ status = "F" if kronk.diff.any?
21
23
  text = diff_text kronk if status == "F"
22
24
  time =
23
25
  (kronk.responses[0].time.to_f + kronk.responses[1].time.to_f) / 2
@@ -27,7 +29,7 @@ class Kronk
27
29
  elsif kronk.response
28
30
  begin
29
31
  # Make sure response is parsable
30
- kronk.response.stringify
32
+ kronk.response.parsed_body if kronk.response.parser
31
33
  rescue => e
32
34
  error e, kronk, mutex
33
35
  return
@@ -100,7 +102,8 @@ class Kronk
100
102
 
101
103
  def resp_text kronk
102
104
  <<-STR
103
- Request: #{kronk.response.code} - #{kronk.response.uri}
105
+ Request: #{kronk.response.code} - #{kronk.response.request.http_method} \
106
+ #{kronk.response.uri}
104
107
  Options: #{kronk.options.inspect}
105
108
  STR
106
109
  end
@@ -108,8 +111,12 @@ class Kronk
108
111
 
109
112
  def diff_text kronk
110
113
  <<-STR
111
- Request: #{kronk.responses[0].code} - #{kronk.responses[0].uri}
112
- #{kronk.responses[1].code} - #{kronk.responses[1].uri}
114
+ Request: #{kronk.responses[0].code} - \
115
+ #{kronk.responses[0].request.http_method} \
116
+ #{kronk.responses[0].uri}
117
+ #{kronk.responses[1].code} - \
118
+ #{kronk.responses[0].request.http_method} \
119
+ #{kronk.responses[1].uri}
113
120
  Options: #{kronk.options.inspect}
114
121
  Diffs: #{kronk.diff.count}
115
122
  STR
@@ -117,12 +124,12 @@ class Kronk
117
124
 
118
125
 
119
126
  def error_text err, kronk=nil
120
- str = "#{err.class}: #{err.message}"
127
+ str = " #{err.class}: #{err.message}"
121
128
 
122
129
  if kronk
123
- str << "\n Options: #{kronk.options.inspect}\n\n"
130
+ str << "\n Options: #{kronk.options.inspect}\n"
124
131
  else
125
- str << "\n #{err.backtrace}\n\n"
132
+ str << "\n #{err.backtrace}\n"
126
133
  end
127
134
 
128
135
  str