wasp 0.2.0
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/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +15 -0
- data/bin/wasp +6 -0
- data/config/aws.yml +4 -0
- data/lib/wasp.rb +14 -0
- data/lib/wasp/config.rb +11 -0
- data/lib/wasp/const.rb +21 -0
- data/lib/wasp/core_ext.rb +113 -0
- data/lib/wasp/ec2.rb +355 -0
- data/lib/wasp/nest.rb +321 -0
- data/lib/wasp/queenwasp.rb +7 -0
- data/lib/wasp/stingab.rb +355 -0
- data/lib/wasp/wasp.rb +514 -0
- data/report/makeplot.rb +131 -0
- data/test/test_wasp.rb +9 -0
- data/wasp.gemspec +38 -0
- metadata +112 -0
data/lib/wasp/nest.rb
ADDED
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require File.dirname(__FILE__) + '/wasp'
|
3
|
+
|
4
|
+
module WASP
|
5
|
+
|
6
|
+
class Nest
|
7
|
+
attr_reader :key
|
8
|
+
attr_reader :options
|
9
|
+
attr_reader :args
|
10
|
+
attr_reader :wasps
|
11
|
+
attr_reader :queenwasp
|
12
|
+
attr_reader :optionhelp
|
13
|
+
|
14
|
+
def info
|
15
|
+
puts "be aware of me"
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_options!
|
19
|
+
|
20
|
+
opts_parser = OptionParser.new do |opts|
|
21
|
+
opts.banner = "\nAvailable options:\n\n"
|
22
|
+
|
23
|
+
opts.on('-k', '--key PRIVATE-KEY',
|
24
|
+
'The ssh key pair name to use to connect to the new servers.') do |key|
|
25
|
+
@options[:key] = key
|
26
|
+
end
|
27
|
+
opts.on('-s', '--servers NUM-SERVERS',
|
28
|
+
'The number of servers to start (default: 5).') do |server|
|
29
|
+
@options[:server] = server
|
30
|
+
end
|
31
|
+
opts.on('-g', '--group SECURITY-GROUP',
|
32
|
+
'The security group to run the instances under (default: default).') do |group|
|
33
|
+
@options[:group] = group
|
34
|
+
end
|
35
|
+
opts.on('-z', '--zone AVAILABILITY-ZONE',
|
36
|
+
"The availability zone to start the instances in (default: us-east-1).") do |zone|
|
37
|
+
@options[:zone] = zone
|
38
|
+
end
|
39
|
+
opts.on('-a', '--ami AMI',
|
40
|
+
"The ami-id to use for each server from (default: ami-bfb473d6).") do |ami|
|
41
|
+
@options[:ami] = ami
|
42
|
+
end
|
43
|
+
opts.on('-u', '--url URL', 'URL of the target to attack.') do |url|
|
44
|
+
@options[:url] = url
|
45
|
+
end
|
46
|
+
opts.on('-p', '--pattern PATTERN',
|
47
|
+
'The pattern of concurrent wasps are growing and time (default: 5000(wasps):60(secs)).') do |pattern|
|
48
|
+
@options[:pattern] = pattern
|
49
|
+
end
|
50
|
+
opts.on('-t', '--time TIME',
|
51
|
+
'The time to make to the target (seconds).') do |time|
|
52
|
+
@options[:time] = time
|
53
|
+
end
|
54
|
+
opts.on('-i', '--cookie',
|
55
|
+
'The request doesn\'t include a cookie which have fake session id. (default: with sessionID).') do
|
56
|
+
@options[:wo_cookie] = true
|
57
|
+
end
|
58
|
+
opts.on('-H', '--header HEADER',
|
59
|
+
'Append extra headers to the request. (i.e.: "Accep-Encoding: zip/zop;8bit").') do |header|
|
60
|
+
@options[:header] = header
|
61
|
+
end
|
62
|
+
opts.on('-n', '--numreq NUM-REQUEST',
|
63
|
+
'The number of total connections to make to the target (default: 1000).') do |num|
|
64
|
+
@options[:num] = num
|
65
|
+
end
|
66
|
+
opts.on('-c', '--concurrent CONCURRENT',
|
67
|
+
'The number of concurrent connections to make to the target (default: 100).') do |conn|
|
68
|
+
@options[:conn] = conn
|
69
|
+
end
|
70
|
+
opts.on('-l', '--login LOGINID',
|
71
|
+
'The ssh username name to use to connect to the new servers (default: ubuntu).') do |login|
|
72
|
+
@options[:login] = login
|
73
|
+
end
|
74
|
+
opts.on('-w', '--weapon WEAPON',
|
75
|
+
'The name of weapon to attack (default: ab).') do |weapon|
|
76
|
+
@options[:weapon] = weapon
|
77
|
+
end
|
78
|
+
opts.on('-e', '--keepalive', 'Use keep-alive option for weapon.') do
|
79
|
+
@options[:keepalive] = true
|
80
|
+
end
|
81
|
+
opts.on('-o', '--compact', 'Use compact version of results.') do
|
82
|
+
@options[:compact] = true
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on('-v', '--version') { puts "wasp " + "#{WASP::Const::VERSION}\n".green; exit(true) }
|
86
|
+
opts.on('-h', '--help') { @optionhelp = true; help }
|
87
|
+
|
88
|
+
opts.on_tail('--options') { puts "#{opts}\n" }
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
@args = opts_parser.parse!(@args)
|
93
|
+
rescue => ex
|
94
|
+
print "[WARN]".yellow + " #{ex.message}\n"
|
95
|
+
@optionhelp = true
|
96
|
+
help
|
97
|
+
end
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def command_usage
|
102
|
+
puts "Usage:".green + " wasps COMMAND [options]"
|
103
|
+
<<-USAGE
|
104
|
+
|
105
|
+
Wasps with Rain of Stings (ruby version of waspswithmachineguns)
|
106
|
+
|
107
|
+
A utility for arming (creating) many wasps (small EC2 instances) to attack
|
108
|
+
(load test) targets (web applications).
|
109
|
+
|
110
|
+
commands:
|
111
|
+
set Set credential file for AWS.
|
112
|
+
up Start a batch of load testing servers.
|
113
|
+
equip Check and install weapon to wasps.
|
114
|
+
attack Begin the attack on a specific url.
|
115
|
+
rattack Begin the attack incrementally growing up wasps during the period.
|
116
|
+
down Shutdown and deactivate the load testing servers.
|
117
|
+
status Report the status of the load testing servers.
|
118
|
+
regions Get AWS regions for the wasps.
|
119
|
+
help Show options.
|
120
|
+
|
121
|
+
To set config file:
|
122
|
+
$ wasp set aws.yml
|
123
|
+
|
124
|
+
To launch 6 wasps:
|
125
|
+
- launch 6 instances in us-east zone with private key named wasps
|
126
|
+
$ wasp up -k wasps -s 6
|
127
|
+
|
128
|
+
- launch 5 instances in us-west-2 zone with ami-8cb33ebc AMI, username ubuntu and private key named wasps
|
129
|
+
$ wasp up -k wasps -z us-west-2 -a ami-8cb33ebc -s 5 -l ubuntu
|
130
|
+
|
131
|
+
To equip weapon(apachebench):
|
132
|
+
$ wasp equip -w ab
|
133
|
+
|
134
|
+
To attack target with 1000 requests and 100 concurrent wasps:
|
135
|
+
$ wasp attack -n 1000 -c 100 -u http://target_site
|
136
|
+
|
137
|
+
To attack target with incrementally increase wasps from 1 to 10000 during 60 seconds:
|
138
|
+
$ wasp rattack -p 10000:60 -u http://target_site
|
139
|
+
|
140
|
+
To sleep wasps:
|
141
|
+
$ wasp down
|
142
|
+
|
143
|
+
USAGE
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
def lost_wasps
|
148
|
+
File.exists?("#{ENV["HOME"]}/.nest")
|
149
|
+
false
|
150
|
+
end
|
151
|
+
|
152
|
+
def parse_command!
|
153
|
+
verb = @args.shift
|
154
|
+
case verb
|
155
|
+
|
156
|
+
when 'set'
|
157
|
+
file = @args.shift
|
158
|
+
|
159
|
+
help('nofile') if file.nil?
|
160
|
+
|
161
|
+
file = File.absolute_path(file)
|
162
|
+
|
163
|
+
begin
|
164
|
+
FileUtils.cp(file, ENV["HOME"] + "/.waspaws.yml")
|
165
|
+
rescue => ex
|
166
|
+
puts "#{ex.message}"
|
167
|
+
exit false
|
168
|
+
end
|
169
|
+
puts "AWS credential is set."
|
170
|
+
|
171
|
+
when 'up'
|
172
|
+
if lost_wasps then
|
173
|
+
puts "[WARN]".yellow + " There are lost wasps in the air. They need to go home first. [./nest down]"
|
174
|
+
exit(false)
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
if @options[:key].nil? then
|
179
|
+
help("nokey")
|
180
|
+
end
|
181
|
+
|
182
|
+
puts 'Breeding wasps..'
|
183
|
+
|
184
|
+
@wasps = WASP::Wasp.new(@options)
|
185
|
+
@wasps.ready
|
186
|
+
@wasps.breed
|
187
|
+
|
188
|
+
#puts 'Breeding queen wasp..'
|
189
|
+
#@queenwasp = WASP::QueenWasp.new(num_wasps)
|
190
|
+
|
191
|
+
when 'equip'
|
192
|
+
puts "Check wasps weapon.."
|
193
|
+
|
194
|
+
@wasps = WASP::Wasp.new(@options)
|
195
|
+
@wasps.equip
|
196
|
+
|
197
|
+
when 'rattack'
|
198
|
+
puts "Connecting to the nest"
|
199
|
+
|
200
|
+
if @options[:url].nil? then
|
201
|
+
help("nourl")
|
202
|
+
end
|
203
|
+
|
204
|
+
if @options[:pattern].nil? then
|
205
|
+
@options[:pattern] = "5000:60"
|
206
|
+
end
|
207
|
+
|
208
|
+
pattern = @options[:pattern]
|
209
|
+
to, time, keep = pattern.split(":")
|
210
|
+
|
211
|
+
help if time.nil? or to.nil?
|
212
|
+
|
213
|
+
if keep == 'keep' then
|
214
|
+
keep = true
|
215
|
+
else
|
216
|
+
keep = false
|
217
|
+
end
|
218
|
+
@wasps = WASP::Wasp.new(@options)
|
219
|
+
@wasps.rangeattack(to.to_i, time.to_i, @options[:url], keep)
|
220
|
+
|
221
|
+
when 'attack'
|
222
|
+
puts "Connecting to the nest"
|
223
|
+
|
224
|
+
if @options[:url].nil? then
|
225
|
+
help("nourl")
|
226
|
+
end
|
227
|
+
|
228
|
+
num = if @options[:num].nil? then
|
229
|
+
WASP::Const::DEFAULT_NUMBER_OF_REQUESTS
|
230
|
+
else
|
231
|
+
@options[:num].to_i
|
232
|
+
end
|
233
|
+
|
234
|
+
time = @options[:time]
|
235
|
+
|
236
|
+
# number of requests would be ignored if time parameter have given
|
237
|
+
num = nil if not time.nil?
|
238
|
+
|
239
|
+
conn = if @options[:conn].nil? then
|
240
|
+
WASP::Const::DEFAULT_CONCURRENT_OF_CONNECTIONS
|
241
|
+
else
|
242
|
+
@options[:conn].to_i
|
243
|
+
end
|
244
|
+
|
245
|
+
@wasps = WASP::Wasp.new(@options)
|
246
|
+
@wasps.attack(num, conn, @options[:url], time)
|
247
|
+
|
248
|
+
|
249
|
+
when 'down'
|
250
|
+
puts "Connecting to the nest."
|
251
|
+
@wasps = WASP::Wasp.new(@options)
|
252
|
+
@wasps.down
|
253
|
+
|
254
|
+
when 'status'
|
255
|
+
puts 'Report the wasp..'
|
256
|
+
@wasps = WASP::Wasp.new(@options)
|
257
|
+
@wasps.status
|
258
|
+
|
259
|
+
when 'regions'
|
260
|
+
puts 'Searching airfield..'
|
261
|
+
@wasps = WASP::Wasp.new(@options)
|
262
|
+
@wasps.airfield
|
263
|
+
|
264
|
+
when 'help'
|
265
|
+
@optionhelp = true
|
266
|
+
help
|
267
|
+
else
|
268
|
+
help
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def help (errcode = nil)
|
273
|
+
case errcode
|
274
|
+
|
275
|
+
when 'nokey'
|
276
|
+
puts "[Error]".red + " : AWS private key is required.\n";
|
277
|
+
|
278
|
+
when 'nourl'
|
279
|
+
puts "[Error]".red + " : Target url is required.\n";
|
280
|
+
|
281
|
+
when 'nofile'
|
282
|
+
puts "[Error]".red + " : Config file not found.\n";
|
283
|
+
|
284
|
+
end
|
285
|
+
puts command_usage
|
286
|
+
if @optionhelp then
|
287
|
+
@args = @args.unshift('--options')
|
288
|
+
parse_options!
|
289
|
+
end
|
290
|
+
exit(true)
|
291
|
+
end
|
292
|
+
|
293
|
+
def cleanup
|
294
|
+
puts "Clean up.."
|
295
|
+
exit(true)
|
296
|
+
end
|
297
|
+
|
298
|
+
def wakeup
|
299
|
+
trap('TERM') { print '\nTerminated\n'; exit(false)}
|
300
|
+
|
301
|
+
parse_options!
|
302
|
+
|
303
|
+
WASP::Config.output ||= STDOUT
|
304
|
+
|
305
|
+
parse_command!
|
306
|
+
end
|
307
|
+
|
308
|
+
def initialize(args)
|
309
|
+
@args = args
|
310
|
+
@options = { :colorize => true }
|
311
|
+
@wasps = []
|
312
|
+
@queenwasp = nil
|
313
|
+
@optionhelp = false
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.wakeup(args)
|
317
|
+
new(args).wakeup
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
data/lib/wasp/stingab.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
module WASP
|
2
|
+
class WeaponAB
|
3
|
+
attr_reader :ssh
|
4
|
+
attr_reader :ab
|
5
|
+
attr_reader :zip
|
6
|
+
attr_reader :ulimit
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@ab = false
|
10
|
+
@zip = false
|
11
|
+
@ulimit = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def check(ssh)
|
15
|
+
return nil if ssh.nil?
|
16
|
+
|
17
|
+
@ssh = ssh
|
18
|
+
result = @ssh.exec! "ab -V"
|
19
|
+
@ab = true if not result.match(/This is ApacheBench, Version 2/).nil?
|
20
|
+
|
21
|
+
zip_result = @ssh.exec! "zip -h"
|
22
|
+
@zip = true if not zip_result.match(/Info-ZIP/).nil?
|
23
|
+
|
24
|
+
open_files = @ssh.exec! "ulimit -n"
|
25
|
+
@ulimit = true if open_files.to_i > 1024
|
26
|
+
|
27
|
+
return @ab && @zip && @ulimit
|
28
|
+
end
|
29
|
+
|
30
|
+
def reload
|
31
|
+
if @ssh.nil? then
|
32
|
+
print "[Error]".red + " Check your weapon first!"
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
if not @ab then
|
37
|
+
command = "uname -v"
|
38
|
+
dist = @ssh.exec! command
|
39
|
+
case
|
40
|
+
when dist.match(/Ubuntu/) then
|
41
|
+
command = 'sudo apt-get update'
|
42
|
+
@ssh.exec! command
|
43
|
+
command = 'sudo apt-get install apache2-utils -y'
|
44
|
+
result = @ssh.exec! command
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if not @zip then
|
49
|
+
command = "uname -v"
|
50
|
+
dist = @ssh.exec! command
|
51
|
+
case
|
52
|
+
when dist.match(/Ubuntu/) then
|
53
|
+
command = 'sudo apt-get update'
|
54
|
+
@ssh.exec! command
|
55
|
+
command = 'sudo apt-get install zip -y'
|
56
|
+
result = @ssh.exec! command
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if not @ulimit then
|
61
|
+
command = "sudo bash -c \"echo '* soft nofile 19999' >> /etc/security/limits.conf\""
|
62
|
+
@ssh.exec! command
|
63
|
+
command = "sudo bash -c \"echo '* hard nofile 19999' >> /etc/security/limits.conf\""
|
64
|
+
@ssh.exec! command
|
65
|
+
command = "sudo bash -c \"echo 100000 > /proc/sys/kernel/threads-max\""
|
66
|
+
@ssh.exec! command
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def wavefire (concurrnet_user, url, id, wave, keepalive=false, without_cookie=false, header=nil)
|
71
|
+
if @ssh.nil? then
|
72
|
+
print "[Error]".red + " Check your weapon first!"
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
if wave.nil? then
|
77
|
+
print "[Error]".red + " No wave!"
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
header = "-H '#{header}'" if not header.nil?
|
82
|
+
|
83
|
+
cookie = "-C 'sessionid=NotARealSessionID'"
|
84
|
+
cookie = nil if without_cookie
|
85
|
+
|
86
|
+
keepalive_s = ""
|
87
|
+
keepalive_s = "-k" if keepalive
|
88
|
+
|
89
|
+
time = WASP::Const::DEFAULT_WAVE_TIME
|
90
|
+
|
91
|
+
command = "ab -t #{time} #{keepalive_s} -r #{cookie} #{header} -c #{concurrnet_user} #{url}/"
|
92
|
+
report = @ssh.exec! command
|
93
|
+
|
94
|
+
report
|
95
|
+
end
|
96
|
+
|
97
|
+
def fire (num_requests, concurrnet_user, url, id, report=false, keepalive=false, without_cookie=false, time=nil, header=nil)
|
98
|
+
if @ssh.nil? then
|
99
|
+
print "[Error]".red + " Check your weapon first!"
|
100
|
+
return nil
|
101
|
+
end
|
102
|
+
|
103
|
+
keepalive_s = ""
|
104
|
+
keepalive_s = "-k" if keepalive
|
105
|
+
|
106
|
+
gnuplot = "-g #{id}.plot" if report
|
107
|
+
|
108
|
+
cookie = "-C 'sessionid=NotARealSessionID'"
|
109
|
+
cookie = nil if without_cookie
|
110
|
+
|
111
|
+
header = "-H '#{header}'" if not header.nil?
|
112
|
+
|
113
|
+
option = "-n #{num_requests}"
|
114
|
+
|
115
|
+
option = "-t #{time}" if not time.nil?
|
116
|
+
|
117
|
+
command = "ab #{keepalive_s} -r #{gnuplot} #{option} #{header} -c #{concurrnet_user} #{cookie} #{url}/"
|
118
|
+
report = @ssh.exec! command
|
119
|
+
|
120
|
+
command = "zip #{id}.zip #{id}.plot"
|
121
|
+
@ssh.exec! command
|
122
|
+
|
123
|
+
report
|
124
|
+
end
|
125
|
+
|
126
|
+
def points (report)
|
127
|
+
return nil if report.nil?
|
128
|
+
|
129
|
+
#puts report
|
130
|
+
|
131
|
+
error_regex = []
|
132
|
+
|
133
|
+
response = {}
|
134
|
+
|
135
|
+
error_regex.push(/Too many open files/)
|
136
|
+
error_regex.push(/Cannot use concurrency level greater than total number of requests/)
|
137
|
+
error_regex.push(/Invalid Concurrency/)
|
138
|
+
error_regex.push(/The timeout specified has expired/)
|
139
|
+
|
140
|
+
error_regex.each do |reg|
|
141
|
+
return nil if not report.match(reg).nil?
|
142
|
+
end
|
143
|
+
|
144
|
+
regex = /Concurrency Level:.*[\d]*/
|
145
|
+
report.match(regex) do |data|
|
146
|
+
response[:concurrency] = data.to_s.gsub!(/\D/, '')
|
147
|
+
#puts response[:concurrency]
|
148
|
+
end
|
149
|
+
|
150
|
+
regex = /Time taken for tests:.*[\d]*.[\d]*.*seconds/
|
151
|
+
report.match(regex) do |data|
|
152
|
+
response[:time] = data.to_s.gsub!(/[a-zA-Z:\s]/, '')
|
153
|
+
#puts response[:time]
|
154
|
+
end
|
155
|
+
|
156
|
+
regex = /Complete requests:.*[\d]*/
|
157
|
+
report.match(regex) do |data|
|
158
|
+
response[:complete] = data.to_s.gsub!(/\D/, '')
|
159
|
+
#puts response[:complete]
|
160
|
+
end
|
161
|
+
|
162
|
+
regex = /Failed requests:.*[\d]*/
|
163
|
+
report.match(regex) do |data|
|
164
|
+
response[:failed] = data.to_s.gsub!(/\D/, '')
|
165
|
+
#puts response[:failed]
|
166
|
+
end
|
167
|
+
|
168
|
+
regex = /Write errors:.*[\d]*/
|
169
|
+
report.match(regex) do |data|
|
170
|
+
response[:write_errors] = data.to_s.gsub!(/\D/, '')
|
171
|
+
#puts response[:write_errors]
|
172
|
+
end
|
173
|
+
|
174
|
+
regex = /Non-2xx responses:.*/
|
175
|
+
response[:non2xx] = 0
|
176
|
+
report.match(regex) do |data|
|
177
|
+
values = data.to_s.split(" ")
|
178
|
+
response[:non2xx] = values[2] unless values.nil?
|
179
|
+
#puts response[:transferred]
|
180
|
+
end
|
181
|
+
|
182
|
+
response[:ok] = response[:complete].to_i - response[:non2xx].to_i
|
183
|
+
response[:ok] = 0 if response[:ok] < 0
|
184
|
+
|
185
|
+
regex = /Total transferred:.*[\d]*.*bytes/
|
186
|
+
report.match(regex) do |data|
|
187
|
+
response[:transferred] = data.to_s.gsub!(/\D/, '')
|
188
|
+
#puts response[:transferred]
|
189
|
+
end
|
190
|
+
|
191
|
+
regex = /HTML transferred:.*[\d]*.*bytes/
|
192
|
+
report.match(regex) do |data|
|
193
|
+
response[:html_transferred] = data.to_s.gsub!(/\D/, '')
|
194
|
+
#puts response[:html_transferred]
|
195
|
+
end
|
196
|
+
|
197
|
+
regex = /Requests per second:.*[\d]*.[\d]*.*\[#\/sec\].*\(mean\)/
|
198
|
+
report.match(regex) do |data|
|
199
|
+
#response[:requests_per_second] = data.to_s.gsub!(/[a-zA-Z:#\s\[\/\]\(\)]/, '')
|
200
|
+
response[:requests_per_second] = response[:ok].to_f / response[:time].to_f
|
201
|
+
#puts response[:requests_per_second]
|
202
|
+
end
|
203
|
+
|
204
|
+
regex = /Time per request:.*[\d]*.[\d]*.*\[ms\].*\(mean\)/
|
205
|
+
report.match(regex) do |data|
|
206
|
+
response[:time_per_request] = data.to_s.gsub!(/[a-zA-Z:\s\[\]\(\)]/, '')
|
207
|
+
#puts response[:time_per_request]
|
208
|
+
end
|
209
|
+
|
210
|
+
regex = /Total:.*/
|
211
|
+
report.match(regex) do |data|
|
212
|
+
values = data.to_s.split(" ")
|
213
|
+
response[:mean_per_request] = values[2] unless values.nil?
|
214
|
+
response[:standard_deviation] = values[3] unless values.nil?
|
215
|
+
#puts response[:mean_per_request]
|
216
|
+
#puts response[:standard_deviation]
|
217
|
+
end
|
218
|
+
|
219
|
+
regex = /.*80\%.*[\d]*/
|
220
|
+
report.match(regex) do |data|
|
221
|
+
response[:eighty_percent] = data.to_s.gsub!(/\s*80\%\s*/, '')
|
222
|
+
#puts response[:eighty_percent]
|
223
|
+
end
|
224
|
+
|
225
|
+
regex = /.*100\%.*[\d]*/
|
226
|
+
report.match(regex) do |data|
|
227
|
+
response[:hundred_percent] = data.to_s.gsub!(/\s*100\%\s*/, '')
|
228
|
+
#puts response[:hundred_percent]
|
229
|
+
end
|
230
|
+
|
231
|
+
response
|
232
|
+
end
|
233
|
+
|
234
|
+
def compact_summary (results)
|
235
|
+
return nil if results.nil?
|
236
|
+
|
237
|
+
# remove nil report from results array
|
238
|
+
reports = []
|
239
|
+
results.each do |r|
|
240
|
+
if not r.nil? then
|
241
|
+
reports.push(r)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
concurrency_results = reports.map do |r| r[:concurrency].to_i end
|
246
|
+
total_concurrency = concurrency_results.inject do |sum, r| sum + r end
|
247
|
+
|
248
|
+
print " Concurrent users:\t\t" + "#{total_concurrency}".yellow + "\n"
|
249
|
+
|
250
|
+
rps_results = reports.map do |r| r[:requests_per_second].to_f end
|
251
|
+
mean_requests = rps_results.inject do |sum, r| sum + r end
|
252
|
+
|
253
|
+
print " Requests per second:\t\t" + "%.3f".green % mean_requests + " [#/sec] (mean)\n"
|
254
|
+
|
255
|
+
#tpr_results = reports.map do |r| r[:time_per_request].to_f end
|
256
|
+
#tpr = tpr_results.inject { |sum, r| sum + r } / reports.count
|
257
|
+
|
258
|
+
tpr = total_concurrency / mean_requests * 1000
|
259
|
+
print " Time per request:\t\t" + "%.3f".green % tpr + " [ms] (mean)\n"
|
260
|
+
|
261
|
+
mpr_results = reports.map do |r| r[:mean_per_request].to_f end
|
262
|
+
mean_response = mpr_results.inject { |sum, r| sum + r } / reports.count
|
263
|
+
|
264
|
+
print " Connection Time:\t\t" + "%.3f".green % mean_response + " [ms] (mean)\n"
|
265
|
+
|
266
|
+
eighty_results = reports.map do |r| r[:eighty_percent].to_f end
|
267
|
+
mean_eighty = eighty_results.inject { |sum, r| sum + r } / reports.count
|
268
|
+
|
269
|
+
print " Almost(80%%) response time:\t%.3f [ms] (mean)\n" % mean_eighty
|
270
|
+
end
|
271
|
+
|
272
|
+
def summary (results)
|
273
|
+
return nil if results.nil?
|
274
|
+
|
275
|
+
# remove nil report from results array
|
276
|
+
reports = []
|
277
|
+
results.each do |r|
|
278
|
+
if not r.nil? then
|
279
|
+
reports.push(r)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
concurrency_results = reports.map do |r| r[:concurrency].to_i end
|
284
|
+
total_concurrency = concurrency_results.inject do |sum, r| sum + r end
|
285
|
+
|
286
|
+
print " Concurrent users:\t\t" + "#{total_concurrency}".yellow + "\n"
|
287
|
+
|
288
|
+
complete_results = reports.map do |r| r[:complete].to_i end
|
289
|
+
total_complete_requests = complete_results.inject do |sum, r| sum + r end
|
290
|
+
|
291
|
+
print " Complete requests:\t\t#{total_complete_requests}\n"
|
292
|
+
|
293
|
+
ok_results = reports.map do |r| r[:ok].to_i end
|
294
|
+
total_ok_responses = ok_results.inject do |sum, r| sum + r end
|
295
|
+
|
296
|
+
print " 20x responses:\t\t\t#{total_ok_responses}\n"
|
297
|
+
|
298
|
+
non2xx_results = reports.map do |r| r[:non2xx].to_i end
|
299
|
+
total_non2xx_responses = non2xx_results.inject do |sum, r| sum + r end
|
300
|
+
|
301
|
+
print " Non-2xx responses:\t\t#{total_non2xx_responses}\n"
|
302
|
+
|
303
|
+
failed_results = reports.map do |r| r[:failed].to_i end
|
304
|
+
total_failed_requests = failed_results.inject do |sum, r| sum + r end
|
305
|
+
|
306
|
+
print " Failed requests:\t\t" + "#{total_failed_requests}".red + "\n"
|
307
|
+
|
308
|
+
time_results = reports.map do |r| r[:time].to_f end
|
309
|
+
time_taken = time_results.inject { |sum, r| sum + r } / reports.count
|
310
|
+
|
311
|
+
print " Time taken for test:\t\t%.3f seconds (mean)\n" % time_taken
|
312
|
+
|
313
|
+
rps_results = reports.map do |r| r[:requests_per_second].to_f end
|
314
|
+
mean_requests = rps_results.inject do |sum, r| sum + r end
|
315
|
+
|
316
|
+
print " Requests per second:\t\t" + "%.3f".green % mean_requests + " [#/sec] (mean)\n"
|
317
|
+
|
318
|
+
#tpr_results = reports.map do |r| r[:time_per_request].to_f end
|
319
|
+
#tpr = tpr_results.inject { |sum, r| sum + r } / reports.count
|
320
|
+
|
321
|
+
tpr = total_concurrency.to_f / mean_requests.to_f * 1000.0
|
322
|
+
|
323
|
+
print " Time per request:\t\t" + "%.3f".green % tpr + " [ms] (mean)\n"
|
324
|
+
|
325
|
+
mpr_results = reports.map do |r| r[:mean_per_request].to_f end
|
326
|
+
mean_response = mpr_results.inject { |sum, r| sum + r } / reports.count
|
327
|
+
|
328
|
+
print " Connection Time:\t\t" + "%.3f".green % mean_response + " [ms] (mean)\n"
|
329
|
+
|
330
|
+
eighty_results = reports.map do |r| r[:eighty_percent].to_f end
|
331
|
+
mean_eighty = eighty_results.inject { |sum, r| sum + r } / reports.count
|
332
|
+
|
333
|
+
print " Almost(80%%) response time:\t%.3f [ms] (mean)\n" % mean_eighty
|
334
|
+
|
335
|
+
hundred_results = reports.map do |r| r[:hundred_percent].to_f end
|
336
|
+
longest_response = hundred_results.sort.last
|
337
|
+
|
338
|
+
print " The longest response time:\t%.3f [ms] (mean)\n\n" % longest_response
|
339
|
+
|
340
|
+
case
|
341
|
+
when mean_response < 500 then
|
342
|
+
print "Mission Assessment: Target crushed wasp offensive.\n\n"
|
343
|
+
when mean_response < 1000 then
|
344
|
+
print "Mission Assessment: Target successfully fended off the wasp.\n\n"
|
345
|
+
when mean_response < 1500 then
|
346
|
+
print "Mission Assessment: Target wounded, but operational.\n\n"
|
347
|
+
when mean_response < 2000 then
|
348
|
+
print "Mission Assessment: Target severely compromised.\n\n"
|
349
|
+
else
|
350
|
+
print "Mission Assessment: wasp annihilated target.\n\n"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
end
|