stella 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.txt +135 -0
- data/Rakefile +100 -0
- data/bin/stella +12 -0
- data/lib/stella.rb +58 -0
- data/lib/stella/adapter/ab.rb +303 -0
- data/lib/stella/adapter/base.rb +87 -0
- data/lib/stella/adapter/httperf.rb +296 -0
- data/lib/stella/adapter/siege.rb +321 -0
- data/lib/stella/cli.rb +291 -0
- data/lib/stella/cli/agents.rb +73 -0
- data/lib/stella/cli/base.rb +19 -0
- data/lib/stella/cli/language.rb +18 -0
- data/lib/stella/cli/localtest.rb +80 -0
- data/lib/stella/command/base.rb +111 -0
- data/lib/stella/command/localtest.rb +339 -0
- data/lib/stella/logger.rb +63 -0
- data/lib/stella/response.rb +82 -0
- data/lib/stella/storable.rb +116 -0
- data/lib/stella/support.rb +106 -0
- data/lib/stella/test/base.rb +34 -0
- data/lib/stella/test/definition.rb +79 -0
- data/lib/stella/test/run/summary.rb +50 -0
- data/lib/stella/test/summary.rb +82 -0
- data/lib/stella/text.rb +64 -0
- data/lib/stella/text/resource.rb +39 -0
- data/lib/utils/crypto-key.rb +84 -0
- data/lib/utils/escape.rb +302 -0
- data/lib/utils/fileutil.rb +59 -0
- data/lib/utils/httputil.rb +210 -0
- data/lib/utils/mathutil.rb +78 -0
- data/lib/utils/textgraph.rb +267 -0
- data/lib/utils/timerutil.rb +58 -0
- data/support/text/en.yaml +54 -0
- data/support/text/nl.yaml +1 -0
- data/support/useragents.txt +75 -0
- data/vendor/useragent/MIT-LICENSE +20 -0
- data/vendor/useragent/README +21 -0
- data/vendor/useragent/init.rb +1 -0
- data/vendor/useragent/lib/user_agent.rb +83 -0
- data/vendor/useragent/lib/user_agent/browsers.rb +24 -0
- data/vendor/useragent/lib/user_agent/browsers/all.rb +69 -0
- data/vendor/useragent/lib/user_agent/browsers/gecko.rb +43 -0
- data/vendor/useragent/lib/user_agent/browsers/internet_explorer.rb +40 -0
- data/vendor/useragent/lib/user_agent/browsers/opera.rb +49 -0
- data/vendor/useragent/lib/user_agent/browsers/webkit.rb +94 -0
- data/vendor/useragent/lib/user_agent/comparable.rb +25 -0
- data/vendor/useragent/lib/user_agent/operating_systems.rb +19 -0
- data/vendor/useragent/spec/browsers/gecko_user_agent_spec.rb +209 -0
- data/vendor/useragent/spec/browsers/internet_explorer_user_agent_spec.rb +99 -0
- data/vendor/useragent/spec/browsers/opera_user_agent_spec.rb +59 -0
- data/vendor/useragent/spec/browsers/other_user_agent_spec.rb +19 -0
- data/vendor/useragent/spec/browsers/webkit_user_agent_spec.rb +373 -0
- data/vendor/useragent/spec/spec_helper.rb +1 -0
- data/vendor/useragent/spec/user_agent_spec.rb +331 -0
- data/vendor/useragent/useragent.gemspec +12 -0
- metadata +139 -0
@@ -0,0 +1,87 @@
|
|
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 :arguments, :load_factor, :working_directory, :stats
|
14
|
+
|
15
|
+
def initialize(options={}, arguments=[])
|
16
|
+
self.arguments = arguments
|
17
|
+
self.options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_factor=(load_factor)
|
21
|
+
@load_factor = load_factor
|
22
|
+
end
|
23
|
+
def stdout_path
|
24
|
+
File.join(@working_directory, 'STDOUT.txt')
|
25
|
+
end
|
26
|
+
|
27
|
+
def stderr_path
|
28
|
+
File.join(@working_directory, 'STDERR.txt')
|
29
|
+
end
|
30
|
+
|
31
|
+
def summary_path(ext='yaml')
|
32
|
+
File.join(@working_directory, "SUMMARY.#{ext}")
|
33
|
+
end
|
34
|
+
|
35
|
+
# process_options
|
36
|
+
#
|
37
|
+
# This method must be overridden by the implementing class. This is intended
|
38
|
+
# for processing the command-specific command-line arguments
|
39
|
+
def process_options
|
40
|
+
raise Stella::TEXT.msg(:error_class_must_override, 'process_options')
|
41
|
+
end
|
42
|
+
|
43
|
+
def options=(options={})
|
44
|
+
options = options.marshal_dump if options.is_a? OpenStruct
|
45
|
+
unless options.nil? || options.empty?
|
46
|
+
options.each_pair do |name,value|
|
47
|
+
next if @private_variables.member?(name)
|
48
|
+
Stella::LOGGER.info(:error_class_unknown_argument, name) && next unless self.respond_to?("#{name.to_s}=")
|
49
|
+
instance_variable_set("@#{name.to_s}", value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def arguments=(arguments=[])
|
55
|
+
@arguments = arguments unless arguments.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
def available?
|
59
|
+
(version.to_f > 0)
|
60
|
+
end
|
61
|
+
|
62
|
+
def name
|
63
|
+
@name
|
64
|
+
end
|
65
|
+
|
66
|
+
def rate
|
67
|
+
1
|
68
|
+
end
|
69
|
+
def vuser_rate
|
70
|
+
"#{vusers}/#{rate}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def command
|
74
|
+
raise Stella::TEXT.msg(:error_class_must_override, 'command')
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_header
|
78
|
+
raise Stella::TEXT.msg(:error_class_must_override, 'add_header')
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_agent=
|
82
|
+
raise Stella::TEXT.msg(:error_class_must_override, 'user_agent=')
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,296 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Stella
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
#Usage: httperf [-hdvV] [--add-header S] [--burst-length N] [--client N/N]
|
7
|
+
# [--close-with-reset] [--debug N] [--failure-status N]
|
8
|
+
# [--help] [--hog] [--http-version S] [--max-connections N]
|
9
|
+
# [--max-piped-calls N] [--method S] [--no-host-hdr]
|
10
|
+
# [--num-calls N] [--num-conns N] [--period [d|u|e]T1[,T2]]
|
11
|
+
# [--port N] [--print-reply [header|body]] [--print-request [header|body]]
|
12
|
+
# [--rate X] [--recv-buffer N] [--retry-on-failure] [--send-buffer N]
|
13
|
+
# [--server S] [--server-name S] [--session-cookies]
|
14
|
+
# [--ssl] [--ssl-ciphers L] [--ssl-no-reuse]
|
15
|
+
# [--think-timeout X] [--timeout X] [--uri S] [--verbose] [--version]
|
16
|
+
# [--wlog y|n,file] [--wsess N,N,X] [--wsesslog N,X,file]
|
17
|
+
# [--wset N,X]
|
18
|
+
#
|
19
|
+
class Httperf < Stella::Adapter::Base
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
attr_accessor :hog, :server, :uri, :num_conns, :num_calls, :rate, :timeout, :think_timeout, :port
|
24
|
+
attr_accessor :add_header, :burst_length, :client, :close_with_reset, :debug, :failure_status
|
25
|
+
attr_accessor :help, :http_version, :max_connections, :max_piped_calls, :method, :no_host_hdr
|
26
|
+
attr_accessor :period, :print_reply, :print_request, :recv_buffer, :retry_on_failure, :send_buffer
|
27
|
+
attr_accessor :server_name, :session_cookies, :ssl, :ssl_ciphers, :ssl_no_reuse, :verbose
|
28
|
+
attr_accessor :version, :wlog, :wsess, :wsesslog, :wset
|
29
|
+
|
30
|
+
def initialize(options={}, arguments=[])
|
31
|
+
super(options, arguments)
|
32
|
+
@name = 'httperf'
|
33
|
+
|
34
|
+
@private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory']
|
35
|
+
@load_factor = 1
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
# Before calling run
|
41
|
+
def before
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
def command
|
46
|
+
raise CommandNotReady.new(self.class.to_s) unless ready?
|
47
|
+
|
48
|
+
command = "#{@name} "
|
49
|
+
|
50
|
+
instance_variables.each do |name|
|
51
|
+
canon = name.tr('@', '') # instance_variables returns '@name'
|
52
|
+
next if @private_variables.member?(canon)
|
53
|
+
|
54
|
+
# It's important that we take the value from the getter method
|
55
|
+
# because it applies the load factor.
|
56
|
+
value = self.send(canon)
|
57
|
+
if (value.is_a? Array)
|
58
|
+
value.each { |el| command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(el.to_s)} " }
|
59
|
+
else
|
60
|
+
command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(value.to_s)} "
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
command << (@arguments.map { |uri| "'#{uri}'" }).join(' ') unless @arguments.empty?
|
66
|
+
command
|
67
|
+
end
|
68
|
+
|
69
|
+
# After calling run
|
70
|
+
def after
|
71
|
+
|
72
|
+
|
73
|
+
save_stats
|
74
|
+
end
|
75
|
+
|
76
|
+
#httperf --hog --server=queen --uri=/0k.html --num-conns=10000 --rate=0 --timeout=30 --think-timeout=0
|
77
|
+
def process_options(arguments)
|
78
|
+
|
79
|
+
options = OpenStruct.new
|
80
|
+
opts = OptionParser.new
|
81
|
+
opts.on('--hog') do @hog = true end
|
82
|
+
opts.on('--server=S', String) do |v| @server = v end
|
83
|
+
opts.on('--server-name=S', String) do |v| @server_name = v end
|
84
|
+
opts.on('--port=N', Integer) do |v| @port = v end
|
85
|
+
opts.on('--uri=S', String) do |v| @uri = v end
|
86
|
+
opts.on('--num-conns=N', Integer) do |v| @num_conns = v end
|
87
|
+
opts.on('--num-calls=N', Integer) do |v| @num_calls = v end
|
88
|
+
opts.on('--rate=N', Integer) do |v| @rate = v end
|
89
|
+
opts.on('--timeout=N', Integer) do |v| @timeout = v end
|
90
|
+
opts.on('--think-timeout=N', Integer) do |v| @think_timeout = v end
|
91
|
+
|
92
|
+
opts.on('-h', '--help') do |v| @help = true end
|
93
|
+
opts.on('-v', '--verbose') do |v| @verbose = true end
|
94
|
+
opts.on('-V', '--version') do |v| @version = true end
|
95
|
+
opts.on('--close-with-reset') do |v| @close_with_reset = true end
|
96
|
+
opts.on('--session-cookies') do |v| @session_cookies = true end
|
97
|
+
opts.on('--ssl') do |v| @ssl = true end
|
98
|
+
opts.on('--ssl-ciphers') do |v| @ssl_ciphers = true end
|
99
|
+
opts.on('--ssl-no-reuse') do |v| @ssl_no_reuse = true end
|
100
|
+
opts.on('--no-host-hdr') do |v| @no_host_hdr = true end
|
101
|
+
opts.on('--retry-on-failure') do |v| @retry_on_failure = true end
|
102
|
+
|
103
|
+
opts.on('--add-header=S', String) do |v| @add_header ||= []; @add_header << v; end
|
104
|
+
opts.on('--burst-length=N', Integer) do |v| @burst_length = v end
|
105
|
+
opts.on('--client=S', String) do |v| @client = v end
|
106
|
+
opts.on('-d N', '--debug=N', Integer) do |v| @debug ||= 0; @debug = v end
|
107
|
+
opts.on('--failure-status=N', Integer) do |v| @failure_status = v end
|
108
|
+
|
109
|
+
opts.on('--http-version=S', String) do |v| @http_version = v end
|
110
|
+
|
111
|
+
opts.on('--max-connections=N', Integer) do |v| @max_connections = v end
|
112
|
+
opts.on('--max-piped-calls=N', Integer) do |v| @max_piped_calls = v end
|
113
|
+
opts.on('--method=S', String) do |v| @method = v end
|
114
|
+
|
115
|
+
opts.on('--period=S', String) do |v| @period = v end # TODO: Requires parsing
|
116
|
+
opts.on('--print-reply=[S]', String) do |v| @print_reply = v end
|
117
|
+
opts.on('--print-request=[S]', String) do |v| @print_request = v end
|
118
|
+
|
119
|
+
opts.on('--recv-buffer=N', Integer) do |v| @recv_buffer = v end
|
120
|
+
opts.on('--send-buffer=N', Integer) do |v| @send_buffer = v end
|
121
|
+
|
122
|
+
|
123
|
+
opts.on('--wlog=S', String) do |v| @wlog = Stella::Util::expand_str(v) end
|
124
|
+
opts.on('--wsess=S', String) do |v| @wsess = Stella::Util::expand_str(v) end
|
125
|
+
opts.on('--wsesslog=S', String) do |v| @wsesslog = Stella::Util::expand_str(v) end
|
126
|
+
opts.on('--wset=S', String) do |v| @wset = Stella::Util::expand_str(v) end
|
127
|
+
|
128
|
+
# parse! removes the options it finds.
|
129
|
+
# It also fails when it finds unknown switches (i.e. -X)
|
130
|
+
# Which should leave only the remaining arguments (URIs in this case)
|
131
|
+
opts.parse!(arguments)
|
132
|
+
|
133
|
+
|
134
|
+
options
|
135
|
+
rescue OptionParser::InvalidOption => ex
|
136
|
+
# We want to replace this text so we grab just the name of the argument
|
137
|
+
badarg = ex.message.gsub('invalid option: ', '')
|
138
|
+
raise InvalidArgument.new(badarg)
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def version
|
143
|
+
vsn = 0
|
144
|
+
text = ""
|
145
|
+
Open3.popen3("#{@name} --version") do |stdin, stdout, stderr|
|
146
|
+
text = stdout.readlines.join
|
147
|
+
text.scan(/httperf\-([\d\.]+)\s/) { |v| vsn = v[0] }
|
148
|
+
end
|
149
|
+
vsn
|
150
|
+
end
|
151
|
+
|
152
|
+
# loadtest
|
153
|
+
#
|
154
|
+
# True or false: is the call to siege a load test? If it's a call to help or version or
|
155
|
+
# to display the config this with return false. It's no reason for someone to make this
|
156
|
+
# call through Stella but it's here for goodness sake.
|
157
|
+
def loadtest?
|
158
|
+
@uri && !@uri.empty?
|
159
|
+
end
|
160
|
+
def ready?
|
161
|
+
@name && !instance_variables.empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
def add_header(name=false, value=false)
|
165
|
+
# This is a hack since we have an instance variable called add_header.
|
166
|
+
# I figure this is the best of two evils because I'd rather keep the
|
167
|
+
# instance variable naming consistent.
|
168
|
+
return @add_header if !name && !value
|
169
|
+
@add_header ||= []
|
170
|
+
@add_header << "#{name}: #{value}"
|
171
|
+
end
|
172
|
+
|
173
|
+
def user_agent=(list=[])
|
174
|
+
return unless list && !list.empty?
|
175
|
+
list = list.to_ary
|
176
|
+
list.each do |agent|
|
177
|
+
add_header("User-Agent", agent)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
def vusers
|
181
|
+
@rate
|
182
|
+
end
|
183
|
+
def vusers=(v)
|
184
|
+
0
|
185
|
+
end
|
186
|
+
def requests
|
187
|
+
@num_conns # TODO: also check wsess and wlog params
|
188
|
+
end
|
189
|
+
def requests=(v)
|
190
|
+
0
|
191
|
+
end
|
192
|
+
def vuser_requests
|
193
|
+
0
|
194
|
+
end
|
195
|
+
def wsess
|
196
|
+
@wsess.join(',')
|
197
|
+
end
|
198
|
+
|
199
|
+
def wset
|
200
|
+
@wset.join(',')
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
def wsesslog
|
205
|
+
@wsesslog.join(',')
|
206
|
+
end
|
207
|
+
def wlog
|
208
|
+
@wlog.join(',')
|
209
|
+
end
|
210
|
+
|
211
|
+
#def concurrent
|
212
|
+
# (@concurrent * @load_factor).to_i
|
213
|
+
#end
|
214
|
+
#def concurrent_f
|
215
|
+
# (@concurrent * @load_factor).to_f
|
216
|
+
#end
|
217
|
+
#def reps
|
218
|
+
# @reps
|
219
|
+
#end
|
220
|
+
|
221
|
+
|
222
|
+
|
223
|
+
# Siege writes the summary to STDERR
|
224
|
+
def stats_file
|
225
|
+
File.new(stdout_path)
|
226
|
+
end
|
227
|
+
|
228
|
+
def rc_file
|
229
|
+
File.join(@working_directory, "siegerc")
|
230
|
+
end
|
231
|
+
|
232
|
+
def log_file
|
233
|
+
File.join(@working_directory, "siege.log")
|
234
|
+
end
|
235
|
+
|
236
|
+
def uris_file
|
237
|
+
File.join(@working_directory, File.basename(@file))
|
238
|
+
end
|
239
|
+
|
240
|
+
# httperf --hog --timeout=30 --client=0/1 --server=127.0.0.1 --port=5600 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=5 --num-calls=1
|
241
|
+
# httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
|
242
|
+
# Maximum connect burst length: 1
|
243
|
+
#
|
244
|
+
# Total: connections 5 requests 5 replies 5 test-duration 0.513 s
|
245
|
+
#
|
246
|
+
# Connection rate: 9.7 conn/s (102.7 ms/conn, <=1 concurrent connections)
|
247
|
+
# Connection time [ms]: min 102.1 avg 102.7 max 104.1 median 102.5 stddev 0.8
|
248
|
+
# Connection time [ms]: connect 0.2
|
249
|
+
# Connection length [replies/conn]: 1.000
|
250
|
+
#
|
251
|
+
# Request rate: 9.7 req/s (102.7 ms/req)
|
252
|
+
# Request size [B]: 62.0
|
253
|
+
#
|
254
|
+
# Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
|
255
|
+
# Reply time [ms]: response 102.3 transfer 0.1
|
256
|
+
# Reply size [B]: header 136.0 content 96.0 footer 0.0 (total 232.0)
|
257
|
+
# Reply status: 1xx=0 2xx=5 3xx=0 4xx=0 5xx=0
|
258
|
+
#
|
259
|
+
# CPU time [s]: user 0.12 system 0.39 (user 22.5% system 75.3% total 97.8%)
|
260
|
+
# Net I/O: 2.8 KB/s (0.0*10^6 bps)
|
261
|
+
#
|
262
|
+
# Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
|
263
|
+
# Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
|
264
|
+
|
265
|
+
def stats
|
266
|
+
return unless stats_file
|
267
|
+
|
268
|
+
raw = stats_file.readlines.join
|
269
|
+
stats = Stella::Test::Run::Summary.new
|
270
|
+
|
271
|
+
raw.scan(/Request rate: (\d+?\.\d+?) req.s .(\d+?\.\d+?) ms.req./) do |rate,time|
|
272
|
+
stats.transaction_rate = rate.to_f
|
273
|
+
stats.response_time = (time.to_f) / 1000
|
274
|
+
end
|
275
|
+
|
276
|
+
raw.scan(/connections (\d+?) requests (\d+?) replies (\d+?) test-duration (\d+\.\d+?) s/) do |conn,req,rep,time|
|
277
|
+
stats.elapsed_time = time.to_f
|
278
|
+
stats.successful = rep.to_i
|
279
|
+
stats.failed = conn.to_i - rep.to_i # maybe this should be from the Error line
|
280
|
+
stats.transactions = conn.to_i
|
281
|
+
end
|
282
|
+
|
283
|
+
raw.scan(/Reply size [B]: header (\d+\.\d+?) content (\d+\.\d+?) footer (\d+\.\d+?) .total (\d+\.\d+?)./) do |h,c,f,t|
|
284
|
+
stats.data_transferred = ((t.to_f || 0 ) / 1_048_576).to_f # TODO: convert from bytes to MB
|
285
|
+
end
|
286
|
+
stats.vusers = self.vusers
|
287
|
+
|
288
|
+
|
289
|
+
stats
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Stella
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
# SIEGE
|
7
|
+
# Usage: siege [options]
|
8
|
+
# siege [options] URL
|
9
|
+
# siege -g URL
|
10
|
+
# Options:
|
11
|
+
# -V, --version VERSION, prints version number to screen.
|
12
|
+
# -h, --help HELP, prints this section.
|
13
|
+
# -C, --config CONFIGURATION, show the current configuration.
|
14
|
+
# -v, --verbose VERBOSE, prints notification to screen.
|
15
|
+
# -g, --get GET, pull down headers from the server and display HTTP
|
16
|
+
# transaction. Great for web application debugging.
|
17
|
+
# -c, --concurrent=NUM CONCURRENT users, default is 10
|
18
|
+
# -u, --url="URL" Deprecated. Set URL as the last argument.
|
19
|
+
# -i, --internet INTERNET user simulation, hits the URLs randomly.
|
20
|
+
# -b, --benchmark BENCHMARK, signifies no delay for time testing.
|
21
|
+
# -t, --time=NUMm TIME based testing where "m" is the modifier S, M, or H
|
22
|
+
# no space between NUM and "m", ex: --time=1H, one hour test.
|
23
|
+
# -r, --reps=NUM REPS, number of times to run the test, default is 25
|
24
|
+
# -f, --file=FILE FILE, change the configuration file to file.
|
25
|
+
# -R, --rc=FILE RC, change the siegerc file to file. Overrides
|
26
|
+
# the SIEGERC environmental variable.
|
27
|
+
# -l, --log LOG, logs the transaction to PREFIX/var/siege.log
|
28
|
+
# -m, --mark="text" MARK, mark the log file with a string separator.
|
29
|
+
# -d, --delay=NUM Time DELAY, random delay between 1 and num designed
|
30
|
+
# to simulate human activity. Default value is 3
|
31
|
+
# -H, --header="text" Add a header to request (can be many)
|
32
|
+
# -A, --user-agent="text" Sets User-Agent in request
|
33
|
+
class Siege < Stella::Adapter::Base
|
34
|
+
|
35
|
+
|
36
|
+
attr_accessor :version, :help, :config, :verbose, :get, :log, :mark, :delay, :header, :user_agent
|
37
|
+
attr_accessor :reps, :concurrent, :rc, :file, :time, :benchmark, :internet
|
38
|
+
|
39
|
+
def initialize(options={}, arguments=[])
|
40
|
+
super(options, arguments)
|
41
|
+
@name = 'siege'
|
42
|
+
@reps = 1
|
43
|
+
@concurrent = 1
|
44
|
+
@rc = File.join(ENV['HOME'], '.siegerc')
|
45
|
+
@private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory', 'orig_logfile']
|
46
|
+
@load_factor = 1
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def version
|
51
|
+
vsn = 0
|
52
|
+
text = ""
|
53
|
+
Open3.popen3("#{@name} --version") do |stdin, stdout, stderr|
|
54
|
+
text = stderr.readlines.join
|
55
|
+
text.scan(/SIEGE (\d+?\.\d+)/) { |v| vsn = v[0] }
|
56
|
+
end
|
57
|
+
vsn
|
58
|
+
end
|
59
|
+
|
60
|
+
# loadtest
|
61
|
+
#
|
62
|
+
# True or false: is the call to siege a load test? If it's a call to help or version or
|
63
|
+
# to display the config this with return false. It's no reason for someone to make this
|
64
|
+
# call through Stella but it's here for goodness sake.
|
65
|
+
def loadtest?
|
66
|
+
!@arguments.empty? # The argument is a URI
|
67
|
+
end
|
68
|
+
|
69
|
+
def ready?
|
70
|
+
@name && !instance_variables.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Before calling run
|
75
|
+
def before
|
76
|
+
|
77
|
+
# Keep a copy of the configuration file.
|
78
|
+
copy_siegerc
|
79
|
+
|
80
|
+
# Keep a copy of the URLs file.
|
81
|
+
copy_urls_file if @file
|
82
|
+
|
83
|
+
# TODO: Print message about neither --benchmark or --internet
|
84
|
+
end
|
85
|
+
def command
|
86
|
+
raise CommandNotReady.new(self.class.to_s) unless ready?
|
87
|
+
|
88
|
+
command = "#{@name} "
|
89
|
+
|
90
|
+
instance_variables.each do |name|
|
91
|
+
canon = name.tr('@', '') # instance_variables returns '@name'
|
92
|
+
next if @private_variables.member?(canon)
|
93
|
+
|
94
|
+
# It's important that we take the value from the getter method
|
95
|
+
# because it applies the load factor.
|
96
|
+
value = self.send(canon)
|
97
|
+
if (value.is_a? Array)
|
98
|
+
value.each { |el| command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(el.to_s)} " }
|
99
|
+
else
|
100
|
+
command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(value.to_s)} "
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
command << (@arguments.map { |uri| "'#{uri}'" }).join(' ') unless @arguments.empty?
|
106
|
+
command
|
107
|
+
end
|
108
|
+
|
109
|
+
# After calling run
|
110
|
+
def after
|
111
|
+
|
112
|
+
update_orig_logfile if @orig_logfile
|
113
|
+
|
114
|
+
save_stats
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def process_options(arguments)
|
119
|
+
options = OpenStruct.new
|
120
|
+
opts = OptionParser.new
|
121
|
+
opts.on('-V', '--version') do |v| @version = v end
|
122
|
+
opts.on('-h', '--help') do |v| @help = v end
|
123
|
+
opts.on('-C', '--config') do |v| @config = v end
|
124
|
+
opts.on('-v', '--verbose') do |v| @verbose = v end
|
125
|
+
opts.on('-g', '--get') do |v| @get = v end
|
126
|
+
opts.on('-l', '--log') do |v| @log = v end
|
127
|
+
opts.on('-m S', '--mark=S', String) do |v| @mark = v end
|
128
|
+
opts.on('-d N', '--delay=N', Float) do |v| @delay = v end
|
129
|
+
opts.on('-H S', '--header=S', String) do |v| @header ||= []; @header << v end
|
130
|
+
|
131
|
+
opts.on('-r N', '--reps=N', Integer) do |v| @reps = v.to_i end
|
132
|
+
opts.on('-c N', '--concurrent=N', Integer) do |v| @concurrent = v.to_i end
|
133
|
+
opts.on('-R S', '--rc=S', String) do |v| @rc = v end
|
134
|
+
opts.on('-f S', '--file=S', String) do |v| @file = v end
|
135
|
+
opts.on('-t S', '--time=S', String) do |v| @time = v end
|
136
|
+
opts.on('-b', '--benchmark') do |v| @benchmark = true; end
|
137
|
+
opts.on('-i', '--internet') do |v| @internet = true; end
|
138
|
+
opts.on('-A S', '--user-agent=S', String) do |v| @user_agent ||= []; @user_agent << v end
|
139
|
+
|
140
|
+
raise "You cannot select both --internet and --benchmark" if options.internet && options.benchmark
|
141
|
+
|
142
|
+
# parse! removes the options it finds.
|
143
|
+
# It also fails when it finds unknown switches (i.e. -X)
|
144
|
+
# Which should leave only the remaining arguments (URIs in this case)
|
145
|
+
opts.parse!(arguments)
|
146
|
+
options
|
147
|
+
rescue OptionParser::InvalidOption => ex
|
148
|
+
# We want to replace this text so we grab just the name of the argument
|
149
|
+
badarg = ex.message.gsub('invalid option: ', '')
|
150
|
+
raise InvalidArgument.new(badarg)
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def add_header(name, value)
|
155
|
+
@header ||= []
|
156
|
+
@header << "#{name}: #{value}"
|
157
|
+
end
|
158
|
+
def user_agent=(list=[])
|
159
|
+
return unless list && !list.empty?
|
160
|
+
list = list.to_ary
|
161
|
+
@user_agent ||= []
|
162
|
+
@user_agent << list
|
163
|
+
@user_agent.flatten
|
164
|
+
end
|
165
|
+
|
166
|
+
def vusers
|
167
|
+
concurrent || 0
|
168
|
+
end
|
169
|
+
def vusers=(v)
|
170
|
+
@concurrent = v
|
171
|
+
end
|
172
|
+
def requests
|
173
|
+
(@reps * concurrent_f).to_i
|
174
|
+
end
|
175
|
+
def requests=(v)
|
176
|
+
@reps = (v / concurrent_f).to_i
|
177
|
+
end
|
178
|
+
def vuser_requests
|
179
|
+
@reps
|
180
|
+
end
|
181
|
+
|
182
|
+
def concurrent
|
183
|
+
(@concurrent * @load_factor).to_i
|
184
|
+
end
|
185
|
+
def concurrent_f
|
186
|
+
(@concurrent * @load_factor).to_f
|
187
|
+
end
|
188
|
+
def reps
|
189
|
+
@reps
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Take the last line of the siege.log file and write it to the log file
|
194
|
+
# specified by the user. We don't this so running with Stella is
|
195
|
+
# identical to running it standalone
|
196
|
+
def update_orig_logfile
|
197
|
+
|
198
|
+
return unless (@orig_logfile)
|
199
|
+
log_str = FileUtil.read_file_to_array(log_file) || ''
|
200
|
+
return if log_str.empty?
|
201
|
+
|
202
|
+
if File.exists?(@orig_logfile)
|
203
|
+
FileUtil.append_file(@orig_logfile, log_str[-1], true)
|
204
|
+
else
|
205
|
+
FileUtil.write_file(@orig_logfile, log_str.join(''), true)
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# We want to keep a copy of the configuration file and also
|
211
|
+
# modify it a little bit to make sure we get all the mad info from siege
|
212
|
+
def copy_siegerc
|
213
|
+
|
214
|
+
# Read in the siegerc file so we can manipulate it
|
215
|
+
siegerc_str = FileUtil.read_file(File.expand_path(@rc))
|
216
|
+
|
217
|
+
siegerc_vars = {
|
218
|
+
:verbose => [false, true], # We want to maximize the data output by siege
|
219
|
+
:logging => [false, true],
|
220
|
+
:csv => [false, true]
|
221
|
+
}
|
222
|
+
|
223
|
+
#if (@agent)
|
224
|
+
# siegerc_vars['user-agent'] = ['.*', 'dogs2']
|
225
|
+
#end
|
226
|
+
|
227
|
+
# We'll set the variables in the siegerc file
|
228
|
+
siegerc_vars.each_pair do |var,value|
|
229
|
+
siegerc_str.gsub!(/#{var}\s*=\s*#{value[0]}/, "#{var} = #{value[1]}") # make true
|
230
|
+
siegerc_str.gsub!(/^\#+\s*#{var}/, "#{var}") # remove comment
|
231
|
+
end
|
232
|
+
|
233
|
+
# Look for the enabled logile path
|
234
|
+
# We will use this later to update it from the last line in our copy
|
235
|
+
siegerc_str =~ /^\s*logfile\s*=\s*(.+?)$/
|
236
|
+
@orig_logfile = $1 || nil
|
237
|
+
|
238
|
+
# Replace all environment variables with literal values
|
239
|
+
@orig_logfile.gsub!(/\$\{#{$1}\}/, ENV[$1]) while (@orig_logfile =~ /\$\{(.+?)\}/ && ENV.has_key?($1))
|
240
|
+
|
241
|
+
@orig_logfile = File.expand_path(@orig_logfile) if @orig_logfile
|
242
|
+
|
243
|
+
|
244
|
+
siegerc_str.gsub!(/^\#*\s*logfile\s*=\s*.*?$/, "logfile = " + log_file)
|
245
|
+
|
246
|
+
FileUtil.write_file(rc_file, siegerc_str, true)
|
247
|
+
@rc = rc_file
|
248
|
+
end
|
249
|
+
|
250
|
+
# We want to keep a copy of the URLs file too
|
251
|
+
def copy_urls_file
|
252
|
+
if @file
|
253
|
+
File.copy(File.expand_path(@file), uris_file)
|
254
|
+
@file = uris_file
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Siege writes the summary to STDERR
|
259
|
+
def stats_file
|
260
|
+
File.new(stderr_path)
|
261
|
+
end
|
262
|
+
|
263
|
+
def rc_file
|
264
|
+
File.join(@working_directory, "siegerc")
|
265
|
+
end
|
266
|
+
|
267
|
+
def log_file
|
268
|
+
File.join(@working_directory, "siege.log")
|
269
|
+
end
|
270
|
+
|
271
|
+
def uris_file
|
272
|
+
File.join(@working_directory, File.basename(@file))
|
273
|
+
end
|
274
|
+
|
275
|
+
def stats
|
276
|
+
return unless stats_file
|
277
|
+
raw = {}
|
278
|
+
stats_file.each_line { |l|
|
279
|
+
l.chomp!
|
280
|
+
nvpair = l.split(':')
|
281
|
+
next unless nvpair && nvpair.size == 2
|
282
|
+
n = nvpair[0].strip.tr(' ', '_').downcase[/\w+/]
|
283
|
+
v = nvpair[1].strip[/[\.\d]+/]
|
284
|
+
raw[n.to_sym] = v.to_f
|
285
|
+
}
|
286
|
+
|
287
|
+
stats = Stella::Test::Run::Summary.new
|
288
|
+
|
289
|
+
# Transactions: 750 hits
|
290
|
+
# Availability: 100.00 %
|
291
|
+
# Elapsed time: 2.33 secs
|
292
|
+
# Data transferred: 0.07 MB
|
293
|
+
# Response time: 0.21 secs
|
294
|
+
# Transaction rate: 321.89 trans/sec
|
295
|
+
# Throughput: 0.03 MB/sec
|
296
|
+
# Concurrency: 67.49
|
297
|
+
# Successful transactions: 750
|
298
|
+
# Failed transactions: 0
|
299
|
+
# Longest transaction: 0.33
|
300
|
+
# Shortest transaction: 0.10
|
301
|
+
|
302
|
+
stats.vusers = raw[:concurrency]
|
303
|
+
stats.data_transferred = raw[:data_transferred]
|
304
|
+
stats.elapsed_time = raw[:elapsed_time]
|
305
|
+
stats.response_time = raw[:response_time]
|
306
|
+
stats.transactions = raw[:transactions].to_i
|
307
|
+
stats.transaction_rate = raw[:transaction_rate]
|
308
|
+
stats.failed = raw[:failed_transactions].to_i
|
309
|
+
stats.successful = raw[:successful_transactions].to_i
|
310
|
+
|
311
|
+
#stats.shortest_transaction = raw[:shortest_transaction]
|
312
|
+
#stats.longest_transaction = raw[:longest_transaction]
|
313
|
+
|
314
|
+
stats
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|