rwb 0.2.1
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/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: []
|