solutious-stella 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGES.txt +36 -0
  2. data/README.textile +162 -0
  3. data/Rakefile +88 -0
  4. data/bin/stella +12 -0
  5. data/bin/stella.bat +12 -0
  6. data/lib/daemonize.rb +56 -0
  7. data/lib/pcaplet.rb +180 -0
  8. data/lib/stella.rb +101 -0
  9. data/lib/stella/adapter/ab.rb +337 -0
  10. data/lib/stella/adapter/base.rb +106 -0
  11. data/lib/stella/adapter/httperf.rb +305 -0
  12. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  13. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  14. data/lib/stella/adapter/siege.rb +341 -0
  15. data/lib/stella/cli.rb +258 -0
  16. data/lib/stella/cli/agents.rb +73 -0
  17. data/lib/stella/cli/base.rb +55 -0
  18. data/lib/stella/cli/language.rb +18 -0
  19. data/lib/stella/cli/localtest.rb +78 -0
  20. data/lib/stella/cli/sysinfo.rb +16 -0
  21. data/lib/stella/cli/watch.rb +278 -0
  22. data/lib/stella/command/base.rb +40 -0
  23. data/lib/stella/command/localtest.rb +358 -0
  24. data/lib/stella/data/domain.rb +82 -0
  25. data/lib/stella/data/http.rb +131 -0
  26. data/lib/stella/logger.rb +84 -0
  27. data/lib/stella/response.rb +85 -0
  28. data/lib/stella/storable.rb +201 -0
  29. data/lib/stella/support.rb +276 -0
  30. data/lib/stella/sysinfo.rb +257 -0
  31. data/lib/stella/test/definition.rb +79 -0
  32. data/lib/stella/test/run/summary.rb +70 -0
  33. data/lib/stella/test/stats.rb +114 -0
  34. data/lib/stella/text.rb +64 -0
  35. data/lib/stella/text/resource.rb +38 -0
  36. data/lib/utils/crypto-key.rb +84 -0
  37. data/lib/utils/domainutil.rb +47 -0
  38. data/lib/utils/escape.rb +302 -0
  39. data/lib/utils/fileutil.rb +78 -0
  40. data/lib/utils/httputil.rb +266 -0
  41. data/lib/utils/mathutil.rb +15 -0
  42. data/lib/utils/stats.rb +88 -0
  43. data/lib/utils/textgraph.rb +267 -0
  44. data/lib/utils/timerutil.rb +58 -0
  45. data/lib/win32/Console.rb +970 -0
  46. data/lib/win32/Console/ANSI.rb +305 -0
  47. data/support/kvm.h +91 -0
  48. data/support/ruby-pcap-takuma-notes.txt +19 -0
  49. data/support/ruby-pcap-takuma-patch.txt +30 -0
  50. data/support/text/en.yaml +80 -0
  51. data/support/text/nl.yaml +7 -0
  52. data/support/useragents.txt +75 -0
  53. data/tests/01-util_test.rb +0 -0
  54. data/tests/02-stella-util_test.rb +42 -0
  55. data/tests/10-stella_test.rb +104 -0
  56. data/tests/11-stella-storable_test.rb +68 -0
  57. data/tests/60-stella-command_test.rb +248 -0
  58. data/tests/80-stella-cli_test.rb +45 -0
  59. data/tests/spec-helper.rb +31 -0
  60. metadata +165 -0
data/lib/stella.rb ADDED
@@ -0,0 +1,101 @@
1
+
2
+
3
+ require 'date'
4
+ require 'time'
5
+ require 'tempfile'
6
+ require 'socket'
7
+ require 'ostruct'
8
+ require 'optparse'
9
+ require 'rubygems'
10
+
11
+
12
+ # Common utilities
13
+ require 'utils/domainutil'
14
+ require 'utils/httputil'
15
+ require 'utils/fileutil'
16
+ require 'utils/mathutil'
17
+ require 'utils/escape'
18
+ require 'utils/stats'
19
+
20
+ # Common dependencies
21
+ $: << File.join(STELLA_HOME, 'vendor', 'useragent', 'lib')
22
+ require 'user_agent'
23
+
24
+ # Common Stella dependencies
25
+ require 'stella/support'
26
+ require 'stella/storable'
27
+
28
+ # Common Stella Data Objects
29
+ require 'stella/data/http'
30
+ require 'stella/data/domain'
31
+
32
+ # Common Stella objects
33
+ require 'stella/text'
34
+
35
+ require 'stella/logger'
36
+ require 'stella/response'
37
+ require 'stella/sysinfo'
38
+ require 'stella/test/definition'
39
+ require 'stella/test/run/summary'
40
+ require 'stella/test/stats'
41
+
42
+ # Commands
43
+ require 'stella/command/base'
44
+ require 'stella/command/localtest'
45
+
46
+ # Adapters
47
+ require 'stella/adapter/base'
48
+ require 'stella/adapter/ab'
49
+ require 'stella/adapter/siege'
50
+ require 'stella/adapter/httperf'
51
+
52
+ # = Stella
53
+ # A friend in performance testing.
54
+ #
55
+ # This class ties Stella together. It must be required because it defines
56
+ # several constants which are used througout the other classes. +SYSINFO+
57
+ # is particularly important because it detects the platform and requires
58
+ # platform specific modules.
59
+ module Stella
60
+ # Autodetecets information about the local system,
61
+ # including OS (unix), implementation (freebsd), and architecture (x64)
62
+ SYSINFO = Stella::SystemInfo.new unless defined? SYSINFO
63
+ # A global logger for info, error, and debug messages.
64
+ LOGGER = Stella::Logger.new(:debug=>false) unless defined? LOGGER
65
+ # A global resource for all interface text.
66
+ TEXT = Stella::Text.new('en') unless defined? TEXT
67
+
68
+ module VERSION #:nodoc:
69
+ MAJOR = 0.freeze unless defined? MAJOR
70
+ MINOR = 5.freeze unless defined? MINOR
71
+ TINY = 5.freeze unless defined? TINY
72
+ def self.to_s
73
+ [MAJOR, MINOR, TINY].join('.')
74
+ end
75
+ def self.to_f
76
+ self.to_s.to_f
77
+ end
78
+ end
79
+
80
+ def self.debug=(enable=false)
81
+ Stella::LOGGER.debug_level = enable
82
+ end
83
+
84
+ def self.text(*args)
85
+ TEXT.msg(*args)
86
+ end
87
+
88
+ def self.sysinfo
89
+ SYSINFO
90
+ end
91
+
92
+ def self.info(*args)
93
+ LOGGER.info(*args)
94
+ end
95
+
96
+ def self.error(*args)
97
+ LOGGER.error(*args)
98
+ end
99
+
100
+ end
101
+
@@ -0,0 +1,337 @@
1
+
2
+ require 'fileutils'
3
+
4
+ module Stella
5
+ module Adapter
6
+
7
+ #Usage: ab [options] [http[s]://]hostname[:port]/path
8
+ #Options are:
9
+ # -n requests Number of requests to perform
10
+ # -c concurrency Number of multiple requests to make
11
+ # -t timelimit Seconds to max. wait for responses
12
+ # -b windowsize Size of TCP send/receive buffer, in bytes
13
+ # -p postfile File containing data to POST. Remember also to set -T
14
+ # -T content-type Content-type header for POSTing, eg.
15
+ # 'application/x-www-form-urlencoded'
16
+ # Default is 'text/plain'
17
+ # -v verbosity How much troubleshooting info to print
18
+ # -w Print out results in HTML tables
19
+ # -i Use HEAD instead of GET
20
+ # -x attributes String to insert as table attributes
21
+ # -y attributes String to insert as tr attributes
22
+ # -z attributes String to insert as td or th attributes
23
+ # -C attribute Add cookie, eg. 'Apache=1234. (repeatable)
24
+ # -H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
25
+ # Inserted after all normal header lines. (repeatable)
26
+ # -A attribute Add Basic WWW Authentication, the attributes
27
+ # are a colon separated username and password.
28
+ # -P attribute Add Basic Proxy Authentication, the attributes
29
+ # are a colon separated username and password.
30
+ # -X proxy:port Proxyserver and port number to use
31
+ # -V Print version number and exit
32
+ # -k Use HTTP KeepAlive feature
33
+ # -d Do not show percentiles served table.
34
+ # -S Do not show confidence estimators and warnings.
35
+ # -g filename Output collected data to gnuplot format file.
36
+ # -e filename Output CSV file with percentages served
37
+ # -r Don't exit on socket receive errors.
38
+ # -h Display usage information (this message)
39
+ # -Z ciphersuite Specify SSL/TLS cipher suite (See openssl ciphers)
40
+ # -f protocol Specify SSL/TLS protocol (SSL2, SSL3, TLS1, or ALL)
41
+ class ApacheBench < Stella::Adapter::Base
42
+
43
+ attr_writer :n, :c
44
+ attr_accessor :t, :b, :p, :T, :v, :w, :i, :x, :z, :y
45
+ attr_accessor :C, :H, :A, :P, :X, :V, :k, :d, :S, :e, :g, :r, :h, :Z, :f
46
+
47
+ def initialize(options={}, arguments=[])
48
+ @private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory']
49
+ @c = 1
50
+ @n = 1
51
+ @name = 'ab'
52
+ @load_factor = 1
53
+
54
+ super(options, arguments)
55
+
56
+ end
57
+
58
+ def error
59
+ (File.exists? stderr_path) ? FileUtil.read_file(stderr_path) : "Unknown error"
60
+ end
61
+
62
+ def version
63
+ vsn = 0
64
+ Stella::Util.capture_output("#{@name} -V") do |stdout, stderr|
65
+ stdout.join.scan(/Version (\d+?\.\d+)/) { |v| vsn = v[0] }
66
+ end
67
+ vsn
68
+ end
69
+
70
+ def percentiles_file
71
+ @working_directory + "/ab-percentiles.log"
72
+ end
73
+
74
+ def requests_file
75
+ @working_directory + "/ab-requests.log"
76
+ end
77
+
78
+ def before
79
+ @e = percentiles_file if @e.nil?
80
+ @g = requests_file if @g.nil?
81
+ end
82
+
83
+ def command
84
+ raise CommandNotReady.new(self.class.to_s) unless ready?
85
+
86
+ command = "#{@name} "
87
+
88
+ instance_variables.each do |name|
89
+ canon = name.to_s.tr('@', '') # instance_variables returns '@name'
90
+ next if @private_variables.member?(canon)
91
+
92
+ # It's important that we take the value from the getter method
93
+ # because it applies the load factor.
94
+ value = self.send(canon)
95
+ if (value.is_a? Array)
96
+ value.each { |el| command << "-#{canon} #{EscapeUtil.shell_single_word(value.to_s)} " }
97
+ else
98
+ command << "-#{canon} #{EscapeUtil.shell_single_word(value.to_s)} "
99
+ end
100
+
101
+ end
102
+
103
+ command << (@arguments.map { |uri| "#{uri}" }).join(' ') unless @arguments.empty?
104
+
105
+ command
106
+ end
107
+ # loadtest
108
+ #
109
+ # True or false: is the call to ab a load test? If it's a call to help or version or
110
+ # to display the config this with return false. It's no reason for someone to make this
111
+ # call through Stella but it's here for goodness sake.
112
+ def loadtest?
113
+ !@arguments.empty? # The argument is a URI
114
+ end
115
+ def ready?
116
+ (!self.loadtest?) || (@name && !instance_variables.empty? && !@arguments.empty?)
117
+ end
118
+
119
+
120
+ def process_arguments(arguments)
121
+ opts = OptionParser.new
122
+
123
+ # TODO: there's no need to use an OpenStruct here. It's confusing b/c we can
124
+ # use the instance var methods here instead of in Base::options=.
125
+
126
+ # TODO: Print a note for w that we don't parse the HTML results
127
+ %w{v w i V k d S r h}.each do |n|
128
+ opts.on("-#{n}") do |v| instance_variable_set("@#{n}", true) end
129
+ end
130
+
131
+ %w{e g p T x y z P Z f A}.each do |n|
132
+ opts.on("-#{n} S", String) do |v| instance_variable_set("@#{n}", v) end
133
+ end
134
+
135
+ %w{c n t b}.each do |n|
136
+ opts.on("-#{n} S", Integer) do |v| instance_variable_set("@#{n}", v) end
137
+ end
138
+
139
+ opts.on('-H S', String) do |v| @H ||= []; @H << v; end
140
+ opts.on('-C S', String) do |v| @C ||= []; @C << v; end
141
+
142
+ opts.on('-b') do |v|
143
+ Stella.warn("-b is not an ab option. I'll pretend it's not there.")
144
+ end
145
+
146
+ opts.on('-r N',Integer) do |v|
147
+ Stella.error("-r is not an ab parameter. You probably want -n.")
148
+ exit 1
149
+ end
150
+
151
+ # NOTE: parse! removes the options it finds in @arguments. It will leave
152
+ # all unnamed arguments and throw a fit about unknown ones.
153
+ opts.parse!(arguments)
154
+
155
+ if arguments.empty?
156
+ Stella.error("You need to provide a URI")
157
+ exit 1
158
+ elsif arguments.size > 1
159
+ Stella.warn("ab can handle only one URI. The others will be ignored.")
160
+ arguments = arguments.first
161
+ else
162
+ # Let's make sure the URI has a path (at least a trailing slash). Otherwise
163
+ # ab gives a cryptic error.
164
+ begin
165
+ uri = URI.parse(arguments.first)
166
+ if !uri || uri.path.empty?
167
+ Stella.error("ab requires a trailing slash for #{uri.to_s}")
168
+ exit 1
169
+ end
170
+ rescue => ex
171
+ Stella.error("Bad URI: #{arguments.first}")
172
+ exit 1
173
+ end
174
+ end
175
+
176
+ self.arguments = arguments
177
+
178
+ rescue OptionParser::InvalidOption => ex
179
+ # We want to replace this text so we grab just the name of the argument
180
+ badarg = ex.message.gsub('invalid option: ', '')
181
+ raise InvalidArgument.new(badarg)
182
+ end
183
+
184
+ def after
185
+ # We want to maintain copies of all test output, even when the user has
186
+ # supplied other path names so we'll copy the files from the testrun directory
187
+ # to the location specified by the user.
188
+ # NOTE: For tests with more than one test run, the specified files will be
189
+ # overwritten after each run. Should we force append the run number?
190
+ [[@e, 'percentiles'], [@g, 'requests']].each do |tuple|
191
+ if File.expand_path(File.dirname(tuple[0])) != File.expand_path(@working_directory)
192
+ from = tuple[0]
193
+ to = @working_directory + "/ab-#{tuple[1]}.log"
194
+ next unless File.exists?(from)
195
+ FileUtils.cp(from, to)
196
+ end
197
+ end
198
+
199
+
200
+ end
201
+
202
+
203
+
204
+
205
+ def add_header(name, value)
206
+ @H ||= []
207
+ @H << "#{name}: #{value}"
208
+ end
209
+
210
+ def user_agent=(list=[])
211
+ return unless list && !list.empty?
212
+ list = list.to_ary
213
+ list.each do |agent|
214
+ add_header("User-Agent", agent)
215
+ end
216
+ end
217
+
218
+ def vusers
219
+ c || 0
220
+ end
221
+ def vusers=(v)
222
+ ratio = vuser_requests
223
+ @c = v
224
+ @n = ratio * @c
225
+ end
226
+ def requests
227
+ n || 0
228
+ end
229
+ def requests=(v)
230
+ @n = v
231
+ end
232
+ def vuser_requests
233
+ ratio = 1
234
+ # The request ratio tells us how many requests will be
235
+ # generated per vuser. It helps us later when we need to
236
+ # warmp up and ramp up.
237
+ if @n > 0 && @c > 0
238
+ ratio = (@n.to_f / @c.to_f).to_f
239
+ # If concurrency isn't set, we'll assume the total number of requests
240
+ # is intended to be per request
241
+ elsif @n > 0
242
+ ratio = @n
243
+ end
244
+ ratio
245
+ end
246
+ def c
247
+ (@c * @load_factor).to_i
248
+ end
249
+ def n
250
+ (@n * @load_factor).to_i
251
+ end
252
+
253
+ def hosts
254
+ hosts = @arguments || []
255
+ #hosts << get_hosts_from_file
256
+ hosts = hosts.map{ |h| tmp = URI.parse(h.strip); "#{tmp.host}:#{tmp.port}" }
257
+ hosts
258
+ end
259
+
260
+ def paths
261
+ paths = @arguments || []
262
+ #hosts << get_hosts_from_file
263
+ paths = paths.map{ |h| tmp = URI.parse(h.strip); "#{tmp.path}?#{tmp.query}" }
264
+ paths
265
+ end
266
+
267
+
268
+
269
+ # Apache bench writes the summary to STDOUT
270
+ def summary_file
271
+ File.new(stdout_path) if File.exists?(stdout_path)
272
+ end
273
+
274
+ def summary
275
+ return unless summary_file
276
+ raw = {}
277
+ summary_file.each_line { |l|
278
+ l.chomp!
279
+ nvpair = l.split(':')
280
+ next unless nvpair && nvpair.size == 2
281
+ n = nvpair[0].strip.tr(' ', '_').downcase[/\w+/]
282
+ v = nvpair[1].strip[/[\.\d]+/]
283
+
284
+ # Apache Bench outputs two fields with the name "Time per request".
285
+ # We want only the first one so we don't overwrite values.
286
+ raw[n.to_sym] = v.to_f unless raw.has_key? n.to_sym
287
+ }
288
+
289
+ # Document Path: /
290
+ # Document Length: 96 bytes
291
+ #
292
+ # Concurrency Level: 75
293
+ # Time taken for tests: 2.001 seconds
294
+ # Complete requests: 750
295
+ # Failed requests: 0
296
+ # Write errors: 0
297
+ # Total transferred: 174000 bytes
298
+ # HTML transferred: 72000 bytes
299
+ # Requests per second: 374.74 [#/sec] (mean)
300
+ # Time per request: 200.138 [ms] (mean)
301
+ # Time per request: 2.669 [ms] (mean, across all concurrent requests)
302
+ # Transfer rate: 84.90 [Kbytes/sec] received
303
+
304
+ stats = Stella::Test::Run::Summary.new
305
+
306
+ if !raw.empty? && raw.has_key?(:time_taken_for_tests)
307
+
308
+ stats.elapsed_time = raw[:time_taken_for_tests]
309
+
310
+ # We want this in MB, Apache Bench gives Bytes.
311
+ stats.data_transferred = ((raw[:html_transferred] || 0) / 1_048_576)
312
+
313
+ # total_transferred is header data + response data (html_transfered)
314
+ stats.headers_transferred = ((raw[:total_transferred] || 0) / 1_048_576) - stats.data_transferred
315
+
316
+ # Apache Bench returns ms
317
+ stats.response_time = (raw[:time_per_request] || 0) / 1000
318
+ stats.transaction_rate = raw[:requests_per_second]
319
+
320
+ stats.vusers = raw[:concurrency_level].to_i
321
+ stats.successful = raw[:complete_requests].to_i
322
+ stats.failed = raw[:failed_requests].to_i
323
+
324
+ stats.transactions = stats.successful + stats.failed
325
+
326
+ #stats.raw = raw if @global_options.debug
327
+ end
328
+
329
+ stats
330
+ end
331
+
332
+
333
+
334
+ end
335
+
336
+ end
337
+ end
@@ -0,0 +1,106 @@
1
+
2
+
3
+ module Stella::Adapter
4
+ class CommandNotReady < RuntimeError
5
+ def initialize(name="")
6
+ super(Stella::TEXT.msg(:error_adapter_command_not_ready, name))
7
+ end
8
+ end
9
+
10
+ class Base
11
+
12
+
13
+ attr_accessor :working_directory
14
+ attr_reader :load_factor, :arguments
15
+
16
+ def initialize(options={}, arguments=[])
17
+ if options.is_a? Array
18
+ self.process_arguments(options)
19
+ else
20
+ self.options = options
21
+ self.arguments = arguments
22
+ end
23
+ end
24
+
25
+ def load_factor=(load_factor)
26
+ @load_factor = load_factor
27
+ end
28
+ def stdout_path
29
+ File.join(@working_directory, 'STDOUT.txt')
30
+ end
31
+
32
+ def stderr_path
33
+ File.join(@working_directory, 'STDERR.txt')
34
+ end
35
+
36
+ def summary_path(ext='yaml')
37
+ File.join(@working_directory, "SUMMARY.#{ext}")
38
+ end
39
+
40
+ # process_arguments
41
+ #
42
+ # This method must be overridden by the implementing class. This is intended
43
+ # for processing the command-specific command-line arguments
44
+ def process_arguments
45
+ raise Stella::TEXT.msg(:error_class_must_override, 'process_options')
46
+ end
47
+
48
+ # options=
49
+ #
50
+ # Takes a hash, OpenStruct and applies the values to the instance variables.
51
+ # The keys should conincide with with the command line argument names.
52
+ # by process_options first and
53
+ # i.e. The key for --help should be :help
54
+ def options=(options={})
55
+ options = options.marshal_dump if options.is_a? OpenStruct
56
+
57
+ unless options.nil? || options.empty?
58
+ options.each_pair do |name,value|
59
+ next if @private_variables.member?(name)
60
+ Stella::LOGGER.info(:error_class_unknown_argument, name) && next unless self.respond_to?("#{name.to_s}=")
61
+ instance_variable_set("@#{name.to_s}", value)
62
+ end
63
+ end
64
+ end
65
+
66
+ def arguments=(arguments=[])
67
+ @arguments = arguments unless arguments.nil?
68
+ end
69
+
70
+ def available?
71
+ (version.to_f > 0)
72
+ end
73
+
74
+ def name
75
+ @name
76
+ end
77
+
78
+ def rate
79
+ @rate || 0
80
+ end
81
+ def vuser_rate
82
+ "#{vusers}/#{rate}"
83
+ end
84
+
85
+ def command
86
+ raise Stella::TEXT.msg(:error_class_must_override, 'command')
87
+ end
88
+
89
+ def summary
90
+ raise Stella::TEXT.msg(:error_class_must_override, 'summary')
91
+ end
92
+
93
+ def add_header
94
+ raise Stella::TEXT.msg(:error_class_must_override, 'add_header')
95
+ end
96
+
97
+ def user_agent=
98
+ raise Stella::TEXT.msg(:error_class_must_override, 'user_agent=')
99
+ end
100
+
101
+ private
102
+
103
+
104
+ end
105
+ end
106
+