rwb 0.2.2 → 0.2.3
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/Rakefile +62 -0
- data/lib/rwb/report.rb +100 -0
- data/lib/rwb/results.rb +164 -0
- data/lib/rwb/url.rb +126 -0
- data/lib/rwb/warmup.rb +48 -0
- metadata +10 -4
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rubygems'
|
7
|
+
|
8
|
+
|
9
|
+
Rake::RDocTask.new do |rd|
|
10
|
+
rd.main = "readme"
|
11
|
+
rd.rdoc_files.include("readme", "lib/**/*.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::TestTask.new(:unittests) do |t|
|
15
|
+
t.test_files = FileList['test/test*.rb']
|
16
|
+
t.warning = true
|
17
|
+
t.verbose = false
|
18
|
+
end
|
19
|
+
|
20
|
+
task :test => [:unittests]
|
21
|
+
|
22
|
+
spec = Gem::Specification.new do |s|
|
23
|
+
s.name = 'rwb'
|
24
|
+
s.version = "0.2.3"
|
25
|
+
s.author = "Pat Eyler"
|
26
|
+
s.email = "pat.eyler@gmail.com"
|
27
|
+
s.homepage = "http://rubyforge.org/projects/rwb"
|
28
|
+
s.platform = Gem::Platform::RUBY
|
29
|
+
s.summary = "Ruby Web Bench, a web performance and load testing framework"
|
30
|
+
s.files = Dir.glob("{test,lib,bin}/**/*").delete_if {|item|
|
31
|
+
item.include?("CVS") || item.include?("rdoc")}
|
32
|
+
s.files += ['NEWS', 'README', 'TODO', 'Rakefile']
|
33
|
+
s.require_path = 'lib'
|
34
|
+
s.autorequire = 'rwb'
|
35
|
+
s.has_rdoc = 'true'
|
36
|
+
end
|
37
|
+
|
38
|
+
if $0==__FILE__
|
39
|
+
Gem::manage_gems
|
40
|
+
Gem::Builder.new(spec).build
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
44
|
+
pkg.need_zip = true
|
45
|
+
pkg.need_tar = true
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
desc "Perform a cvs update"
|
50
|
+
# relies on ssh keys being set up correctly
|
51
|
+
task :update do
|
52
|
+
sh "cvs update"
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Perform a cvs commit after running unit tests and
|
56
|
+
updating"
|
57
|
+
# relies on ssh keys being set up correctly
|
58
|
+
task :commit => [:update, :unittests] do |t|
|
59
|
+
msg = "commit from Rake:\n"
|
60
|
+
msg << ENV['MSG']
|
61
|
+
sh "cvs commit -m '#{msg}'"
|
62
|
+
end
|
data/lib/rwb/report.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module RWB
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
def report_full(sla_levels = @sla_levels)
|
5
|
+
report_header
|
6
|
+
report_overall(sla_levels)
|
7
|
+
report_urls(sla_levels)
|
8
|
+
report_by_time(sla_levels)
|
9
|
+
end
|
10
|
+
|
11
|
+
def report_overall(sla_levels = @sla_levels)
|
12
|
+
results = @new_results.times_by_url
|
13
|
+
|
14
|
+
puts "Overall results:"
|
15
|
+
print_times(results.sort, sla_levels)
|
16
|
+
end
|
17
|
+
|
18
|
+
def report_urls(sla_levels = @sla_levels)
|
19
|
+
@urls.urls.keys.sort.each do |key|
|
20
|
+
url = @urls.urls[key].to_base
|
21
|
+
results = @new_results.times_by_url(url)
|
22
|
+
puts "Results for #{url}:"
|
23
|
+
if results.length > 0
|
24
|
+
print_times(results.sort, sla_levels)
|
25
|
+
else
|
26
|
+
puts "no results for url"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def graph_quartiles_urls(scale = nil)
|
32
|
+
@urls.urls.keys.sort.each do |key|
|
33
|
+
url = @urls.urls[key].to_base
|
34
|
+
results = @new_results.times_by_url(url)
|
35
|
+
puts "#{url}:\n\t" + results_quartile(results.sort, scale)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def graph_quartiles_overall
|
40
|
+
results = @new_results.times_by_url
|
41
|
+
puts results_quartile(results.sort)
|
42
|
+
end
|
43
|
+
|
44
|
+
def report_by_time(sla_levels = @sla_levels, granularity = 0.2)
|
45
|
+
start = 0.0
|
46
|
+
stop = granularity
|
47
|
+
results = @new_results.times_by_url
|
48
|
+
puts "Results by time:"
|
49
|
+
while stop <= 1.0
|
50
|
+
first = (results.length * start).to_i
|
51
|
+
last = (results.length * stop).to_i
|
52
|
+
these_results = results.slice(first..last)
|
53
|
+
puts "results for requests #{first} - #{last}"
|
54
|
+
print_times(these_results.sort, sla_levels)
|
55
|
+
start = stop
|
56
|
+
stop += granularity
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_times(results, levels)
|
61
|
+
times = get_times(results, levels)
|
62
|
+
puts "\tShortest time:\t#{times.shift} msecs"
|
63
|
+
levels.each_with_index do |num, index|
|
64
|
+
percent = num.to_f * 100.0
|
65
|
+
puts "\t#{percent}%ile time:\t#{times[index]} msecs"
|
66
|
+
end
|
67
|
+
puts "\tLongest time:\t#{times[-1]} msecs"
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_times(results, levels)
|
71
|
+
times = Array.new
|
72
|
+
times.push(make_milli(results[0]))
|
73
|
+
levels.each do |num|
|
74
|
+
times.push(make_milli(results[(results.length*num - 1).round]))
|
75
|
+
end
|
76
|
+
unless times.last == make_milli(results.last)
|
77
|
+
times.push(make_milli(results.last))
|
78
|
+
end
|
79
|
+
times
|
80
|
+
end
|
81
|
+
|
82
|
+
def make_milli(num)
|
83
|
+
(num * 1000).to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
def report_header
|
87
|
+
print <<EOF
|
88
|
+
Concurrency Level: #{@max_threads}
|
89
|
+
Total Requests: #{@max_runs}
|
90
|
+
Total time for testing: #{@total_time} secs
|
91
|
+
Requests per second: #{@max_runs/@total_time}
|
92
|
+
Mean time per request: #{results_mean} msecs
|
93
|
+
Standard deviation: #{results_std_dev}
|
94
|
+
EOF
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/rwb/results.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
module RWB
|
2
|
+
|
3
|
+
class Result
|
4
|
+
attr_reader :id, :timestamp, :url
|
5
|
+
attr_reader :elapsed_time, :response_code
|
6
|
+
|
7
|
+
def initialize(id, timestamp, url, elapsed_time, response_code)
|
8
|
+
@id = id
|
9
|
+
@timestamp = timestamp
|
10
|
+
@url = url
|
11
|
+
@elapsed_time = elapsed_time
|
12
|
+
@response_code = response_code
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RunResults
|
17
|
+
attr_reader :results
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@results = Array.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_result(result)
|
24
|
+
@results.push(result)
|
25
|
+
end
|
26
|
+
|
27
|
+
def each_by_response_code(code)
|
28
|
+
@results.each do |result|
|
29
|
+
yield result if result.response_code.to_i == code.to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_by_response_code(code)
|
34
|
+
coded_results = Array.new
|
35
|
+
@results.each do |result|
|
36
|
+
if result.response_code.to_i == code.to_i
|
37
|
+
coded_results.push(result)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
coded_results
|
41
|
+
end
|
42
|
+
|
43
|
+
def all_by_url(url)
|
44
|
+
coded_results = Array.new
|
45
|
+
@results.each do |result|
|
46
|
+
if result.url.to_s == url.to_s
|
47
|
+
coded_results.push(result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
coded_results
|
51
|
+
end
|
52
|
+
|
53
|
+
def each_by_url(url)
|
54
|
+
@results.each do |result|
|
55
|
+
yield result if result.url.to_s == url.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def all_with_id
|
60
|
+
id_results = Hash.new
|
61
|
+
@results.each do |result|
|
62
|
+
id_results[result.id] = result
|
63
|
+
end
|
64
|
+
id_results
|
65
|
+
end
|
66
|
+
|
67
|
+
def times_by_url(url = nil)
|
68
|
+
coded_results = Array.new
|
69
|
+
@results.each do |result|
|
70
|
+
if url
|
71
|
+
if result.url.to_s == url.to_s
|
72
|
+
coded_results.push(result.elapsed_time)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
coded_results.push(result.elapsed_time)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
coded_results
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class Runner
|
84
|
+
def results_mean
|
85
|
+
if @results_mean
|
86
|
+
return @results_mean
|
87
|
+
end
|
88
|
+
@results_mean = @new_results.times_by_url.inject(0) do |sum, time|
|
89
|
+
sum += time
|
90
|
+
end
|
91
|
+
@results_mean = @results_mean/@new_results.times_by_url.length
|
92
|
+
make_milli(@results_mean)
|
93
|
+
end
|
94
|
+
|
95
|
+
def results_std_dev
|
96
|
+
if @results_std_dev
|
97
|
+
return @results_std_dev
|
98
|
+
end
|
99
|
+
unless @results_mean
|
100
|
+
results_mean
|
101
|
+
end
|
102
|
+
@results_std_dev = @new_results.times_by_url.inject(0) do |std_dev, time|
|
103
|
+
std_dev += (time - @results_mean) ** 2
|
104
|
+
end
|
105
|
+
@results_std_dev =
|
106
|
+
Math.sqrt(@results_std_dev/@new_results.times_by_url.length)
|
107
|
+
make_milli(@results_std_dev)
|
108
|
+
end
|
109
|
+
|
110
|
+
def adjust_scale(scale, max)
|
111
|
+
if scale/2 > max
|
112
|
+
scale = scale/2
|
113
|
+
scale = adjust_scale(scale, max)
|
114
|
+
end
|
115
|
+
scale
|
116
|
+
end
|
117
|
+
|
118
|
+
def results_quartile(times, scale = nil)
|
119
|
+
times = times.map { |t| make_milli(t) }
|
120
|
+
size = results_mean + results_std_dev * 2
|
121
|
+
min = times[0]
|
122
|
+
first = times[(times.length*0.25).to_int - 1]
|
123
|
+
second = times[(times.length*0.5).to_int - 1]
|
124
|
+
third = times[(times.length*0.75).to_int - 1]
|
125
|
+
max = times[-1]
|
126
|
+
|
127
|
+
len = max.to_s.length
|
128
|
+
unless scale
|
129
|
+
scale = (max/((10**len).to_f)).ceil * (10**len)
|
130
|
+
scale = adjust_scale(scale, max)
|
131
|
+
end
|
132
|
+
step = scale/50
|
133
|
+
step = 1 if step == 0
|
134
|
+
scale = 50 if scale < 50
|
135
|
+
line = Array.new
|
136
|
+
char = ' '
|
137
|
+
for i in 0..49 do
|
138
|
+
case i
|
139
|
+
when max/step
|
140
|
+
line[i] = ']'
|
141
|
+
when third/step
|
142
|
+
line[i] = '|'
|
143
|
+
char = ' '
|
144
|
+
when second/step
|
145
|
+
line[i] = '+'
|
146
|
+
when first/step
|
147
|
+
line[i] = '|'
|
148
|
+
char = '-'
|
149
|
+
when min/step
|
150
|
+
line[i] = '['
|
151
|
+
when 0
|
152
|
+
line[i] = ':'
|
153
|
+
when 49
|
154
|
+
line[i] = ':'
|
155
|
+
else
|
156
|
+
line[i] = char
|
157
|
+
end
|
158
|
+
end
|
159
|
+
return '0' + line.join('') + scale.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
end
|
data/lib/rwb/url.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module RWB
|
2
|
+
|
3
|
+
#
|
4
|
+
# The Url class holds individual URLs for testing. +Url+s are defined by a
|
5
|
+
# Fixnum representing the weight and a String representing the URL itself.
|
6
|
+
# The weights are not required to be percentages.
|
7
|
+
#
|
8
|
+
# url = RWB::Url.new(20, "http://www.example.com")
|
9
|
+
class Url
|
10
|
+
attr_reader :weight, :method
|
11
|
+
|
12
|
+
def initialize(weight, url, method='GET')
|
13
|
+
@weight = weight
|
14
|
+
@url = url
|
15
|
+
@method = read_method(method)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_method(method = 'GET')
|
19
|
+
method = method.to_s.upcase
|
20
|
+
if method != 'GET' && method != 'POST'
|
21
|
+
return 'GET'
|
22
|
+
else
|
23
|
+
return method
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# to_url returns the URL to be requested as a string
|
28
|
+
#
|
29
|
+
# url.to_url # => "http://www.example.com"
|
30
|
+
def to_url
|
31
|
+
@url
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# to_base returns a string suitable for creating a +Regexp+ to match
|
36
|
+
# against.
|
37
|
+
#
|
38
|
+
# url.to_base # => "http://www.example.com "
|
39
|
+
#
|
40
|
+
def to_base
|
41
|
+
@url
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
@weight.to_s + " " + @url
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# A UrlGroup is a collection of related URLs, for example the URL for a
|
51
|
+
# search tool, with several search queries. The group is weighted (and
|
52
|
+
# reported on) as a whole, but individual requests are made with random
|
53
|
+
# elements from an array of extensions.
|
54
|
+
#
|
55
|
+
# urls = UrlGroup.new(20, "http://www.example.com/search?",
|
56
|
+
# ["foo", "bar", "baz"])
|
57
|
+
#
|
58
|
+
class UrlGroup < RWB::Url
|
59
|
+
|
60
|
+
def initialize(weight, base, extension_array, method='GET')
|
61
|
+
@weight = weight.to_i
|
62
|
+
@base = base.to_s
|
63
|
+
@extension_array = extension_array
|
64
|
+
@method = read_method(method)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
#
|
69
|
+
# to_url returns a complete URL to be requested. It takes an optional
|
70
|
+
# seed argument, which must be a Fixnum. If given, this will seed the
|
71
|
+
# random selection of the extension.
|
72
|
+
#
|
73
|
+
# urls.to_url(1234) # => "http://www.example.com/search?baz"
|
74
|
+
#
|
75
|
+
def to_url(seed = nil)
|
76
|
+
if seed
|
77
|
+
srand = seed
|
78
|
+
end
|
79
|
+
@base + @extension_array[rand(@extension_array.length)]
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# to_base returns a String suitable for building a Regex to match
|
84
|
+
# against. This String will not include any extensions.
|
85
|
+
#
|
86
|
+
# urls.to_base # => "http://www.example.com/search?"
|
87
|
+
#
|
88
|
+
def to_base
|
89
|
+
@base
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
@weight.to_s + " " + @base
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
class UrlSession < RWB::Url
|
100
|
+
def initialize(weight, session, name, method='GET')
|
101
|
+
@weight = weight
|
102
|
+
@session = session
|
103
|
+
@urls = []
|
104
|
+
@name = name
|
105
|
+
@method = read_method(method)
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_url
|
109
|
+
if @urls == []
|
110
|
+
@urls << @session
|
111
|
+
@urls.flatten!
|
112
|
+
end
|
113
|
+
@urls.shift
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_base
|
117
|
+
@name
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_s
|
121
|
+
@weight.to_s + " " + @name
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
end
|
data/lib/rwb/warmup.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module RWB
|
2
|
+
|
3
|
+
class Runner
|
4
|
+
def warmup(num_runs = 1)
|
5
|
+
$stderr.puts "warming up with #{num_runs} runs"
|
6
|
+
for run in 1..num_runs
|
7
|
+
$stderr.print "#{run} "
|
8
|
+
@urls.urls.values.each do |url|
|
9
|
+
url = URI.parse(url.to_url)
|
10
|
+
@http.start(url.host, url.port) do |http|
|
11
|
+
http.request( Net::HTTP::Get.new(url) )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
$stderr.puts
|
16
|
+
end
|
17
|
+
|
18
|
+
def rand_warmup(num_requests)
|
19
|
+
$stderr.puts "warming up with #{num_requests} requests"
|
20
|
+
checkpoint = num_requests/10
|
21
|
+
for run in 1..num_requests
|
22
|
+
if run % checkpoint == 0
|
23
|
+
$stderr.print "#{run} "
|
24
|
+
end
|
25
|
+
url = URI.parse(@urls.get_url.to_url)
|
26
|
+
@http.start(url.host, url.port) do |http|
|
27
|
+
http.request( Net::HTTP::Get.new(url) )
|
28
|
+
end
|
29
|
+
end
|
30
|
+
$stderr.puts
|
31
|
+
end
|
32
|
+
|
33
|
+
def spec_warmup(urls, num_runs=1)
|
34
|
+
$stderr.puts "warming up with #{num_runs} runs"
|
35
|
+
for run in 1..num_runs
|
36
|
+
$stderr.print "#{run} "
|
37
|
+
urls.each do |url|
|
38
|
+
url = URI.parse(url)
|
39
|
+
@http.start(url.host, url.port) do |http|
|
40
|
+
http.request( Net::HTTP::Get.new(url) )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
$stderr.puts
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rwb
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2007-01
|
6
|
+
version: 0.2.3
|
7
|
+
date: 2007-02-01 00:00:00 -07:00
|
8
8
|
summary: Ruby Web Bench, a web performance and load testing framework
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -28,11 +28,17 @@ cert_chain:
|
|
28
28
|
authors:
|
29
29
|
- Pat Eyler
|
30
30
|
files:
|
31
|
+
- test/test_rwb.rb
|
32
|
+
- lib/rwb
|
33
|
+
- lib/rwb.rb
|
34
|
+
- lib/rwb/warmup.rb
|
35
|
+
- lib/rwb/report.rb
|
36
|
+
- lib/rwb/url.rb
|
37
|
+
- lib/rwb/results.rb
|
31
38
|
- NEWS
|
32
39
|
- README
|
33
40
|
- TODO
|
34
|
-
-
|
35
|
-
- test/test_rwb.rb
|
41
|
+
- Rakefile
|
36
42
|
test_files: []
|
37
43
|
|
38
44
|
rdoc_options: []
|