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.
@@ -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