rwb 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS +29 -0
- data/README +75 -0
- data/TODO +15 -0
- data/lib/rwb.rb +493 -0
- data/test/test_rwb.rb +362 -0
- metadata +41 -0
data/NEWS
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
version 0.2.1
|
2
|
+
+ added quartile graphs by url or overall
|
3
|
+
+ added more information to response objects
|
4
|
+
+ released as a gem w00t!
|
5
|
+
|
6
|
+
Version 0.2.0
|
7
|
+
+ added proxy handling throughout
|
8
|
+
|
9
|
+
Version 0.1.1
|
10
|
+
Changes in this version:
|
11
|
+
+ added three kinds of warmup methods
|
12
|
+
|
13
|
+
Version 0.1.0
|
14
|
+
Changes in this version:
|
15
|
+
+ starting to add documentation
|
16
|
+
+ added more granular control over reporting levels
|
17
|
+
+ fixed standard deviation
|
18
|
+
|
19
|
+
|
20
|
+
Version 0.0.1
|
21
|
+
Changes in this version:
|
22
|
+
+ made reporting more modular
|
23
|
+
+ added reporting based on slices of the results
|
24
|
+
+ added standard deviation for mean time per request
|
25
|
+
+ fixed standard deviation
|
26
|
+
|
27
|
+
Version 0.0.0
|
28
|
+
Changes in this version:
|
29
|
+
+ Initial Release
|
data/README
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
RWB, the Ruby Web Bench, provides a simple way to manage a performance and
|
2
|
+
load tests suite for your webserver. Unlike ab, by which it was inspired, it
|
3
|
+
will allow you to build a collection of test URLs with corresponding weights
|
4
|
+
which it will use to generate the actual tests that it runs (and reports on).
|
5
|
+
|
6
|
+
RWB could be used for:
|
7
|
+
- testing your website to ensure that it performs within timing
|
8
|
+
requirements
|
9
|
+
- testing a new webserver to verify that performance is at least as good
|
10
|
+
as your existing server
|
11
|
+
- testing a website to verify that it will handle expected load
|
12
|
+
|
13
|
+
While this is an alpha release, it is already useful. The API is expected to
|
14
|
+
change somewhat as functionality is added and the design is adjusted.
|
15
|
+
Reporting by error type, serializing of results, cookie handling, POST
|
16
|
+
requests, and set_up methods are some of the planned additions before rwb
|
17
|
+
hits a beta release.
|
18
|
+
|
19
|
+
A sample RWB script for testing a website looks like this (for more
|
20
|
+
information, see the file USING):
|
21
|
+
|
22
|
+
#!/usr/bin/env ruby
|
23
|
+
|
24
|
+
require 'rwb'
|
25
|
+
|
26
|
+
urls = RWB::Builder.new()
|
27
|
+
|
28
|
+
urls.add_url(10, "http://www.example.com")
|
29
|
+
urls.add_url(10, "http://www.example.com/nonesuch")
|
30
|
+
urls.add_url(70, "http://www.example.com/entries")
|
31
|
+
|
32
|
+
queries = ['foo+bar', 'bar+baz', 'quux']
|
33
|
+
urls.add_url_group(10, "http://www.example.com:3000/search?", queries)
|
34
|
+
|
35
|
+
tests = RWB::Runner.new(urls, 100, 20)
|
36
|
+
tests.report_header
|
37
|
+
tests.report_overall
|
38
|
+
tests.graph_quartiles_urls(1000)
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
The output from this script looks like this:
|
43
|
+
$ ruby -Ilib quick.rb
|
44
|
+
completed 10 runs
|
45
|
+
completed 20 runs
|
46
|
+
completed 30 runs
|
47
|
+
completed 40 runs
|
48
|
+
completed 50 runs
|
49
|
+
completed 60 runs
|
50
|
+
completed 70 runs
|
51
|
+
completed 80 runs
|
52
|
+
completed 90 runs
|
53
|
+
completed 100 runs
|
54
|
+
Concurrency Level: 20
|
55
|
+
Total Requests: 100
|
56
|
+
Total time for testing: 2.021048 secs
|
57
|
+
Requests per second: 49.4792800566835
|
58
|
+
Mean time per request: 236 msecs
|
59
|
+
Standard deviation: 342
|
60
|
+
Overall results:
|
61
|
+
Shortest time: 30 msecs
|
62
|
+
25.0%ile time: 74 msecs
|
63
|
+
50.0%ile time: 98 msecs
|
64
|
+
75.0%ile time: 185 msecs
|
65
|
+
Longest time: 1281 msecs
|
66
|
+
http://localhost:
|
67
|
+
0: +-| :1000
|
68
|
+
http://localhost/nonesuch:
|
69
|
+
0: [|+----| :1000
|
70
|
+
http://localhost/entries:
|
71
|
+
0:[ |+--| :1000
|
72
|
+
http://localhost:3000/search?:
|
73
|
+
0: |------+--------------------------------------:1000
|
74
|
+
|
75
|
+
(Additional examples of use can be seen at www.red-bean.com/~pate/
|
data/TODO
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Enhance docs & tests (in progress)
|
2
|
+
|
3
|
+
Figure out and implement cookie handling
|
4
|
+
|
5
|
+
Add POST capabilities
|
6
|
+
|
7
|
+
Enhance reporting to use expanded info in Response Objects (in progress)
|
8
|
+
|
9
|
+
Migrate to a DSL
|
10
|
+
|
11
|
+
(maybe) Add html output
|
12
|
+
|
13
|
+
(maybe) Add PDF output
|
14
|
+
|
15
|
+
|
data/lib/rwb.rb
ADDED
@@ -0,0 +1,493 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module RWB
|
5
|
+
|
6
|
+
#
|
7
|
+
# The Url class holds individual URLs for testing. +Url+s are defined by a
|
8
|
+
# Fixnum representing the weight and a String representing the URL itself.
|
9
|
+
# The weights are not required to be percentages.
|
10
|
+
#
|
11
|
+
# url = RWB::Url.new(20, "http://www.example.com")
|
12
|
+
class Url
|
13
|
+
attr_reader :weight
|
14
|
+
|
15
|
+
def initialize(weight, url)
|
16
|
+
@weight = weight
|
17
|
+
@url = url
|
18
|
+
end
|
19
|
+
|
20
|
+
# to_url returns the URL to be requested as a string
|
21
|
+
#
|
22
|
+
# url.to_url # => "http://www.example.com"
|
23
|
+
def to_url
|
24
|
+
@url
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# to_base returns a string suitable for creating a +Regexp+ to match
|
29
|
+
# against.
|
30
|
+
#
|
31
|
+
# url.to_base # => "http://www.example.com "
|
32
|
+
#
|
33
|
+
def to_base
|
34
|
+
@url
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
@weight + " " + @url
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# A UrlGroup is a collection of related URLs, for example the URL for a
|
44
|
+
# search tool, with several search queries. The group is weighted (and
|
45
|
+
# reported on) as a whole, but individual requests are made with random
|
46
|
+
# elements from an array of extensions.
|
47
|
+
#
|
48
|
+
# urls = UrlGroup.new(20, "http://www.example.com/search?",
|
49
|
+
# ["foo", "bar", "baz"])
|
50
|
+
#
|
51
|
+
class UrlGroup
|
52
|
+
attr_reader :weight
|
53
|
+
|
54
|
+
def initialize(weight, base, extension_array)
|
55
|
+
@weight = weight.to_i
|
56
|
+
@base = base.to_s
|
57
|
+
@extension_array = extension_array
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# to_url returns a complete URL to be requested. It takes an optional
|
62
|
+
# seed argument, which must be a Fixnum. If given, this will seed the
|
63
|
+
# random selection of the extension.
|
64
|
+
#
|
65
|
+
# urls.to_url(1234) # => "http://www.example.com/search?baz"
|
66
|
+
#
|
67
|
+
def to_url(seed = nil)
|
68
|
+
if seed
|
69
|
+
srand = seed
|
70
|
+
end
|
71
|
+
@base + @extension_array[rand(@extension_array.length)]
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# to_base returns a String suitable for building a Regex to match
|
76
|
+
# against. This String will not include any extensions.
|
77
|
+
#
|
78
|
+
# urls.to_base # => "http://www.example.com/search?"
|
79
|
+
#
|
80
|
+
def to_base
|
81
|
+
@base
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
@weight + " " + @base
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
class Result
|
91
|
+
attr_reader :id, :timestamp, :url
|
92
|
+
attr_reader :elapsed_time, :response_code
|
93
|
+
|
94
|
+
def initialize(id, timestamp, url, elapsed_time, response_code)
|
95
|
+
@id = id
|
96
|
+
@timestamp = timestamp
|
97
|
+
@url = url
|
98
|
+
@elapsed_time = elapsed_time
|
99
|
+
@response_code = response_code
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class RunResults
|
104
|
+
attr_reader :results
|
105
|
+
|
106
|
+
def initialize
|
107
|
+
@results = Array.new
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_result(result)
|
111
|
+
@results.push(result)
|
112
|
+
end
|
113
|
+
|
114
|
+
def each_by_response_code(code)
|
115
|
+
@results.each do |result|
|
116
|
+
yield result if result.response_code.to_i == code.to_i
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def all_by_response_code(code)
|
121
|
+
coded_results = Array.new
|
122
|
+
@results.each do |result|
|
123
|
+
if result.response_code.to_i == code.to_i
|
124
|
+
coded_results.push(result)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
coded_results
|
128
|
+
end
|
129
|
+
|
130
|
+
def all_by_url(url)
|
131
|
+
coded_results = Array.new
|
132
|
+
@results.each do |result|
|
133
|
+
if result.url.to_s == url.to_s
|
134
|
+
coded_results.push(result)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
coded_results
|
138
|
+
end
|
139
|
+
|
140
|
+
def each_by_url(url)
|
141
|
+
@results.each do |result|
|
142
|
+
yield result if result.url.to_s == url.to_s
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def all_with_id
|
147
|
+
id_results = Hash.new
|
148
|
+
@results.each do |result|
|
149
|
+
id_results[result.id] = result
|
150
|
+
end
|
151
|
+
id_results
|
152
|
+
end
|
153
|
+
|
154
|
+
def times_by_url(url = nil)
|
155
|
+
coded_results = Array.new
|
156
|
+
@results.each do |result|
|
157
|
+
if url
|
158
|
+
if result.url.to_s == url.to_s
|
159
|
+
coded_results.push(result.elapsed_time)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
coded_results.push(result.elapsed_time)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
coded_results
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
class Builder
|
171
|
+
attr_reader :urls, :total_weight
|
172
|
+
|
173
|
+
def initialize(testhash = nil)
|
174
|
+
@urls = Hash.new
|
175
|
+
@total_weight = 0
|
176
|
+
if testhash
|
177
|
+
build_urls(testhash)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_url(weight, url)
|
182
|
+
@total_weight += weight.to_i
|
183
|
+
@urls[@total_weight] = Url.new(weight, url)
|
184
|
+
end
|
185
|
+
|
186
|
+
def add_url_group(weight, base, extension_array)
|
187
|
+
@total_weight += weight.to_i
|
188
|
+
@urls[@total_weight] = UrlGroup.new(weight, base, extension_array)
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_url(seed = nil)
|
192
|
+
if seed
|
193
|
+
srand seed
|
194
|
+
end
|
195
|
+
pick = rand(@total_weight)
|
196
|
+
@urls.keys.sort.each do |key|
|
197
|
+
if pick <= key
|
198
|
+
return @urls[key]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def build_urls(tests)
|
204
|
+
tests.keys.each do |key|
|
205
|
+
add_url(tests[key], key)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Runner
|
211
|
+
attr_accessor :sla_levels
|
212
|
+
|
213
|
+
def initialize(urls, max_runs=100, max_threads=10)
|
214
|
+
@urls = urls
|
215
|
+
@current_runs = 0
|
216
|
+
@current_threads = 0
|
217
|
+
@max_runs = max_runs
|
218
|
+
@max_threads = max_threads
|
219
|
+
@threads = []
|
220
|
+
@results_mean = nil
|
221
|
+
@results_std_dev = nil
|
222
|
+
@sla_levels = [0.5, 0.9]
|
223
|
+
@http = Net::HTTP
|
224
|
+
@new_results = RunResults.new
|
225
|
+
end
|
226
|
+
|
227
|
+
def add_proxy(proxy_addr, proxy_port = 80, proxy_user = nil,
|
228
|
+
proxy_pass = nil)
|
229
|
+
@http = Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user,
|
230
|
+
proxy_pass)
|
231
|
+
end
|
232
|
+
|
233
|
+
def warmup(num_runs = 1)
|
234
|
+
$stderr.puts "warming up with #{num_runs} runs"
|
235
|
+
for run in 1..num_runs
|
236
|
+
$stderr.print "#{run} "
|
237
|
+
@urls.urls.values.each do |url|
|
238
|
+
url = URI.parse(url.to_url)
|
239
|
+
@http.start(url.host, url.port) do |http|
|
240
|
+
http.request( Net::HTTP::Get.new(url) )
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
$stderr.puts
|
245
|
+
end
|
246
|
+
|
247
|
+
def rand_warmup(num_requests)
|
248
|
+
$stderr.puts "warming up with #{num_requests} requests"
|
249
|
+
checkpoint = num_requests/10
|
250
|
+
for run in 1..num_requests
|
251
|
+
if run % checkpoint == 0
|
252
|
+
$stderr.print "#{run} "
|
253
|
+
end
|
254
|
+
url = URI.parse(@urls.get_url.to_url)
|
255
|
+
@http.start(url.host, url.port) do |http|
|
256
|
+
http.request( Net::HTTP::Get.new(url) )
|
257
|
+
end
|
258
|
+
end
|
259
|
+
$stderr.puts
|
260
|
+
end
|
261
|
+
|
262
|
+
def spec_warmup(urls, num_runs=1)
|
263
|
+
$stderr.puts "warming up with #{num_runs} runs"
|
264
|
+
for run in 1..num_runs
|
265
|
+
$stderr.print "#{run} "
|
266
|
+
urls.each do |url|
|
267
|
+
url = URI.parse(url)
|
268
|
+
@http.start(url.host, url.port) do |http|
|
269
|
+
http.request( Net::HTTP::Get.new(url) )
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
$stderr.puts
|
274
|
+
end
|
275
|
+
|
276
|
+
def run
|
277
|
+
printed_runs = Array.new
|
278
|
+
total_start_time = Time.now
|
279
|
+
checkpoint = @max_runs/10
|
280
|
+
while @current_runs < @max_runs
|
281
|
+
build_thread
|
282
|
+
if @current_runs % checkpoint == 0 &&
|
283
|
+
! ( printed_runs.include?(@current_runs))
|
284
|
+
$stderr.puts "completed #{@current_runs} runs"
|
285
|
+
printed_runs.push(@current_runs)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
@threads.each { |th| th.join }
|
290
|
+
|
291
|
+
total_stop_time = Time.now
|
292
|
+
@total_time = total_stop_time - total_start_time
|
293
|
+
end
|
294
|
+
|
295
|
+
def results_mean
|
296
|
+
if @results_mean
|
297
|
+
return @results_mean
|
298
|
+
end
|
299
|
+
@results_mean = @new_results.times_by_url.inject(0) do |sum, time|
|
300
|
+
sum += time
|
301
|
+
end
|
302
|
+
@results_mean = @results_mean/@new_results.times_by_url.length
|
303
|
+
make_milli(@results_mean)
|
304
|
+
end
|
305
|
+
|
306
|
+
def results_std_dev
|
307
|
+
if @results_std_dev
|
308
|
+
return @results_std_dev
|
309
|
+
end
|
310
|
+
unless @results_mean
|
311
|
+
results_mean
|
312
|
+
end
|
313
|
+
@results_std_dev = @new_results.times_by_url.inject(0) do |std_dev, time|
|
314
|
+
std_dev += (time - @results_mean) ** 2
|
315
|
+
end
|
316
|
+
@results_std_dev =
|
317
|
+
Math.sqrt(@results_std_dev/@new_results.times_by_url.length)
|
318
|
+
make_milli(@results_std_dev)
|
319
|
+
end
|
320
|
+
|
321
|
+
def adjust_scale(scale, max)
|
322
|
+
if scale/2 > max
|
323
|
+
scale = scale/2
|
324
|
+
scale = adjust_scale(scale, max)
|
325
|
+
end
|
326
|
+
scale
|
327
|
+
end
|
328
|
+
|
329
|
+
def results_quartile(times, scale = nil)
|
330
|
+
times = times.map { |t| make_milli(t) }
|
331
|
+
size = results_mean + results_std_dev * 2
|
332
|
+
min = times[0]
|
333
|
+
first = times[(times.length*0.25).to_int - 1]
|
334
|
+
second = times[(times.length*0.5).to_int - 1]
|
335
|
+
third = times[(times.length*0.75).to_int - 1]
|
336
|
+
max = times[-1]
|
337
|
+
|
338
|
+
len = max.to_s.length
|
339
|
+
unless scale
|
340
|
+
scale = (max/((10**len).to_f)).ceil * (10**len)
|
341
|
+
scale = adjust_scale(scale, max)
|
342
|
+
end
|
343
|
+
step = scale/50
|
344
|
+
step = 1 if step == 0
|
345
|
+
scale = 50 if scale < 50
|
346
|
+
line = Array.new
|
347
|
+
char = ' '
|
348
|
+
for i in 0..49 do
|
349
|
+
case i
|
350
|
+
when max/step
|
351
|
+
line[i] = ']'
|
352
|
+
when third/step
|
353
|
+
line[i] = '|'
|
354
|
+
char = ' '
|
355
|
+
when second/step
|
356
|
+
line[i] = '+'
|
357
|
+
when first/step
|
358
|
+
line[i] = '|'
|
359
|
+
char = '-'
|
360
|
+
when min/step
|
361
|
+
line[i] = '['
|
362
|
+
when 0
|
363
|
+
line[i] = ':'
|
364
|
+
when 49
|
365
|
+
line[i] = ':'
|
366
|
+
else
|
367
|
+
line[i] = char
|
368
|
+
end
|
369
|
+
end
|
370
|
+
return '0' + line.join('') + scale.to_s
|
371
|
+
end
|
372
|
+
|
373
|
+
def report_full(sla_levels = @sla_levels)
|
374
|
+
report_header
|
375
|
+
report_overall(sla_levels)
|
376
|
+
report_urls(sla_levels)
|
377
|
+
report_by_time(sla_levels)
|
378
|
+
end
|
379
|
+
|
380
|
+
def report_overall(sla_levels = @sla_levels)
|
381
|
+
results = @new_results.times_by_url
|
382
|
+
|
383
|
+
puts "Overall results:"
|
384
|
+
print_times(results.sort, sla_levels)
|
385
|
+
end
|
386
|
+
|
387
|
+
def report_urls(sla_levels = @sla_levels)
|
388
|
+
@urls.urls.keys.sort.each do |key|
|
389
|
+
url = @urls.urls[key].to_base
|
390
|
+
results = @new_results.times_by_url(url)
|
391
|
+
puts "Results for #{url}:"
|
392
|
+
if results.length > 0
|
393
|
+
print_times(results.sort, sla_levels)
|
394
|
+
else
|
395
|
+
puts "no results for url"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def graph_quartiles_urls(scale = nil)
|
401
|
+
@urls.urls.keys.sort.each do |key|
|
402
|
+
url = @urls.urls[key].to_base
|
403
|
+
results = @new_results.times_by_url(url)
|
404
|
+
puts "#{url}:\n\t" + results_quartile(results.sort, scale)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def graph_quartiles_overall
|
409
|
+
results = @new_results.times_by_url
|
410
|
+
puts results_quartile(results.sort)
|
411
|
+
end
|
412
|
+
|
413
|
+
def report_by_time(sla_levels = @sla_levels, granularity = 0.2)
|
414
|
+
start = 0.0
|
415
|
+
stop = granularity
|
416
|
+
results = @new_results.times_by_url
|
417
|
+
puts "Results by time:"
|
418
|
+
while stop <= 1.0
|
419
|
+
first = (results.length * start).to_i
|
420
|
+
last = (results.length * stop).to_i
|
421
|
+
these_results = results.slice(first..last)
|
422
|
+
puts "results for requests #{first} - #{last}"
|
423
|
+
print_times(these_results.sort, sla_levels)
|
424
|
+
start = stop
|
425
|
+
stop += granularity
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def print_times(results, levels)
|
430
|
+
times = get_times(results, levels)
|
431
|
+
puts "\tShortest time:\t#{times.shift} msecs"
|
432
|
+
levels.each_with_index do |num, index|
|
433
|
+
percent = num.to_f * 100.0
|
434
|
+
puts "\t#{percent}%ile time:\t#{times[index]} msecs"
|
435
|
+
end
|
436
|
+
puts "\tLongest time:\t#{times[-1]} msecs"
|
437
|
+
end
|
438
|
+
|
439
|
+
def get_times(results, levels)
|
440
|
+
times = Array.new
|
441
|
+
times.push(make_milli(results[0]))
|
442
|
+
levels.each do |num|
|
443
|
+
times.push(make_milli(results[(results.length*num).to_i]))
|
444
|
+
end
|
445
|
+
times.push(make_milli(results[-1]))
|
446
|
+
end
|
447
|
+
|
448
|
+
def make_milli(num)
|
449
|
+
(num * 1000).to_i
|
450
|
+
end
|
451
|
+
|
452
|
+
def report_header
|
453
|
+
print <<EOF
|
454
|
+
Concurrency Level: #{@max_threads}
|
455
|
+
Total Requests: #{@max_runs}
|
456
|
+
Total time for testing: #{@total_time} secs
|
457
|
+
Requests per second: #{@max_runs/@total_time}
|
458
|
+
Mean time per request: #{results_mean} msecs
|
459
|
+
Standard deviation: #{results_std_dev}
|
460
|
+
EOF
|
461
|
+
end
|
462
|
+
|
463
|
+
def build_thread
|
464
|
+
if @current_threads < @max_threads
|
465
|
+
@current_runs += 1
|
466
|
+
@threads << Thread.new { run_test(@current_runs)}
|
467
|
+
@current_threads += 1
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def run_test(id)
|
472
|
+
request = @urls.get_url
|
473
|
+
url = URI.parse request.to_url
|
474
|
+
base = request.to_base
|
475
|
+
start_time = Time.now
|
476
|
+
response_code = nil
|
477
|
+
|
478
|
+
@http.start(url.host, url.port) do |http|
|
479
|
+
response = http.request( Net::HTTP::Get.new(url) )
|
480
|
+
response_code = response.code
|
481
|
+
end
|
482
|
+
|
483
|
+
stop_time = Time.now
|
484
|
+
time = stop_time - start_time
|
485
|
+
|
486
|
+
result = Result.new(id, Time.now, base, time, response_code)
|
487
|
+
|
488
|
+
@new_results.add_result(result)
|
489
|
+
@current_threads -= 1
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
end
|
data/test/test_rwb.rb
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'test/unit' unless defined? $ZENTEST and $ZENTEST
|
2
|
+
require 'rwb'
|
3
|
+
|
4
|
+
module RWB
|
5
|
+
class Runner
|
6
|
+
attr_writer :results_mean, :results_std_dev
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module TestRWB
|
12
|
+
class TestBuilder < Test::Unit::TestCase
|
13
|
+
def setup
|
14
|
+
@urls = RWB::Builder.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_add_url
|
18
|
+
@urls.add_url(20, "http://localhost")
|
19
|
+
assert_equal(1, @urls.urls.length)
|
20
|
+
assert_equal("http://localhost", @urls.get_url.to_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_add_url_group
|
24
|
+
@urls.add_url_group(20, "http://localhost/search?", ['foo', 'bar'])
|
25
|
+
assert_equal(1, @urls.urls.length)
|
26
|
+
assert_instance_of(RWB::UrlGroup, @urls.get_url)
|
27
|
+
assert_equal(20, @urls.get_url.weight)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_build_urls
|
31
|
+
config = {
|
32
|
+
"http://localhost" => 20,
|
33
|
+
"http://localhost/nonesuch" => 20}
|
34
|
+
@urls.build_urls(config)
|
35
|
+
assert_equal(2, @urls.urls.length)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_get_url
|
39
|
+
@urls.add_url(20, "http://localhost")
|
40
|
+
assert_equal("http://localhost", @urls.get_url.to_url)
|
41
|
+
@urls.add_url(20, "http://localhost/foo")
|
42
|
+
assert_equal("http://localhost", @urls.get_url(1).to_url)
|
43
|
+
assert_equal("http://localhost", @urls.get_url(2).to_url)
|
44
|
+
assert_equal("http://localhost/foo", @urls.get_url(3).to_url)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_total_weight
|
48
|
+
@urls.add_url(20, "http://localhost")
|
49
|
+
assert_equal(20, @urls.total_weight)
|
50
|
+
@urls.add_url(20, "http://localhost/foo")
|
51
|
+
assert_equal(40, @urls.total_weight)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_urls
|
55
|
+
@urls.add_url(20, "http://localhost")
|
56
|
+
@urls.add_url(20, "http://localhost/foo")
|
57
|
+
assert_instance_of(Hash, @urls.urls)
|
58
|
+
assert_instance_of(RWB::Url, @urls.urls[20])
|
59
|
+
assert_equal([40, 20], @urls.urls.keys)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class TestRunner < Test::Unit::TestCase
|
64
|
+
def test_build_thread
|
65
|
+
#raise NotImplementedError, 'Need to write test_build_thread'
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_get_times
|
69
|
+
#raise NotImplementedError, 'Need to write test_get_times'
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_make_milli
|
73
|
+
tests = RWB::Runner.new(nil)
|
74
|
+
assert_equal(23, tests.make_milli(0.023))
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def test_results_quartile
|
79
|
+
times = [0.011, 0.134, 0.199, 0.212, 0.236, 0.275,
|
80
|
+
0.351, 0.401, 0.479, 0.603, 0.758, 0.978]
|
81
|
+
quartile_graph =
|
82
|
+
"0[ |---+---------| ]:1000"
|
83
|
+
test = RWB::Runner.new(nil)
|
84
|
+
test.results_mean = 450
|
85
|
+
test.results_std_dev = 200
|
86
|
+
assert_equal(quartile_graph, test.results_quartile(times))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_results_quartile_half_step
|
90
|
+
times = [0.011, 0.134, 0.199, 0.212, 0.236, 0.275,
|
91
|
+
0.322, 0.351, 0.379, 0.403, 0.458, 0.478]
|
92
|
+
quartile_graph =
|
93
|
+
"0:[ |-------+---------| ] :500"
|
94
|
+
test = RWB::Runner.new(nil)
|
95
|
+
test.results_mean = 450
|
96
|
+
test.results_std_dev = 200
|
97
|
+
assert_equal(quartile_graph, test.results_quartile(times))
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_results_quartile_mid_values
|
101
|
+
times = [0.011, 0.013, 0.019, 0.021, 0.023, 0.027,
|
102
|
+
0.035, 0.040, 0.047, 0.060, 0.075, 0.085]
|
103
|
+
quartile_graph =
|
104
|
+
"0: [ |---+---------| ] :100"
|
105
|
+
test = RWB::Runner.new(nil)
|
106
|
+
test.results_mean = 45
|
107
|
+
test.results_std_dev = 20
|
108
|
+
assert_equal(quartile_graph, test.results_quartile(times))
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_results_quartile_small_values
|
112
|
+
times = [0.001, 0.002, 0.003,0.004,0.005,0.006,
|
113
|
+
0.007,0.008,0.009]
|
114
|
+
quartile_graph =
|
115
|
+
"0:[|-+-| ] :50"
|
116
|
+
test = RWB::Runner.new(nil)
|
117
|
+
test.results_mean = 4
|
118
|
+
test.results_std_dev = 2
|
119
|
+
assert_equal(quartile_graph, test.results_quartile(times))
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_results_quartile_set_scale
|
123
|
+
times = [0.001, 0.002, 0.003,0.004,0.005,0.006,
|
124
|
+
0.007,0.008,0.009]
|
125
|
+
quartile_graph =
|
126
|
+
"0] :1000"
|
127
|
+
test = RWB::Runner.new(nil)
|
128
|
+
test.results_mean = 4
|
129
|
+
test.results_std_dev = 2
|
130
|
+
assert_equal(quartile_graph, test.results_quartile(times, 1000))
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_adjust_scale
|
134
|
+
test = RWB::Runner.new(nil)
|
135
|
+
assert_equal(25, test.adjust_scale(100,15))
|
136
|
+
assert_equal(50, test.adjust_scale(100,49))
|
137
|
+
assert_equal(100, test.adjust_scale(100,75))
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_print_times
|
141
|
+
#raise NotImplementedError, 'Need to write test_print_times'
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_report_by_time
|
145
|
+
#raise NotImplementedError, 'Need to write test_report_by_time'
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_report_full
|
149
|
+
#raise NotImplementedError, 'Need to write test_report_full'
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_report_header
|
153
|
+
#raise NotImplementedError, 'Need to write test_report_header'
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_report_overall
|
157
|
+
#raise NotImplementedError, 'Need to write test_report_overall'
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_report_urls
|
161
|
+
#raise NotImplementedError, 'Need to write test_report_urls'
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_run_test
|
165
|
+
# not testing yet ...
|
166
|
+
#raise NotImplementedError, 'Need to write test_run_test'
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
class TestUrl < Test::Unit::TestCase
|
172
|
+
def setup
|
173
|
+
@url = RWB::Url.new(10, "http://www.example.com")
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_to_base
|
177
|
+
assert_equal("http://www.example.com", @url.to_base)
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_to_url
|
181
|
+
assert_equal("http://www.example.com", @url.to_url)
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_weight
|
185
|
+
assert_equal(10, @url.weight)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class TestUrlGroup < Test::Unit::TestCase
|
190
|
+
def setup
|
191
|
+
queries = ["foo", "bar", "baz"]
|
192
|
+
@url = RWB::UrlGroup.new(10, "http://www.example.com/search?", queries)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_to_base
|
196
|
+
assert_equal("http://www.example.com/search?", @url.to_base)
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_to_url
|
200
|
+
assert_equal("http://www.example.com/search?foo", @url.to_url(1))
|
201
|
+
assert_equal("http://www.example.com/search?baz", @url.to_url(2))
|
202
|
+
assert_equal("http://www.example.com/search?baz", @url.to_url(3))
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_weight
|
206
|
+
assert_equal(10, @url.weight)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class TestResult < Test::Unit::TestCase
|
211
|
+
def setup
|
212
|
+
@now = Time.now
|
213
|
+
@result = RWB::Result.new(1, @now, "http://localhost", 0.234, 200)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_id
|
217
|
+
assert_equal 1, @result.id
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_timestamp
|
221
|
+
assert_instance_of(Time, @result.timestamp)
|
222
|
+
assert_equal(@now, @result.timestamp)
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_url
|
226
|
+
assert_equal("http://localhost", @result.url)
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_elapsed_time
|
230
|
+
assert_equal(0.234, @result.elapsed_time)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_response_code
|
234
|
+
assert_equal(200, @result.response_code)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
class TestRunResults < Test::Unit::TestCase
|
239
|
+
def setup
|
240
|
+
@results = RWB::RunResults.new
|
241
|
+
@now = Time.now
|
242
|
+
@result = RWB::Result.new(1, @now, "http://localhost", 0.234, 200)
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_results
|
246
|
+
assert_instance_of(Array, @results.results)
|
247
|
+
assert_equal(0, @results.results.length)
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_add_result
|
251
|
+
@results.add_result(@result)
|
252
|
+
assert_equal(1, @results.results.length)
|
253
|
+
assert_instance_of(RWB::Result, @results.results[0])
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_all_by_response_code
|
257
|
+
@results.add_result(@result)
|
258
|
+
assert_instance_of(Array, @results.all_by_response_code(200))
|
259
|
+
assert_equal(1, @results.all_by_response_code(200).length)
|
260
|
+
assert_equal(0, @results.all_by_response_code(404).length)
|
261
|
+
result = RWB::Result.new(2, Time.now,
|
262
|
+
"http://localhost/nonesuch",
|
263
|
+
0.118, 404)
|
264
|
+
@results.add_result(result)
|
265
|
+
third = RWB::Result.new(3, Time.now,
|
266
|
+
"http://localhost",
|
267
|
+
0.118, 200)
|
268
|
+
@results.add_result(third)
|
269
|
+
assert_equal(2, @results.all_by_response_code(200).length)
|
270
|
+
assert_equal(1, @results.all_by_response_code(404).length)
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_each_by_response_code
|
274
|
+
@results.add_result(@result)
|
275
|
+
result = RWB::Result.new(2, Time.now,
|
276
|
+
"http://localhost/nonesuch",
|
277
|
+
0.118, 404)
|
278
|
+
@results.add_result(result)
|
279
|
+
third = RWB::Result.new(3, Time.now,
|
280
|
+
"http://localhost",
|
281
|
+
0.118, 200)
|
282
|
+
@results.add_result(third)
|
283
|
+
@results.each_by_response_code(200) do |result|
|
284
|
+
assert_equal(200, result.response_code)
|
285
|
+
end
|
286
|
+
|
287
|
+
@results.each_by_response_code(404) do |result|
|
288
|
+
assert_equal(404, result.response_code)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_all_by_url
|
293
|
+
@results.add_result(@result)
|
294
|
+
assert_instance_of(Array, @results.all_by_url("http://localhost"))
|
295
|
+
assert_equal(1, @results.all_by_url("http://localhost").length)
|
296
|
+
result = RWB::Result.new(2, Time.now,
|
297
|
+
"http://localhost/nonesuch",
|
298
|
+
0.118, 404)
|
299
|
+
@results.add_result(result)
|
300
|
+
third = RWB::Result.new(3, Time.now,
|
301
|
+
"http://localhost",
|
302
|
+
0.118, 200)
|
303
|
+
@results.add_result(third)
|
304
|
+
assert_equal(2, @results.all_by_url("http://localhost").length)
|
305
|
+
assert_equal(1, @results.all_by_url("http://localhost/nonesuch").length)
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_each_by_url
|
309
|
+
@results.add_result(@result)
|
310
|
+
result = RWB::Result.new(2, Time.now,
|
311
|
+
"http://localhost/nonesuch",
|
312
|
+
0.118, 404)
|
313
|
+
@results.add_result(result)
|
314
|
+
third = RWB::Result.new(3, Time.now,
|
315
|
+
"http://localhost",
|
316
|
+
0.118, 200)
|
317
|
+
@results.add_result(third)
|
318
|
+
@results.each_by_url("http://localhost") do |result|
|
319
|
+
assert_equal("http://localhost", result.url)
|
320
|
+
end
|
321
|
+
|
322
|
+
@results.each_by_url("http://localhost/nonesuch") do |result|
|
323
|
+
assert_equal("http://localhost/nonesuch", result.url)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_all_with_id
|
328
|
+
@results.add_result(@result)
|
329
|
+
assert_instance_of(Hash, @results.all_with_id)
|
330
|
+
assert_equal(1, @results.all_with_id.keys.length)
|
331
|
+
result = RWB::Result.new(2, Time.now,
|
332
|
+
"http://localhost/nonesuch",
|
333
|
+
0.118, 404)
|
334
|
+
@results.add_result(result)
|
335
|
+
third = RWB::Result.new(3, Time.now,
|
336
|
+
"http://localhost",
|
337
|
+
0.118, 200)
|
338
|
+
@results.add_result(third)
|
339
|
+
assert_equal([1,2,3], @results.all_with_id.keys)
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_times_by_url
|
343
|
+
@results.add_result(@result)
|
344
|
+
assert_instance_of(Array, @results.times_by_url("http://localhost"))
|
345
|
+
assert_equal(1, @results.times_by_url("http://localhost").length)
|
346
|
+
result = RWB::Result.new(2, Time.now,
|
347
|
+
"http://localhost/nonesuch",
|
348
|
+
0.118, 404)
|
349
|
+
@results.add_result(result)
|
350
|
+
third = RWB::Result.new(3, Time.now,
|
351
|
+
"http://localhost",
|
352
|
+
0.118, 200)
|
353
|
+
@results.add_result(third)
|
354
|
+
assert_equal([0.234, 0.118], @results.times_by_url("http://localhost"))
|
355
|
+
assert_equal([0.118], @results.times_by_url("http://localhost/nonesuch"))
|
356
|
+
assert_equal([0.234, 0.118, 0.118], @results.times_by_url)
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
metadata
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: rwb
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.2.1
|
7
|
+
date: 2005-12-02
|
8
|
+
summary: "Ruby Web Bench, a web performance and load testing framework"
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: pat.eyler@gmail.com
|
12
|
+
homepage: http://rubyforge.org/projects/rwb
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: rwb
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: "true"
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors:
|
28
|
+
- Pat Eyler
|
29
|
+
files:
|
30
|
+
- NEWS
|
31
|
+
- README
|
32
|
+
- TODO
|
33
|
+
- lib/rwb.rb
|
34
|
+
- test/test_rwb.rb
|
35
|
+
test_files: []
|
36
|
+
rdoc_options: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
requirements: []
|
41
|
+
dependencies: []
|