kronk 1.6.2 → 1.7.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.
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