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.
@@ -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
@@ -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, :err_count
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
- @r5XX += 1 if resp.code =~ /^5\d\d$/
53
- @r4XX += 1 if resp.code =~ /^4\d\d$/
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.raw.bytes.count
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
- @res_count = 0
192
- @results = []
193
- @div = @number / 10 if @number
194
- @div = 100 if !@div || @div < 10
195
- puts "Benchmarking..."
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
- puts "#{@res_count} requests" if @res_count % @div == 0
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 render_body
233
- @results.each{|res| res.total_time = @stop_time - @start_time }
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
- puts Diff.new(@results[0].to_s, @results[1].to_s).formatted
278
+ Diff.new(@results[0].to_s, @results[1].to_s, :context => false).
279
+ formatted
237
280
  else
238
- puts @results.first.to_s
281
+ @results.first.to_s
239
282
  end
240
283
  end
241
284
 
242
285
 
243
- def render_head
244
- puts <<-STR
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
+