solutious-stella 0.5.5

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