stresser 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+ *.swo
16
+
17
+ ## PROJECT::GENERAL
18
+ coverage
19
+ rdoc
20
+ pkg
21
+
22
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2010 Moviepilot GmbH
2
+
@@ -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.
@@ -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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'mp_perf'
4
+
5
+ trap("INT") {
6
+ puts "Terminating tests."
7
+ Process.exit
8
+ }
9
+
10
+ MPPerf.new
@@ -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."
@@ -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
Binary file
@@ -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
+
@@ -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
@@ -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
+
@@ -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]
@@ -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
+
@@ -0,0 +1,6 @@
1
+ require "spec/spec_helper"
2
+
3
+ describe MPPerf do
4
+
5
+
6
+ end
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --loadby mtime
3
+ -u
4
+ -f o
5
+ -b
@@ -0,0 +1,7 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'progressbar'
6
+ require 'lib/httperf'
7
+ require 'lib/mp_perf'
@@ -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
+
@@ -0,0 +1,2 @@
1
+ /status think=1
2
+ /
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