kronk 1.8.7 → 1.9.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 +22 -0
- data/Manifest.txt +6 -1
- data/README.rdoc +7 -2
- data/Rakefile +5 -4
- data/TODO.rdoc +3 -5
- data/lib/kronk.rb +20 -14
- data/lib/kronk/buffered_io.rb +7 -0
- data/lib/kronk/cmd.rb +25 -9
- data/lib/kronk/constants.rb +8 -0
- data/lib/kronk/http.rb +129 -2
- data/lib/kronk/multipart.rb +82 -0
- data/lib/kronk/multipart_io.rb +88 -0
- data/lib/kronk/player.rb +1 -1
- data/lib/kronk/player/benchmark.rb +69 -24
- data/lib/kronk/player/download.rb +87 -0
- data/lib/kronk/player/suite.rb +23 -11
- data/lib/kronk/request.rb +144 -77
- data/lib/kronk/response.rb +55 -18
- data/test/mocks/200_response.plist +1 -1
- data/test/test_cmd.rb +5 -3
- data/test/test_helper.rb +20 -10
- data/test/test_multipart.rb +144 -0
- data/test/test_multipart_io.rb +92 -0
- data/test/test_player.rb +7 -2
- data/test/test_request.rb +160 -43
- data/test/test_response.rb +34 -2
- metadata +27 -4
@@ -0,0 +1,82 @@
|
|
1
|
+
class Kronk
|
2
|
+
|
3
|
+
##
|
4
|
+
# Builder for the body of a multipart request.
|
5
|
+
|
6
|
+
class Multipart
|
7
|
+
|
8
|
+
# An array of parts for the multipart body.
|
9
|
+
attr_reader :parts
|
10
|
+
|
11
|
+
# The separator used between parts.
|
12
|
+
attr_reader :boundary
|
13
|
+
|
14
|
+
|
15
|
+
def initialize boundary
|
16
|
+
@boundary = boundary
|
17
|
+
@parts = []
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
# Add a new part to the body.
|
23
|
+
|
24
|
+
def add name, value, headers=nil
|
25
|
+
headers ||= {}
|
26
|
+
|
27
|
+
headers['content-disposition'] = "form-data; name=\"#{name}\""
|
28
|
+
|
29
|
+
if value.respond_to?(:path)
|
30
|
+
headers['content-disposition'] <<
|
31
|
+
"; filename=\"#{File.basename value.path}\""
|
32
|
+
|
33
|
+
headers['Content-Type'] ||= MIME::Types.of(value.path)[0]
|
34
|
+
headers['Content-Type'] &&= headers['Content-Type'].to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
if value.respond_to?(:read)
|
38
|
+
headers['Content-Type'] ||= "application/octet-stream"
|
39
|
+
headers['Content-Transfer-Encoding'] ||= 'binary'
|
40
|
+
end
|
41
|
+
|
42
|
+
parts << [headers, value]
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
##
|
47
|
+
# Convert the instance into a MultipartIO instance.
|
48
|
+
|
49
|
+
def to_io
|
50
|
+
io = Kronk::MultipartIO.new
|
51
|
+
buff = ""
|
52
|
+
|
53
|
+
parts.each do |(headers, value)|
|
54
|
+
buff << "--#{@boundary}\r\n"
|
55
|
+
buff << "content-disposition: #{headers['content-disposition']}\r\n"
|
56
|
+
|
57
|
+
headers.each do |hname, hvalue|
|
58
|
+
next if hname == 'content-disposition'
|
59
|
+
hvalue = hvalue.to_s.inspect if hvalue.to_s.index ":"
|
60
|
+
buff << "#{hname}: #{hvalue}\r\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
buff << "\r\n"
|
64
|
+
|
65
|
+
if value.respond_to?(:read)
|
66
|
+
io.add buff.dup
|
67
|
+
io.add value
|
68
|
+
buff.replace ""
|
69
|
+
else
|
70
|
+
buff << value.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
buff << "\r\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
buff << "--#{@boundary}--"
|
77
|
+
io.add buff
|
78
|
+
|
79
|
+
io
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Kronk
|
2
|
+
|
3
|
+
class MultipartIO
|
4
|
+
|
5
|
+
attr_reader :parts, :curr_part
|
6
|
+
|
7
|
+
def initialize *parts
|
8
|
+
@parts = []
|
9
|
+
@curr_part = 0
|
10
|
+
|
11
|
+
parts.each do |part|
|
12
|
+
add part
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def add part
|
18
|
+
if String === part
|
19
|
+
@parts << StringIO.new(part)
|
20
|
+
|
21
|
+
elsif part.respond_to?(:read)
|
22
|
+
@parts << part
|
23
|
+
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Invalid part #{part.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
@curr_part ||= @parts.length - 1
|
29
|
+
@parts.last
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def close
|
34
|
+
@parts.each(&:close)
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def read bytes=nil
|
40
|
+
return read_all if bytes.nil?
|
41
|
+
return if @parts.empty? || eof?
|
42
|
+
|
43
|
+
buff = ""
|
44
|
+
|
45
|
+
until @curr_part.nil?
|
46
|
+
bytes = bytes - buff.bytes.count
|
47
|
+
buff << @parts[@curr_part].read(bytes).to_s
|
48
|
+
break if buff.bytes.count >= bytes
|
49
|
+
|
50
|
+
@curr_part += 1
|
51
|
+
@curr_part = nil if @curr_part >= @parts.length
|
52
|
+
end
|
53
|
+
|
54
|
+
return if buff.empty?
|
55
|
+
buff
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def read_all
|
60
|
+
return "" if eof?
|
61
|
+
|
62
|
+
out = @parts[@curr_part..-1].inject("") do |out, curr|
|
63
|
+
@curr_part += 1
|
64
|
+
out << curr.read
|
65
|
+
end
|
66
|
+
|
67
|
+
@curr_part = nil
|
68
|
+
out
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def eof?
|
73
|
+
@curr_part.nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def size
|
78
|
+
total = 0
|
79
|
+
|
80
|
+
@parts.each do |part|
|
81
|
+
return nil unless part.respond_to?(:size) && part.size
|
82
|
+
total += part.size
|
83
|
+
end
|
84
|
+
|
85
|
+
total
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/kronk/player.rb
CHANGED
@@ -16,6 +16,7 @@ class Kronk
|
|
16
16
|
klass =
|
17
17
|
case type.to_s
|
18
18
|
when /^(Player::)?benchmark$/i then Benchmark
|
19
|
+
when /^(Player::)?download$/i then Download
|
19
20
|
when /^(Player::)?stream$/i then Stream
|
20
21
|
when /^(Player::)?suite$/i then Suite
|
21
22
|
when /^(Player::)?tsv$/i then TSV
|
@@ -58,7 +59,6 @@ class Kronk
|
|
58
59
|
on(:interrupt){
|
59
60
|
interrupt and return if respond_to?(:interrupt)
|
60
61
|
complete if respond_to?(:complete)
|
61
|
-
kill
|
62
62
|
exit 2
|
63
63
|
}
|
64
64
|
on(:start){
|
@@ -17,16 +17,17 @@ class Kronk
|
|
17
17
|
|
18
18
|
class ResultSet
|
19
19
|
|
20
|
-
attr_accessor :total_time
|
20
|
+
attr_accessor :total_time, :err_count
|
21
21
|
|
22
22
|
attr_reader :byterate, :count, :fastest, :precision,
|
23
|
-
:slowest, :total_bytes
|
23
|
+
:slowest, :total_bytes
|
24
24
|
|
25
25
|
def initialize
|
26
26
|
@times = Hash.new(0)
|
27
27
|
@count = 0
|
28
28
|
@r5XX = 0
|
29
29
|
@r4XX = 0
|
30
|
+
@r3XX = 0
|
30
31
|
@err_count = 0
|
31
32
|
|
32
33
|
@precision = 3
|
@@ -49,15 +50,18 @@ class Kronk
|
|
49
50
|
@times[time] += 1
|
50
51
|
@count += 1
|
51
52
|
|
52
|
-
|
53
|
-
@
|
53
|
+
case resp.code[0, 1]
|
54
|
+
when "5" then @r5XX += 1
|
55
|
+
when "4" then @r4XX += 1
|
56
|
+
when "3" then @r3XX += 1
|
57
|
+
end
|
54
58
|
|
55
59
|
@slowest = time if !@slowest || @slowest < time
|
56
60
|
@fastest = time if !@fastest || @fastest > time
|
57
61
|
|
58
62
|
log_req resp.request, time if resp.request
|
59
63
|
|
60
|
-
@total_bytes += resp.
|
64
|
+
@total_bytes += resp.total_bytes
|
61
65
|
|
62
66
|
@byterate = (@byterate * (@count-1) + resp.byterate) / @count
|
63
67
|
end
|
@@ -68,7 +72,6 @@ class Kronk
|
|
68
72
|
uri.query = nil
|
69
73
|
uri = "#{req.http_method} #{uri.to_s}"
|
70
74
|
|
71
|
-
# TODO: Keep the number in @paths to 10.
|
72
75
|
@paths[uri] ||= [0, 0]
|
73
76
|
pcount = @paths[uri][1] + 1
|
74
77
|
@paths[uri][0] = (@paths[uri][0] * @paths[uri][1] + time) / pcount
|
@@ -78,6 +81,7 @@ class Kronk
|
|
78
81
|
|
79
82
|
|
80
83
|
def deviation
|
84
|
+
return 0 if @count == 0
|
81
85
|
return @deviation if @deviation
|
82
86
|
|
83
87
|
mdiff = @times.to_a.inject(0) do |sum, (time, count)|
|
@@ -89,11 +93,13 @@ class Kronk
|
|
89
93
|
|
90
94
|
|
91
95
|
def mean
|
96
|
+
return 0 if @count == 0
|
92
97
|
@mean ||= (self.sum / @count).round @precision
|
93
98
|
end
|
94
99
|
|
95
100
|
|
96
101
|
def median
|
102
|
+
return 0 if @count == 0
|
97
103
|
@median ||= ((@slowest + @fastest) / 2).round @precision
|
98
104
|
end
|
99
105
|
|
@@ -127,6 +133,7 @@ class Kronk
|
|
127
133
|
|
128
134
|
|
129
135
|
def req_per_sec
|
136
|
+
return 0 if @count == 0
|
130
137
|
(@count / @total_time).round @precision
|
131
138
|
end
|
132
139
|
|
@@ -146,10 +153,23 @@ class Kronk
|
|
146
153
|
end
|
147
154
|
|
148
155
|
|
156
|
+
def clear_caches
|
157
|
+
@percentages = nil
|
158
|
+
@slowest_reqs = nil
|
159
|
+
@sum = nil
|
160
|
+
@mean = nil
|
161
|
+
@median = nil
|
162
|
+
@deviation = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
|
149
166
|
def to_s
|
167
|
+
clear_caches
|
168
|
+
|
150
169
|
out = <<-STR
|
151
170
|
|
152
171
|
Completed: #{@count}
|
172
|
+
300s: #{@r3XX}
|
153
173
|
400s: #{@r4XX}
|
154
174
|
500s: #{@r5XX}
|
155
175
|
Errors: #{@err_count}
|
@@ -158,11 +178,11 @@ Total Bytes: #{@total_bytes}
|
|
158
178
|
Transfer Rate: #{self.transfer_rate} Kbytes/sec
|
159
179
|
|
160
180
|
Connection Times (ms)
|
161
|
-
Min: #{self.fastest}
|
181
|
+
Min: #{self.fastest || 0}
|
162
182
|
Mean: #{self.mean}
|
163
183
|
[+/-sd]: #{self.deviation}
|
164
184
|
Median: #{self.median}
|
165
|
-
Max: #{self.slowest}
|
185
|
+
Max: #{self.slowest || 0}
|
166
186
|
|
167
187
|
Request Percentages (ms)
|
168
188
|
50% #{self.percentages[50]}
|
@@ -188,11 +208,15 @@ Avg. Slowest Requests (ms, count)
|
|
188
208
|
|
189
209
|
|
190
210
|
def start
|
191
|
-
@
|
192
|
-
@
|
193
|
-
@
|
194
|
-
@div
|
195
|
-
|
211
|
+
@interactive = $stdout.isatty
|
212
|
+
@res_count = 0
|
213
|
+
@results = [ResultSet.new]
|
214
|
+
@div = @number / 10 if @number
|
215
|
+
@div = 100 if !@div || @div < 10
|
216
|
+
@last_print = Time.now
|
217
|
+
@line_count = 0
|
218
|
+
|
219
|
+
puts "Benchmarking..." unless @interactive
|
196
220
|
end
|
197
221
|
|
198
222
|
|
@@ -204,11 +228,21 @@ Avg. Slowest Requests (ms, count)
|
|
204
228
|
end
|
205
229
|
|
206
230
|
@res_count += 1
|
207
|
-
|
231
|
+
|
232
|
+
if @interactive
|
233
|
+
render if Time.now - @last_print > 0.5
|
234
|
+
else
|
235
|
+
puts "#{@res_count} requests" if @res_count % @div == 0
|
236
|
+
end
|
208
237
|
end
|
209
238
|
end
|
210
239
|
|
211
240
|
|
241
|
+
def clear_screen
|
242
|
+
$stdout.print "\e[2K\e[1A" * @line_count
|
243
|
+
end
|
244
|
+
|
245
|
+
|
212
246
|
def error err, kronk
|
213
247
|
@mutex.synchronize do
|
214
248
|
@res_count += 1
|
@@ -220,32 +254,43 @@ Avg. Slowest Requests (ms, count)
|
|
220
254
|
|
221
255
|
|
222
256
|
def complete
|
223
|
-
puts "Finished!"
|
224
|
-
|
225
|
-
render_head
|
226
|
-
render_body
|
257
|
+
puts "Finished!" unless @interactive
|
227
258
|
|
259
|
+
render
|
228
260
|
true
|
229
261
|
end
|
230
262
|
|
231
263
|
|
232
|
-
def
|
233
|
-
|
264
|
+
def render
|
265
|
+
clear_screen if @interactive
|
266
|
+
out = "#{head}#{body}\n"
|
267
|
+
@line_count = out.to_s.split("\n").length
|
268
|
+
@last_print = Time.now
|
269
|
+
$stdout.print out
|
270
|
+
$stdout.flush
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
def body
|
275
|
+
@results.each{|res| res.total_time = Time.now - @start_time }
|
234
276
|
|
235
277
|
if @results.length > 1
|
236
|
-
|
278
|
+
Diff.new(@results[0].to_s, @results[1].to_s, :context => false).
|
279
|
+
formatted
|
237
280
|
else
|
238
|
-
|
281
|
+
@results.first.to_s
|
239
282
|
end
|
240
283
|
end
|
241
284
|
|
242
285
|
|
243
|
-
def
|
244
|
-
|
286
|
+
def head
|
287
|
+
<<-STR
|
245
288
|
|
246
289
|
Benchmark Time: #{(Time.now - @start_time).to_f} sec
|
247
290
|
Number of Requests: #{@count}
|
248
291
|
Concurrency: #{@qps ? "#{@qps} qps" : @concurrency}
|
292
|
+
#{"Current Connections: #{Kronk::HTTP.conn_count}\n" if @interactive}\
|
293
|
+
Total Connections: #{Kronk::HTTP.total_conn}
|
249
294
|
STR
|
250
295
|
end
|
251
296
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Kronk
|
2
|
+
|
3
|
+
##
|
4
|
+
# Outputs Player results as a list of files.
|
5
|
+
#
|
6
|
+
# player = Player::Download.new :dir => "foo/bar"
|
7
|
+
#
|
8
|
+
# Note: This output class will not render errors.
|
9
|
+
|
10
|
+
class Player::Download < Player
|
11
|
+
|
12
|
+
# Directory to write the files to.
|
13
|
+
attr_accessor :dir
|
14
|
+
|
15
|
+
def initialize opts={}
|
16
|
+
super
|
17
|
+
|
18
|
+
@counter = 0
|
19
|
+
@counter_mutex = Mutex.new
|
20
|
+
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
default_dir = File.join Dir.pwd, "kronk-#{Time.now.to_i}"
|
24
|
+
@dir = File.expand_path(opts[:dir] || default_dir)
|
25
|
+
|
26
|
+
FileUtils.mkdir_p @dir
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def result kronk
|
31
|
+
output, name, ext =
|
32
|
+
if kronk.diff
|
33
|
+
name = make_name kronk.responses[0].uri
|
34
|
+
name = "#{name}-#{make_name kronk.responses[1].uri}"
|
35
|
+
[kronk.diff.formatted, name, "diff"]
|
36
|
+
|
37
|
+
elsif kronk.response
|
38
|
+
name = make_name kronk.response.uri
|
39
|
+
[kronk.response.stringify, name, ext_for(kronk.response)]
|
40
|
+
end
|
41
|
+
|
42
|
+
return unless output && !output.empty?
|
43
|
+
|
44
|
+
filename = nil
|
45
|
+
|
46
|
+
@counter_mutex.synchronize do
|
47
|
+
@counter += 1
|
48
|
+
filename = File.join(@dir, "#{@counter}-#{name}.#{ext}")
|
49
|
+
end
|
50
|
+
|
51
|
+
File.open(filename, "w"){|file| file.write output}
|
52
|
+
|
53
|
+
@mutex.synchronize do
|
54
|
+
$stdout.puts filename
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def make_name uri
|
60
|
+
return unless uri
|
61
|
+
parts = uri.path.to_s.sub(%r{^/}, "").split("/")
|
62
|
+
parts = parts[-2..-1] || parts
|
63
|
+
parts.join("-").sub(%r{[?.].*$}, "")
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def ext_for resp
|
68
|
+
if will_parse(resp)
|
69
|
+
Kronk.config[:render_lang].to_s == "ruby" ? "rb" : "json"
|
70
|
+
|
71
|
+
elsif resp.stringify_opts[:show_headers]
|
72
|
+
"http"
|
73
|
+
|
74
|
+
else
|
75
|
+
resp.ext
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def will_parse resp
|
81
|
+
(resp.parser || resp.stringify_opts[:parser] ||
|
82
|
+
(resp.stringify_opts[:no_body] && resp.stringify_opts[:show_headers])) &&
|
83
|
+
!resp.stringify_opts[:raw]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|