kronk 1.8.7 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|