stresser 0.3.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/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +2 -0
- data/README.markdown +105 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/stresser +10 -0
- data/bin/stresser-grapher +32 -0
- data/bin/stresser-loggen +15 -0
- data/build/stresser-0.3.1.gem +0 -0
- data/lib/.grapher.rb.swn +0 -0
- data/lib/grapher.rb +132 -0
- data/lib/httperf.rb +128 -0
- data/lib/mp_perf.rb +102 -0
- data/lib/reports.yml +53 -0
- data/sample.conf +39 -0
- data/spec/httperf_output.txt +30 -0
- data/spec/lib/httperf_spec.rb +129 -0
- data/spec/lib/mp_perf_spec.rb +6 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +7 -0
- data/stresser.gemspec +78 -0
- data/urls.log +2 -0
- metadata +142 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# Stresser
|
2
|
+
|
3
|
+
This gem is a wrapper around the httperf command which
|
4
|
+
can put all types of loads on a webserver. It's like
|
5
|
+
apachebench, but you can replay log files, define
|
6
|
+
sessions, and so forth.
|
7
|
+
|
8
|
+
This gem calls httperf many times with different
|
9
|
+
concurrency settings and parses httperf's output into
|
10
|
+
a csv file, that you can then use to visualize your
|
11
|
+
application's performance at different concurrency
|
12
|
+
levels
|
13
|
+
|
14
|
+
## Sample graphs
|
15
|
+
|
16
|
+
Here's a collection of graphs that this gem currently
|
17
|
+
creates (though you can create your own by creating a
|
18
|
+
YML file that maps columns from the generated csv file
|
19
|
+
to labels for the image).
|
20
|
+
|
21
|
+
|
22
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/connection_time.png" />
|
23
|
+
|
24
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/connections.png" />
|
25
|
+
|
26
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/cpu.png" />
|
27
|
+
|
28
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/errors.png" />
|
29
|
+
|
30
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/milliseconds_per.png" />
|
31
|
+
|
32
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/net-io.png" />
|
33
|
+
|
34
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/replies_per_second.png" />
|
35
|
+
|
36
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/session_rate.png" />
|
37
|
+
|
38
|
+
<img src="http://dl.dropbox.com/u/1953503/github/stresser/stati_per_second.png" />
|
39
|
+
|
40
|
+
|
41
|
+
## Installation
|
42
|
+
|
43
|
+
First install the gem
|
44
|
+
|
45
|
+
$ gem install stresser
|
46
|
+
|
47
|
+
## Configuration
|
48
|
+
|
49
|
+
Please refer to the supplied `sample.conf` on how to
|
50
|
+
configure stresser. Also, see `man httperf` as all
|
51
|
+
options in `sample.conf` beginning with `httperf_`
|
52
|
+
go directly to the httperf commands.
|
53
|
+
|
54
|
+
## Examples
|
55
|
+
|
56
|
+
### Stresstest
|
57
|
+
You can call stresser from the command line:
|
58
|
+
|
59
|
+
$ stresser your_app.conf -o /tmp/stress/result.csv
|
60
|
+
... lots of httperf output...
|
61
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
62
|
+
Great, now create a graph with
|
63
|
+
stresser-grapher -o /tmp/stress /tmp/stress/result.csv
|
64
|
+
$
|
65
|
+
|
66
|
+
You will see the output of the httperf commands that
|
67
|
+
are issued, and a full report will be written to
|
68
|
+
result.csv.
|
69
|
+
|
70
|
+
### Creating graphs
|
71
|
+
When you're done, you can create a graph of your testrun like this:
|
72
|
+
|
73
|
+
$ stresser-grapher -o /tmp/stress /tmp/stress/result.csv
|
74
|
+
Generating stati_per_second to /tmp/stress/2010_10_25_17_28_stati_per_second.png...
|
75
|
+
Generating replies_per_second to /tmp/stress/2010_10_25_17_28_replies_per_second.png...
|
76
|
+
Generating errors to /tmp/stress/2010_10_25_17_28_errors.png...
|
77
|
+
Generating connection_time to /tmp/stress/2010_10_25_17_28_connection_time.png...
|
78
|
+
Generating cpu to /tmp/stress/2010_10_25_17_28_cpu.png...
|
79
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
80
|
+
Great, now open the images with
|
81
|
+
open /tmp/2010_10_25_17_28*.png
|
82
|
+
$
|
83
|
+
|
84
|
+
### Log generator
|
85
|
+
As a little helper to generate log files defining some
|
86
|
+
session workload that requires different urls,
|
87
|
+
`stresser-loggen` is supplied. Just create a log template
|
88
|
+
named `mylog.tpl` like this
|
89
|
+
|
90
|
+
# My session workload
|
91
|
+
/users/{{n}}
|
92
|
+
/images/foo.gif
|
93
|
+
/images/bar.gif
|
94
|
+
/users{{n}}/dashboard
|
95
|
+
|
96
|
+
And then use `stresser-loggen` to reproduce these lines
|
97
|
+
as often as you like:
|
98
|
+
|
99
|
+
stresser-loggen mylog.tpl 100 > mylog.conf
|
100
|
+
|
101
|
+
The `{{n}}` will be replaced with the numbers 0-99.
|
102
|
+
|
103
|
+
## Thanks
|
104
|
+
|
105
|
+
Stresser is based on igvita's autoperf driver for httperf.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "stresser"
|
8
|
+
gem.summary = %{Wrapper around httperf for stresstesting your app.}
|
9
|
+
gem.description = %{Wrapper around httperf for stresstesting your app. Runs httperf multiple times with different concurrency levels and generates an executive summary in .csv"}
|
10
|
+
gem.email = "jannis@moviepilot.com"
|
11
|
+
gem.homepage = "http://github.com/moviepilot/stresser"
|
12
|
+
gem.authors = ["Jannis Hermanns"]
|
13
|
+
gem.add_dependency 'ruport'
|
14
|
+
gem.add_dependency 'gruff'
|
15
|
+
gem.add_dependency 'OptionParser'
|
16
|
+
gem.add_dependency 'trollop'
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
25
|
+
test.libs << 'lib' << 'test'
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
27
|
+
test.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |test|
|
33
|
+
test.libs << 'test'
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :test => :check_dependencies
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "stresser #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.1
|
data/bin/stresser
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'gruff'
|
6
|
+
require 'grapher'
|
7
|
+
require 'trollop'
|
8
|
+
|
9
|
+
opts = Trollop::options do
|
10
|
+
banner <<-EOS
|
11
|
+
Takes a stresser csv file and creates graph images from it.
|
12
|
+
|
13
|
+
Usage:
|
14
|
+
stresser-grapher [options] csv_file
|
15
|
+
where [options] are:
|
16
|
+
EOS
|
17
|
+
opt :report, "The report to generate",
|
18
|
+
:type => String
|
19
|
+
opt :report_definitions, "You can provide your own yaml file - it maps csv columns to data rows in an image",
|
20
|
+
:type => String,
|
21
|
+
:default => "lib/reports.yml"
|
22
|
+
opt :output_dir, "Output directory",
|
23
|
+
:type => String
|
24
|
+
end.merge(:csv_file => ARGV.last)
|
25
|
+
|
26
|
+
Trollop::die :output_dir, "must be writeable" unless opts[:output_dir] and File.stat(opts[:output_dir]).writable?
|
27
|
+
Trollop::die :output_dir, "must be a directory" unless opts[:output_dir] and File.stat(opts[:output_dir]).directory?
|
28
|
+
|
29
|
+
Grapher.generate_reports(opts)
|
30
|
+
|
31
|
+
`open #{ARGV[1]}` if ARGV.last == "open"
|
32
|
+
puts "Done."
|
data/bin/stresser-loggen
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
template = File.open(ARGV[0], "r").read
|
5
|
+
amount = ARGV[1].to_i
|
6
|
+
puts "# Autogenerated with stresser-loggen #{ARGV.join(' ')}\n\n"
|
7
|
+
amount.times do |i|
|
8
|
+
puts template.gsub '{{n}}', i.to_s
|
9
|
+
end
|
10
|
+
rescue Exception => e
|
11
|
+
puts "ERROR:"
|
12
|
+
puts e
|
13
|
+
puts "~"*80
|
14
|
+
puts "Usage (e.g. to generate 15 sessions): stresser-loggen some.log.tpl 15 > some.log"
|
15
|
+
end
|
Binary file
|
data/lib/.grapher.rb.swn
ADDED
Binary file
|
data/lib/grapher.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'ruport'
|
2
|
+
require 'gruff'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
6
|
+
|
7
|
+
module Grapher
|
8
|
+
extend self
|
9
|
+
|
10
|
+
#
|
11
|
+
# Parses command line options and creates one or a bunch of
|
12
|
+
# reports, stores them in the given directory, and advises
|
13
|
+
# the user to go ahead and open them
|
14
|
+
#
|
15
|
+
def generate_reports(options)
|
16
|
+
|
17
|
+
# Let's keep things clean
|
18
|
+
prefix = Time.now.strftime("%Y_%m_%d_%H_%M")
|
19
|
+
|
20
|
+
# Generate a single report or all of them?
|
21
|
+
report_keys = reports(options[:report_definitions]).keys
|
22
|
+
report_keys = [options[:report]] if report_keys.include?(options[:report])
|
23
|
+
|
24
|
+
# Generate report(s)
|
25
|
+
report_keys.each do |report|
|
26
|
+
outfile = File.join(options[:output_dir], "#{prefix}_#{report}.png")
|
27
|
+
generate_report(report, options[:csv_file], outfile)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Tell user what to do next
|
31
|
+
puts "~"*80
|
32
|
+
puts "Great, now open the images with"
|
33
|
+
puts " open #{File.join(options[:output_dir], prefix)}*.png"
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Generates a single report given by name. Uses the yml file for
|
38
|
+
# report names
|
39
|
+
#
|
40
|
+
def generate_report(report_type, csv_file, outfile)
|
41
|
+
puts "Generating #{report_type} to #{outfile}..."
|
42
|
+
columns = (reports[report_type] or reports[reports.keys.first])
|
43
|
+
save_graph(csv_file, columns, outfile, :title => report_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Creates and saves a graph
|
48
|
+
#
|
49
|
+
def save_graph(csv_file, columns, outfile, options = {})
|
50
|
+
# Draw graph
|
51
|
+
g = graph(csv_file, columns, :title => options[:title] )
|
52
|
+
|
53
|
+
# Save graph
|
54
|
+
g.write(outfile)
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Creates a graph from a csv file
|
59
|
+
#
|
60
|
+
def graph(csv_file, columns, options = {})
|
61
|
+
table = Table(csv_file)
|
62
|
+
|
63
|
+
# Prepare data structure
|
64
|
+
data = Hash.new
|
65
|
+
labels = table.column "rate"
|
66
|
+
columns.each_index do |i|
|
67
|
+
next unless i%2==0
|
68
|
+
data[columns[i]] = table.column columns[i+1]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Draw graph
|
72
|
+
g = line_graph( options[:title], data, labels )
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Reads a YAML file that defines how reports are built
|
77
|
+
#
|
78
|
+
def reports(report = nil, yaml_file = File.join(File.dirname(__FILE__), "reports.yml"))
|
79
|
+
y = YAML.load(File.read(yaml_file))
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
|
85
|
+
def line_graph(title, data, labels)
|
86
|
+
|
87
|
+
# Prepare line graph
|
88
|
+
g = Gruff::Line.new
|
89
|
+
g.title = title
|
90
|
+
set_defaults(g)
|
91
|
+
|
92
|
+
# Add datas
|
93
|
+
data.each do |name, values|
|
94
|
+
g.data name, values.map(&:to_i)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add labels
|
98
|
+
g.labels = to_hash(labels)
|
99
|
+
|
100
|
+
# Return graph
|
101
|
+
g
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_hash(array)
|
105
|
+
return array if array.class==Hash
|
106
|
+
hash = Hash.new
|
107
|
+
array.each_with_index{ |v, i| hash[i] = v }
|
108
|
+
hash
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_defaults(g)
|
112
|
+
g.hide_dots = true
|
113
|
+
g.line_width = 2
|
114
|
+
g.legend_font_size = 20
|
115
|
+
g.marker_font_size = 10
|
116
|
+
g.sort = false
|
117
|
+
g.x_axis_label = "concurrency (amount of parallel req)"
|
118
|
+
|
119
|
+
colors = %w{EFD279 95CBE9 024769 AFD775 2C5700 DE9D7F B6212D 7F5417}.map{|c| "\##{c}"}
|
120
|
+
|
121
|
+
g.theme = {
|
122
|
+
:colors => colors,
|
123
|
+
:marker_color => "#cdcdcd",
|
124
|
+
:font_color => 'black',
|
125
|
+
:background_colors => ['#fefeee', '#ffffff']
|
126
|
+
}
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
end
|
132
|
+
|
data/lib/httperf.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Httperf
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def run(conf)
|
5
|
+
res = Hash.new ""
|
6
|
+
httperf_opt = conf.keys.grep(/httperf/).collect {|k| "--#{k.gsub(/httperf_/, '')}=#{conf[k]}"}.join(" ")
|
7
|
+
httperf_cmd = "httperf --hog --server=#{conf['host']} --port=#{conf['port']} #{httperf_opt}"
|
8
|
+
IO.popen("#{httperf_cmd} 2>&1") do |pipe|
|
9
|
+
res = parse_output(pipe)
|
10
|
+
|
11
|
+
# Now calculate the amount of stati per second
|
12
|
+
(1..5).each do |i|
|
13
|
+
begin
|
14
|
+
res["status #{i}xx/s"] = res["status #{i}xx"].to_i / res["duration"].to_i
|
15
|
+
rescue
|
16
|
+
res["status #{i}xx/s"] = -1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
res
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_output(pipe)
|
24
|
+
res = Hash.new("")
|
25
|
+
|
26
|
+
while((line = pipe.gets))
|
27
|
+
res['output'] += line
|
28
|
+
|
29
|
+
title, data = line.split(':')
|
30
|
+
next unless title and data
|
31
|
+
nrs = grep_numbers(data)
|
32
|
+
|
33
|
+
case title
|
34
|
+
when "Total" then
|
35
|
+
res['conns'] = nrs[0]
|
36
|
+
res['requests'] = nrs[1]
|
37
|
+
res['replies'] = nrs[2]
|
38
|
+
res['duration'] = nrs[3]
|
39
|
+
when "Connection rate" then
|
40
|
+
res['conn/s'] = nrs[0]
|
41
|
+
res['ms/connection'] = nrs[1]
|
42
|
+
res['concurrent connections max'] = nrs[2]
|
43
|
+
when "Connection time [ms]" then
|
44
|
+
if data.start_with?(" min")
|
45
|
+
res['conn time min'] = nrs[0]
|
46
|
+
res['conn time avg'] = nrs[1]
|
47
|
+
res['conn time max'] = nrs[2]
|
48
|
+
res['conn time median'] = nrs[3]
|
49
|
+
res['conn time stddev'] = nrs[4]
|
50
|
+
else
|
51
|
+
next unless data.start_with?(" connect")
|
52
|
+
res['conn time connect'] = nrs[0]
|
53
|
+
end
|
54
|
+
when "Connection length [replies/conn]" then
|
55
|
+
res['conn length replies/conn'] = nrs[0]
|
56
|
+
when "Request rate" then
|
57
|
+
res['req/s'] = nrs[0]
|
58
|
+
res['ms/req'] = nrs[1]
|
59
|
+
when "Request size [B]"
|
60
|
+
res['request size'] = nrs[0]
|
61
|
+
when "Reply rate [replies/s]" then
|
62
|
+
res['replies/s min'] = nrs[0]
|
63
|
+
res['replies/s avg'] = nrs[1]
|
64
|
+
res['replies/s max'] = nrs[2]
|
65
|
+
res['replies/s stddev'] = nrs[3]
|
66
|
+
when "Reply time [ms]" then
|
67
|
+
res['reply time response'] = nrs[0]
|
68
|
+
res['reply time transfer'] = nrs[1]
|
69
|
+
when "Reply size [B]" then
|
70
|
+
res['reply size header'] = nrs[0]
|
71
|
+
res['reply size content'] = nrs[1]
|
72
|
+
res['reply size footer'] = nrs[2]
|
73
|
+
res['reply size total'] = nrs[3]
|
74
|
+
when "Reply status" then
|
75
|
+
res['status 1xx'] = nrs[0]
|
76
|
+
res['status 2xx'] = nrs[1]
|
77
|
+
res['status 3xx'] = nrs[2]
|
78
|
+
res['status 4xx'] = nrs[3]
|
79
|
+
res['status 5xx'] = nrs[4]
|
80
|
+
when "CPU time [s]" then
|
81
|
+
res['cpu time user'] = nrs[0]
|
82
|
+
res['cpu time system'] = nrs[1]
|
83
|
+
res['cpu time user %'] = nrs[2]
|
84
|
+
res['cpu time system %'] = nrs[3]
|
85
|
+
res['cpu time total %'] = nrs[4]
|
86
|
+
when "Net I/O" then
|
87
|
+
unit = line.match(/Net I\/O: [\d]+\.[\d+] ([^ ]+)/)
|
88
|
+
res["net i/o (#{unit[1]})"] = nrs[0]
|
89
|
+
when "Errors" then
|
90
|
+
if data.start_with?(' total')
|
91
|
+
res['errors total'] = nrs[0]
|
92
|
+
res['errors client-timo'] = nrs[1]
|
93
|
+
res['errors socket-timo'] = nrs[2]
|
94
|
+
res['errors connrefused'] = nrs[3]
|
95
|
+
res['errors connreset'] = nrs[4]
|
96
|
+
else
|
97
|
+
res['errors fd-unavail'] = nrs[0]
|
98
|
+
res['errors addrunavail'] = nrs[1]
|
99
|
+
res['errors ftab-full'] = nrs[2]
|
100
|
+
res['errors other'] = nrs[3]
|
101
|
+
end
|
102
|
+
when "Session rate [sess/s]" then
|
103
|
+
res['session rate min'] = nrs[0]
|
104
|
+
res['session rate avg'] = nrs[1]
|
105
|
+
res['session rate max'] = nrs[2]
|
106
|
+
res['session rate stddev'] = nrs[3]
|
107
|
+
res['session rate quota'] = "#{nrs[4]}/#{nrs[5]}"
|
108
|
+
when "Session" then
|
109
|
+
res['session avg conns/sess'] = nrs[0]
|
110
|
+
when "Session lifetime [s]" then
|
111
|
+
res['session lifetime [s]'] = nrs[0]
|
112
|
+
when "Session failtime [s]" then
|
113
|
+
res['session failtime [s]'] = nrs[0]
|
114
|
+
when "Session length histogram" then
|
115
|
+
res['session length histogram'] = nrs.join(" ")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
res
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def grep_numbers(line)
|
124
|
+
line.scan(/(\d+\.?\d*)[^x]/).flatten.map do |s|
|
125
|
+
s.include?(".") ? s.to_f : s.to_i
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/mp_perf.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ruport'
|
3
|
+
require 'httperf'
|
4
|
+
|
5
|
+
#
|
6
|
+
# Takes command line options and attempts to make a benchmark.
|
7
|
+
#
|
8
|
+
class MPPerf
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@conf = {}
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: stresser -c your.conf -o output.csv"
|
14
|
+
opts.on("-o", "--output FILE", String, "This file will be overwritten with a detailed report of the stresstest") do |v|
|
15
|
+
@output_file = v
|
16
|
+
end
|
17
|
+
opts.on( "-c", "--config FILE", String, "Your stresser configuration file with stresstest options and parameters directly passed to httperf") do |v|
|
18
|
+
@conf = parse_config(v)
|
19
|
+
end
|
20
|
+
end.parse!
|
21
|
+
|
22
|
+
run()
|
23
|
+
|
24
|
+
puts "~"*80
|
25
|
+
puts "Great, now create a graph with"
|
26
|
+
puts " stresser-grapher -o #{File.expand_path(File.dirname(@output_file))} #{@output_file}"
|
27
|
+
puts ""
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Taken from http://github.com/igrigorik/autoperf
|
32
|
+
#
|
33
|
+
def parse_config(config_file)
|
34
|
+
raise Errno::EACCES, "#{config_file} is not readable" unless File.readable?(config_file)
|
35
|
+
|
36
|
+
conf = {}
|
37
|
+
open(config_file).each { |line|
|
38
|
+
line.chomp
|
39
|
+
unless (/^\#/.match(line))
|
40
|
+
if(/\s*=\s*/.match(line))
|
41
|
+
param, value = line.split(/\s*=\s*/, 2)
|
42
|
+
var_name = "#{param}".chomp.strip
|
43
|
+
value = value.chomp.strip
|
44
|
+
new_value = ''
|
45
|
+
if (value)
|
46
|
+
if value =~ /^['"](.*)['"]$/
|
47
|
+
new_value = $1
|
48
|
+
else
|
49
|
+
new_value = value
|
50
|
+
end
|
51
|
+
else
|
52
|
+
new_value = ''
|
53
|
+
end
|
54
|
+
conf[var_name] = new_value =~ /^\d+$/ ? new_value.to_i : new_value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
return conf
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Runs a single benchmark (this method will be called many times
|
64
|
+
# with different concurrency levels)
|
65
|
+
#
|
66
|
+
def single_benchmark(conf)
|
67
|
+
|
68
|
+
# Run httperf
|
69
|
+
res = Httperf.run(conf)
|
70
|
+
|
71
|
+
return res
|
72
|
+
end
|
73
|
+
|
74
|
+
def run
|
75
|
+
results = {}
|
76
|
+
report = nil
|
77
|
+
(@conf['low_rate']..@conf['high_rate']).step(@conf['rate_step']) do |rate|
|
78
|
+
|
79
|
+
# Run httperf
|
80
|
+
results[rate] = single_benchmark(@conf.merge({'httperf_rate' => rate}))
|
81
|
+
|
82
|
+
# Show that we're alive
|
83
|
+
puts "#{results[rate].delete('output')}\n"
|
84
|
+
puts "~"*80
|
85
|
+
|
86
|
+
# Init table unless it's there already
|
87
|
+
report ||= Table(:column_names => ['rate'] + results[rate].keys.sort)
|
88
|
+
|
89
|
+
# Save results of this run
|
90
|
+
report << results[rate].merge({'rate' => rate})
|
91
|
+
|
92
|
+
# Try to keep old pending requests from influencing the next round
|
93
|
+
sleep(@conf['sleep_time'] || 0)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Write csv
|
97
|
+
File.new(@output_file, "w").puts report.to_csv unless report.nil?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
data/lib/reports.yml
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
stati_per_second:
|
2
|
+
[Requests/s, req/s,
|
3
|
+
1xx/s, status 1xx/s,
|
4
|
+
2xx/s, status 2xx/s,
|
5
|
+
3xx/s, status 3xx/s,
|
6
|
+
4xx/s, status 4xx/s,
|
7
|
+
5xx/s, status 5xx/s ]
|
8
|
+
|
9
|
+
errors:
|
10
|
+
[Requests, requests,
|
11
|
+
Client timeouts, errors client-timo,
|
12
|
+
Socket timeouts, errors socket-timo,
|
13
|
+
Filedescriptor unavail, errors fd-unavail,
|
14
|
+
Ftab full, errors ftab-full,
|
15
|
+
Addr unavail, errors addrunavail,
|
16
|
+
Conn. refused, errors connrefused,
|
17
|
+
Conn. reset, errors connreset,
|
18
|
+
Total errors, errors total]
|
19
|
+
|
20
|
+
connection_time:
|
21
|
+
[Average, conn time avg,
|
22
|
+
Max, conn time max,
|
23
|
+
Median, conn time median,
|
24
|
+
Min, conn time min,
|
25
|
+
Std. dev., conn time stddev]
|
26
|
+
|
27
|
+
replies_per_second:
|
28
|
+
[Average, replies/s avg,
|
29
|
+
Max, replies/s max,
|
30
|
+
Min, replies/s min,
|
31
|
+
Std. dev., replies/s stddev]
|
32
|
+
|
33
|
+
cpu:
|
34
|
+
[System, cpu time system %,
|
35
|
+
Total, cpu time total %,
|
36
|
+
User, cpu time user %]
|
37
|
+
|
38
|
+
session_rate:
|
39
|
+
[Average, session rate avg,
|
40
|
+
Maximum, session rate max,
|
41
|
+
Minimum, session rate min,
|
42
|
+
Std. dev., session rate stddev]
|
43
|
+
|
44
|
+
milliseconds_per:
|
45
|
+
[ms per connection, ms/connection,
|
46
|
+
ms per request, ms/req]
|
47
|
+
|
48
|
+
net_io:
|
49
|
+
[KB/s, net i/o (KB/s)]
|
50
|
+
|
51
|
+
connections:
|
52
|
+
[Requests/s, req/s,
|
53
|
+
Connections per second, conn/s]
|
data/sample.conf
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# MPPerf Configuration File
|
2
|
+
|
3
|
+
# The host, URI (relative to the document root) and port to test.
|
4
|
+
host = www.intern.moviepilot.de
|
5
|
+
uri = /
|
6
|
+
port = 12001
|
7
|
+
|
8
|
+
# The 'rate' is the number of number of connections to open per second.
|
9
|
+
# A series of tests will be conducted, starting at low rate,
|
10
|
+
# increasing by rate sep, and finishing at high_rate.
|
11
|
+
low_rate = 5
|
12
|
+
high_rate = 50
|
13
|
+
rate_step = 5
|
14
|
+
|
15
|
+
# The amount of seconds to sleep between each rate (to avoid pending
|
16
|
+
# requests from one run influencing the next run)
|
17
|
+
sleep_time = 10
|
18
|
+
|
19
|
+
# httperf options
|
20
|
+
|
21
|
+
# wlog specifies a replay log file (null terminated requests paths)
|
22
|
+
# 'n' prefix tells httperf to stop after all requests in the file
|
23
|
+
# have been replayed
|
24
|
+
# httperf_wlog = "y,anonymous_forecasts_20.log"
|
25
|
+
httperf_wsesslog = "100,10,urls.log"
|
26
|
+
|
27
|
+
# num_conn is the total number of connections to make during a test
|
28
|
+
# num_call is the number of requests per connection (if keep alive is supported)
|
29
|
+
# The product of num_call and rate is the the approximate number of
|
30
|
+
# requests per second that will be attempted.
|
31
|
+
httperf_num-conns = 100
|
32
|
+
httperf_num-calls = 1
|
33
|
+
|
34
|
+
# timeout sets the maximimum time (in seconds) that httperf will wait
|
35
|
+
# for replies from the web server. If the timeout is exceeded, the
|
36
|
+
# reply concerned is counted as an error.
|
37
|
+
httperf_timeout = 5
|
38
|
+
|
39
|
+
httperf_burst-length=1
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
Maximum connect burst length: 1
|
3
|
+
|
4
|
+
Total: connections 500 requests 600 replies 300 test-duration 50.354 s
|
5
|
+
|
6
|
+
Connection rate: 9.9 conn/s (100.7 ms/conn, <=8 concurrent connections)
|
7
|
+
Connection time [ms]: min 449.7 avg 465.1 max 2856.6 median 451.5 stddev 132.1
|
8
|
+
Connection time [ms]: connect 74.1
|
9
|
+
Connection length [replies/conn]: 1.000
|
10
|
+
|
11
|
+
Request rate: 9.9 req/s (100.7 ms/req)
|
12
|
+
Request size [B]: 65.0
|
13
|
+
|
14
|
+
Reply rate [replies/s]: min 9.2 avg 9.9 max 10.0 stddev 0.3 (10 samples)
|
15
|
+
Reply time [ms]: response 88.1 transfer 302.9
|
16
|
+
Reply size [B]: header 274.0 content 54744.0 footer 2.0 (total 55020.0)
|
17
|
+
Reply status: 1xx=1 2xx=500 3xx=3 4xx=4 5xx=5
|
18
|
+
|
19
|
+
CPU time [s]: user 15.65 system 34.65 (user 31.1% system 68.8% total 99.9%)
|
20
|
+
Net I/O: 534.1 KB/s (4.4*10^6 bps)
|
21
|
+
|
22
|
+
Errors: total 1234 client-timo 2345 socket-timo 3456 connrefused 4567 connreset 5678
|
23
|
+
Errors: fd-unavail 1 addrunavail 2 ftab-full 3 other 4
|
24
|
+
|
25
|
+
Session rate [sess/s]: min 35.80 avg 37.04 max 38.20 stddev 0.98 (1000/1000)
|
26
|
+
Session: avg 2.00 connections/session
|
27
|
+
Session lifetime [s]: 0.3
|
28
|
+
Session failtime [s]: 0.0
|
29
|
+
Session length histogram: 0 0 1000
|
30
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require "spec/spec_helper"
|
2
|
+
|
3
|
+
describe Httperf do
|
4
|
+
|
5
|
+
describe "parsing httperf output" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@pipe = File.open("spec/httperf_output.txt")
|
9
|
+
@〉 = Httperf.parse_output(@pipe)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse the 'Total' line correctly" do
|
13
|
+
@〉['conns'].should == 500
|
14
|
+
@〉['requests'].should == 600
|
15
|
+
@〉['replies'].should == 300
|
16
|
+
@〉['duration'].should == 50.354
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should parse the 'Connection rate' line correctly" do
|
20
|
+
@〉['conn/s'].should == 9.9
|
21
|
+
@〉['ms/connection'].should == 100.7
|
22
|
+
@〉['concurrent connections max'].should == 8
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should parse the 'Connection time' line correctly" do
|
26
|
+
@〉['conn time min'].should == 449.7
|
27
|
+
@〉['conn time avg'].should == 465.1
|
28
|
+
@〉['conn time max'].should == 2856.6
|
29
|
+
@〉['conn time median'].should == 451.5
|
30
|
+
@〉['conn time stddev'].should == 132.1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should parse the second 'Connection time' line correctly" do
|
34
|
+
@〉['conn time connect'].should == 74.1
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should parse the 'Connection length' line correctly" do
|
38
|
+
@〉['conn length replies/conn'].should == 1.0
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should parse the 'Request rate' line correctly" do
|
42
|
+
@〉['req/s'].should == 9.9
|
43
|
+
@〉['ms/req'].should == 100.7
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should parse the 'Request size' line correctly" do
|
47
|
+
@〉['request size'].should == 65.0
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should parse the 'Reply rate' line correctly" do
|
51
|
+
@〉['replies/s min'].should == 9.2
|
52
|
+
@〉['replies/s avg'].should == 9.9
|
53
|
+
@〉['replies/s max'].should == 10.0
|
54
|
+
@〉['replies/s stddev'].should == 0.3
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should parse the 'Reply time' line correctly" do
|
58
|
+
@〉['reply time response'].should == 88.1
|
59
|
+
@〉['reply time transfer'].should == 302.9
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should parse the 'Reply size' line correctly" do
|
63
|
+
@〉['reply size header'].should == 274.0
|
64
|
+
@〉['reply size content'].should == 54744.0
|
65
|
+
@〉['reply size footer'].should == 2.0
|
66
|
+
@〉['reply size total'].should == 55020.0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should parse the 'Reply status' line correctly" do
|
70
|
+
@〉['status 1xx'].should == 1
|
71
|
+
@〉['status 2xx'].should == 500
|
72
|
+
@〉['status 3xx'].should == 3
|
73
|
+
@〉['status 4xx'].should == 4
|
74
|
+
@〉['status 5xx'].should == 5
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should parse the 'CPU time' line correctly" do
|
78
|
+
@〉['cpu time user'].should == 15.65
|
79
|
+
@〉['cpu time system'].should == 34.65
|
80
|
+
@〉['cpu time user %'].should == 31.1
|
81
|
+
@〉['cpu time system %'].should == 68.8
|
82
|
+
@〉['cpu time total %'].should == 99.9
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should parse the 'Net I/O' line correctly" do
|
86
|
+
@〉['net i/o (KB/s)'].should == 534.1
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should parse the first 'Errors' line correctly" do
|
90
|
+
@〉['errors total'].should == 1234
|
91
|
+
@〉['errors client-timo'].should == 2345
|
92
|
+
@〉['errors socket-timo'].should == 3456
|
93
|
+
@〉['errors connrefused'].should == 4567
|
94
|
+
@〉['errors connreset'].should == 5678
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should parse the second 'Errors' line correctly" do
|
98
|
+
@〉['errors fd-unavail'].should == 1
|
99
|
+
@〉['errors addrunavail'].should == 2
|
100
|
+
@〉['errors ftab-full'].should == 3
|
101
|
+
@〉['errors other'].should == 4
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should parse the 'Session rate' line correctly" do
|
105
|
+
@〉['session rate min'].should == 35.80
|
106
|
+
@〉['session rate avg'].should == 37.04
|
107
|
+
@〉['session rate max'].should == 38.20
|
108
|
+
@〉['session rate stddev'].should == 0.98
|
109
|
+
@〉['session rate quota'].should == "1000/1000"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should parse the 'Session' line correctly" do
|
113
|
+
@〉['session avg conns/sess'].should == 2.00
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should parse the 'Session lifetime' line correctly" do
|
117
|
+
@〉['session lifetime [s]'].should == 0.3
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should parse the 'Session failtime' line correctly" do
|
121
|
+
@〉['session failtime [s]'].should == 0.0
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should parse the 'Session length histogram' correctly" do
|
125
|
+
@〉['session length histogram'].should == "0 0 1000"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
data/spec/spec_helper.rb
ADDED
data/stresser.gemspec
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{stresser}
|
8
|
+
s.version = "0.3.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jannis Hermanns"]
|
12
|
+
s.date = %q{2010-11-13}
|
13
|
+
s.description = %q{Wrapper around httperf for stresstesting your app. Runs httperf multiple times with different concurrency levels and generates an executive summary in .csv"}
|
14
|
+
s.email = %q{jannis@moviepilot.com}
|
15
|
+
s.executables = ["stresser", "stresser-grapher", "stresser-loggen"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README.markdown"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/stresser",
|
28
|
+
"bin/stresser-grapher",
|
29
|
+
"bin/stresser-loggen",
|
30
|
+
"build/stresser-0.3.1.gem",
|
31
|
+
"lib/.grapher.rb.swn",
|
32
|
+
"lib/grapher.rb",
|
33
|
+
"lib/httperf.rb",
|
34
|
+
"lib/mp_perf.rb",
|
35
|
+
"lib/reports.yml",
|
36
|
+
"sample.conf",
|
37
|
+
"spec/httperf_output.txt",
|
38
|
+
"spec/lib/httperf_spec.rb",
|
39
|
+
"spec/lib/mp_perf_spec.rb",
|
40
|
+
"spec/spec.opts",
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"stresser.gemspec",
|
43
|
+
"urls.log"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/moviepilot/stresser}
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.3.7}
|
49
|
+
s.summary = %q{Wrapper around httperf for stresstesting your app.}
|
50
|
+
s.test_files = [
|
51
|
+
"spec/lib/httperf_spec.rb",
|
52
|
+
"spec/lib/mp_perf_spec.rb",
|
53
|
+
"spec/spec_helper.rb"
|
54
|
+
]
|
55
|
+
|
56
|
+
if s.respond_to? :specification_version then
|
57
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
58
|
+
s.specification_version = 3
|
59
|
+
|
60
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
61
|
+
s.add_runtime_dependency(%q<ruport>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<gruff>, [">= 0"])
|
63
|
+
s.add_runtime_dependency(%q<OptionParser>, [">= 0"])
|
64
|
+
s.add_runtime_dependency(%q<trollop>, [">= 0"])
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<ruport>, [">= 0"])
|
67
|
+
s.add_dependency(%q<gruff>, [">= 0"])
|
68
|
+
s.add_dependency(%q<OptionParser>, [">= 0"])
|
69
|
+
s.add_dependency(%q<trollop>, [">= 0"])
|
70
|
+
end
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<ruport>, [">= 0"])
|
73
|
+
s.add_dependency(%q<gruff>, [">= 0"])
|
74
|
+
s.add_dependency(%q<OptionParser>, [">= 0"])
|
75
|
+
s.add_dependency(%q<trollop>, [">= 0"])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
data/urls.log
ADDED
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stresser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 1
|
9
|
+
version: 0.3.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jannis Hermanns
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-13 00:00:00 -08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: ruport
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: gruff
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: OptionParser
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: trollop
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :runtime
|
71
|
+
version_requirements: *id004
|
72
|
+
description: Wrapper around httperf for stresstesting your app. Runs httperf multiple times with different concurrency levels and generates an executive summary in .csv"
|
73
|
+
email: jannis@moviepilot.com
|
74
|
+
executables:
|
75
|
+
- stresser
|
76
|
+
- stresser-grapher
|
77
|
+
- stresser-loggen
|
78
|
+
extensions: []
|
79
|
+
|
80
|
+
extra_rdoc_files:
|
81
|
+
- LICENSE
|
82
|
+
- README.markdown
|
83
|
+
files:
|
84
|
+
- .document
|
85
|
+
- .gitignore
|
86
|
+
- LICENSE
|
87
|
+
- README.markdown
|
88
|
+
- Rakefile
|
89
|
+
- VERSION
|
90
|
+
- bin/stresser
|
91
|
+
- bin/stresser-grapher
|
92
|
+
- bin/stresser-loggen
|
93
|
+
- build/stresser-0.3.1.gem
|
94
|
+
- lib/.grapher.rb.swn
|
95
|
+
- lib/grapher.rb
|
96
|
+
- lib/httperf.rb
|
97
|
+
- lib/mp_perf.rb
|
98
|
+
- lib/reports.yml
|
99
|
+
- sample.conf
|
100
|
+
- spec/httperf_output.txt
|
101
|
+
- spec/lib/httperf_spec.rb
|
102
|
+
- spec/lib/mp_perf_spec.rb
|
103
|
+
- spec/spec.opts
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
- stresser.gemspec
|
106
|
+
- urls.log
|
107
|
+
has_rdoc: true
|
108
|
+
homepage: http://github.com/moviepilot/stresser
|
109
|
+
licenses: []
|
110
|
+
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options:
|
113
|
+
- --charset=UTF-8
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: "0"
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
requirements: []
|
133
|
+
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 1.3.7
|
136
|
+
signing_key:
|
137
|
+
specification_version: 3
|
138
|
+
summary: Wrapper around httperf for stresstesting your app.
|
139
|
+
test_files:
|
140
|
+
- spec/lib/httperf_spec.rb
|
141
|
+
- spec/lib/mp_perf_spec.rb
|
142
|
+
- spec/spec_helper.rb
|