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.
@@ -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
@@ -0,0 +1,7 @@
1
+ module WASP
2
+ class QueenWasp
3
+ def info
4
+ puts "give me a report"
5
+ end
6
+ end
7
+ end
@@ -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