mu 5.7.2.7 → 5.7.8
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/lib/mu.rb +1 -0
- data/lib/mu/command/cmd_appid.rb +520 -512
- data/lib/mu/command/cmd_runscale.rb +604 -578
- data/rdoc/Mu/Command/Cmd_appid.html +271 -272
- data/rdoc/VERSION.html +1 -1
- data/rdoc/created.rid +4 -4
- data/rdoc/lib/mu/api/scale_rb.html +1 -1
- data/rdoc/lib/mu/command/cmd_appid_rb.html +1 -1
- data/test/data/app_id_stats.csv +1 -1
- data/test/data/app_id_status.json +50 -50
- data/test/data/scale.json +3 -3
- metadata +5 -41
data/lib/mu.rb
CHANGED
data/lib/mu/command/cmd_appid.rb
CHANGED
@@ -4,520 +4,528 @@
|
|
4
4
|
# the interfaces to use, and the pattern in which to run
|
5
5
|
require 'mu/api/scale'
|
6
6
|
class Mu
|
7
|
-
class Command
|
8
|
-
class Cmd_appid < Command
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
# returns a boolean indicating whether the scale test is running or not
|
18
|
-
# * argv = command-line arguments
|
19
|
-
def cmd_running? argv
|
20
|
-
if @api.nil?
|
21
|
-
msg "false"
|
22
|
-
return
|
23
|
-
end
|
24
|
-
|
25
|
-
status = @api.status
|
26
|
-
if !status.nil?
|
27
|
-
if !status["status"].nil?
|
28
|
-
msg status["status"]["running"]
|
29
|
-
end
|
30
|
-
else
|
31
|
-
msg "false"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# runs a single Studio Scale test
|
36
|
-
# * argv = command-line arguments, requires a scenario (-s) argument
|
37
|
-
def cmd_run_file argv
|
38
|
-
setup argv
|
39
|
-
|
40
|
-
if not @hash['scenario']
|
41
|
-
raise "*** Error: scenario required, using -s option"
|
42
|
-
else
|
43
|
-
scenario = @hash['scenario']
|
44
|
-
end
|
45
|
-
|
46
|
-
if !File.exists?(scenario)
|
47
|
-
raise "*** Error: Scenario file #{scenario} was not found"
|
48
|
-
end
|
49
|
-
|
50
|
-
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
51
|
-
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
52
|
-
|
53
|
-
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
54
|
-
@api.configure("pattern", @cmd_line_pattern)
|
55
|
-
@params = {}
|
56
|
-
@params["msl"] = scenario
|
57
|
-
@params["hosts"] = @cmd_line_hosts
|
58
|
-
run(scenario)
|
59
|
-
@api.release
|
60
|
-
end
|
61
|
-
|
62
|
-
# runs through a directory of msl files and executes a Studio Scale test for each one
|
63
|
-
# * argv = command-line arguments, require a directory (-d) argument
|
64
|
-
# * optional -r argument for recursive directory search (default is a flat directory)
|
65
|
-
def cmd_run_dir argv
|
66
|
-
setup argv
|
67
|
-
|
68
|
-
if not @hash['dir']
|
69
|
-
raise "*** Error: directory required, using -d option"
|
70
|
-
else
|
71
|
-
dir = @hash['dir']
|
72
|
-
end
|
73
|
-
|
74
|
-
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
75
|
-
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
76
|
-
|
77
|
-
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
78
|
-
@api.configure("pattern", @cmd_line_pattern)
|
79
|
-
@params = {}
|
80
|
-
@params["dir"] = dir
|
81
|
-
@params["hosts"] = @cmd_line_hosts
|
82
|
-
Dir.chdir(@params["dir"])
|
83
|
-
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
84
|
-
if @hash['recursive'].nil?
|
85
|
-
files = Dir.glob("*.msl")
|
86
|
-
else
|
87
|
-
files = Dir.glob("**/*.msl")
|
88
|
-
end
|
89
|
-
if !files.empty?
|
90
|
-
files.sort.each do | f |
|
91
|
-
run(f)
|
92
|
-
output_csv(f)
|
93
|
-
sleep 2
|
94
|
-
end
|
95
|
-
else
|
96
|
-
msg "no msl files found in #{dir}"
|
97
|
-
end
|
98
|
-
@api.release
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
def setup argv
|
104
|
-
parse_cli argv
|
105
|
-
@params = {}
|
106
|
-
@peak_throughput = 0.0
|
107
|
-
|
108
|
-
if @hash['test']
|
109
|
-
@verify_only = true
|
110
|
-
else
|
111
|
-
@verify_only = false
|
112
|
-
end
|
113
|
-
|
114
|
-
if not @hash['pattern']
|
115
|
-
@cmd_line_pattern = "{ \"iterations\": 1, \"intervals\": [ {\"iterations\":1, \"end\":100, \"start\":1, \"duration\":20 } ] }"
|
116
|
-
else
|
117
|
-
@cmd_line_pattern = @hash['pattern']
|
118
|
-
end
|
119
|
-
|
120
|
-
if not @hash['interfaces']
|
121
|
-
@cmd_line_hosts = "b1,b2"
|
122
|
-
else
|
123
|
-
@cmd_line_hosts = @hash['interfaces']
|
124
|
-
end
|
125
|
-
|
126
|
-
if not @hash['testset']
|
127
|
-
@testset = ""
|
128
|
-
else
|
129
|
-
@testset = @hash['testset']
|
130
|
-
end
|
131
|
-
|
132
|
-
if not @hash['delay']
|
133
|
-
@delay = 0
|
134
|
-
else
|
135
|
-
@delay = @hash['delay'].to_i
|
136
|
-
end
|
137
|
-
|
138
|
-
if not @hash['no_verify']
|
139
|
-
@no_verify = false
|
140
|
-
else
|
141
|
-
@no_verify = true
|
142
|
-
end
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
def run(msl)
|
147
|
-
if !File.exists?(msl)
|
148
|
-
return "file not found: #{msl}"
|
149
|
-
end
|
150
|
-
|
151
|
-
@api.configure("musl", File.read(msl))
|
152
|
-
|
153
|
-
unless @testset.empty?
|
154
|
-
if @testset.include?("/") # assume it is the full path in this case
|
155
|
-
csv_file = @testset
|
156
|
-
else
|
157
|
-
csv_file = @params["dir"] + "/" + @testset
|
158
|
-
end
|
159
|
-
@api.configure("csv", File.read(csv_file))
|
160
|
-
end
|
161
|
-
|
162
|
-
set_global_hosts
|
163
|
-
all_hosts = get_all_hosts_from_musl(msl)
|
164
|
-
@hosts_config = map_all_hosts_to_json(all_hosts)
|
165
|
-
@api.configure("hosts", @hosts_config)
|
166
|
-
@api.configure("delay", @delay)
|
167
|
-
if @no_verify == false # don't do verify if no_verify==true
|
168
|
-
msg "verifying #{msl} ..."
|
169
|
-
response = @api.verify
|
170
|
-
# sleep 3
|
171
|
-
v = parse_verify_response(response)
|
172
|
-
if v.nil?
|
173
|
-
msg "error in verify"
|
174
|
-
return
|
175
|
-
end
|
176
|
-
if @verify_only
|
177
|
-
msg v
|
178
|
-
return
|
179
|
-
end
|
180
|
-
end
|
181
|
-
msg "starting #{msl} ..."
|
182
|
-
@api.start
|
183
|
-
start_time = Time.now.to_i
|
184
|
-
while true
|
185
|
-
sleep 5
|
186
|
-
status = @api.status
|
187
|
-
if !status.nil?
|
188
|
-
if !status["status"].nil?
|
189
|
-
if status["status"]["running"] == false
|
190
|
-
msg "running = #{status["status"]["running"]}", Logger::DEBUG
|
191
|
-
r = parse_status(status)
|
192
|
-
dump_status(status, msl)
|
193
|
-
return
|
194
|
-
else
|
195
|
-
r = parse_status(status)
|
196
|
-
end
|
197
|
-
else # status['status'].nil? ... no bonafide status was returned
|
198
|
-
time_now = Time.now.to_i
|
199
|
-
if time_now - start_time > 20
|
200
|
-
# puts "\nError: timing out after 20 seconds. Test had failed to start or verify"
|
201
|
-
break
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
ensure
|
207
|
-
msg "stopping #{msl} ..."
|
208
|
-
end
|
209
|
-
|
210
|
-
def set_global_hosts
|
211
|
-
@hosts = Array.new
|
212
|
-
@addr_indexes = Array.new
|
213
|
-
hosts = @params["hosts"]
|
214
|
-
if !hosts.nil?
|
215
|
-
p = hosts.split(",")
|
216
|
-
p.each do | h |
|
217
|
-
if h.include?("-") # b1-1000,b2-1 to indicate addr_count
|
218
|
-
q = h.split("-")
|
219
|
-
@hosts << q[0]
|
220
|
-
@addr_indexes << q[1]
|
221
|
-
else # default to the 1st addr index
|
222
|
-
@hosts << h
|
223
|
-
@addr_indexes << 1
|
224
|
-
end
|
225
|
-
end
|
226
|
-
else
|
227
|
-
@hosts = ['b1','b2']
|
228
|
-
@addr_indexes = [1,1]
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def dump_status(status, msl)
|
233
|
-
filename = "app_id_status.json"
|
234
|
-
f = File.open(filename, "a")
|
235
|
-
status["filename"] = msl
|
236
|
-
str = JSON.pretty_generate(status)
|
237
|
-
f.write(",") if !File.zero?(f) # if appending, we need to insert a comma
|
238
|
-
f.write(str)
|
239
|
-
f.close
|
240
|
-
end
|
241
|
-
|
242
|
-
def output_csv(msl_file)
|
243
|
-
filename = "app_id_stats.csv"
|
244
|
-
f = File.open(filename, "a")
|
245
|
-
doc = "#{msl_file},#{@executed},#{@errors.to_i},#{@timeouts.to_i},#{@client_tx_bytes},#{@client_tx_msgs},#{@client_rx_bytes},#{@client_rx_msgs},#{@server_tx_bytes},#{@server_tx_msgs},#{@server_rx_bytes},#{@server_rx_msgs}\n"
|
246
|
-
File.open(filename, 'a') {|f| f.write(doc) }
|
247
|
-
end
|
248
|
-
|
249
|
-
# finds all the hosts in the musl file
|
250
|
-
def get_all_hosts_from_musl(msl)
|
251
|
-
f = IO.read(msl)
|
252
|
-
hosts = f.scan(/host_\d+/)
|
253
|
-
hosts.uniq!
|
254
|
-
return hosts
|
255
|
-
end
|
256
|
-
|
257
|
-
# maps host_0 to the client interface
|
258
|
-
# maps all other hosts to the server interface
|
259
|
-
def map_all_hosts_to_json(hosts=[])
|
260
|
-
new_hosts = Array.new
|
261
|
-
hosts.each_with_index do | h, i |
|
262
|
-
if i == 0
|
263
|
-
new_hosts << @hosts[0] + "/*,#{@addr_indexes[0]}"
|
264
|
-
else
|
265
|
-
new_hosts << @hosts[1] + "/*,#{@addr_indexes[1]}"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
hosts_config = {}
|
270
|
-
|
271
|
-
# assign hosts to consecutive string keys, host_0, host_1, etc ...
|
272
|
-
new_hosts.each_with_index do | h, i |
|
273
|
-
hosts_config["host_#{i}"] = h # new_hosts[i]
|
274
|
-
end
|
275
|
-
|
276
|
-
# convert keys to symbols
|
277
|
-
new_hosts_config = {}
|
278
|
-
hosts_config.each_key { |k| new_hosts_config[k.to_sym] = hosts_config[k] }
|
279
|
-
|
280
|
-
return new_hosts_config
|
281
|
-
end
|
282
|
-
|
283
|
-
def parse_verify_response(response)
|
284
|
-
if response.nil? # || response.empty?
|
285
|
-
msg "*** error = no response received from /verify ***\n\n"
|
286
|
-
return nil
|
287
|
-
end
|
288
|
-
begin
|
289
|
-
msg JSON.pretty_generate(response), Logger::DEBUG
|
290
|
-
if !response["status"].nil?
|
291
|
-
if response["status"]["error"] == true
|
292
|
-
@error = response["status"]["error"]
|
293
|
-
@reason = response["status"]["reason"]
|
294
|
-
dump_status(response)
|
295
|
-
msg "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
296
|
-
return nil
|
297
|
-
end
|
298
|
-
end
|
299
|
-
msg "*** verify: okay ***", Logger::DEBUG
|
300
|
-
return "*** verify: okay ***"
|
301
|
-
rescue
|
302
|
-
# could nbe json parse error
|
303
|
-
return nil
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
def parse_status(status)
|
308
|
-
return nil if status.nil?
|
309
|
-
msg JSON.pretty_generate(status), Logger::DEBUG
|
310
|
-
@reported_volume = 0
|
311
|
-
if !status["status"]["error"].nil?
|
312
|
-
if status["status"]["error"] == true
|
313
|
-
@error = status["status"]["error"]
|
314
|
-
@reason = status["status"]["reason"]
|
315
|
-
# puts "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
316
|
-
return nil
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
@stats_summary = status["status"]["statistics"]["summary"]
|
321
|
-
@duration = @stats_summary["duration"]
|
322
|
-
@instances = @stats_summary["instances"]
|
323
|
-
@total_instances = @instances["total"]
|
324
|
-
@executed = @instances["executed"]
|
325
|
-
@timeouts = @instances["timeouts"]
|
326
|
-
@errors = @instances["errors"]
|
327
|
-
@asserts_failed = @stats_summary["asserts"]["failed"]
|
328
|
-
@server = @stats_summary["server"]
|
329
|
-
@server_tx_bytes = @server["tx"]["bytes"]
|
330
|
-
@server_tx_msgs = @server["tx"]["msgs"]
|
331
|
-
@server_rx_bytes = @server["rx"]["bytes"]
|
332
|
-
@server_rx_msgs = @server["rx"]["msgs"]
|
333
|
-
@client = @stats_summary["client"]
|
334
|
-
@client_tx_bytes = @client["tx"]["bytes"]
|
335
|
-
@client_tx_msgs = @client["tx"]["msgs"]
|
336
|
-
@client_rx_bytes = @client["rx"]["bytes"]
|
337
|
-
@client_rx_msgs = @client["rx"]["msgs"]
|
338
|
-
@scenarios = status["status"]["statistics"]["scenarios"]
|
339
|
-
@scenarios.each do | scenario |
|
340
|
-
@reported_volume = @reported_volume + scenario["volume"]
|
341
|
-
end
|
342
|
-
|
343
|
-
bits1 = (@client_tx_bytes.to_i + @client_rx_bytes.to_i) * 8
|
344
|
-
dur1 = @duration.to_f
|
345
|
-
thruput = format_float(2, bits1.to_f / dur1)
|
346
|
-
|
347
|
-
if thruput.to_f > @peak_throughput
|
348
|
-
@peak_throughput = thruput.to_f
|
349
|
-
end
|
350
|
-
|
351
|
-
msg ""
|
352
|
-
msg "duration: #{format_float(2, @duration)}"
|
353
|
-
msg "concurrency: #{@reported_volume}"
|
354
|
-
msg "tests/sec: #{format_float(2, @executed.to_f / @duration)}" if @duration.to_i > 0
|
355
|
-
msg "bits/sec: #{thruput}" if @duration.to_i > 0
|
356
|
-
msg "passed: #{@executed}"
|
357
|
-
msg "errors: #{@errors}"
|
358
|
-
msg "timeouts: #{@timeouts}"
|
359
|
-
msg "client tx bytes/sec #{format_float(2, @client_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
360
|
-
msg "client tx msgs/sec #{format_float(2, @client_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
361
|
-
msg "client rx bytes/sec #{format_float(2, @client_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
362
|
-
msg "client rx msgs/sec #{format_float(2, @client_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
363
|
-
msg "server tx bytes/sec #{format_float(2, @server_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
364
|
-
msg "server tx msgs/sec #{format_float(2, @server_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
365
|
-
msg "server rx bytes/sec #{format_float(2, @server_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
366
|
-
msg "server rx msgs/sec #{format_float(2, @server_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
367
|
-
msg ""
|
368
|
-
end
|
369
|
-
|
370
|
-
def parse_cli argv
|
371
|
-
@hash = Hash.new
|
372
|
-
while not argv.empty?
|
373
|
-
break if argv.first[0,1] != '-'
|
374
|
-
|
375
|
-
k = argv.shift
|
376
|
-
|
377
|
-
if [ '-c', '--csv' ].member? k
|
378
|
-
@hash['testset'] = shift(k, argv)
|
379
|
-
next
|
380
|
-
end
|
381
|
-
|
382
|
-
if [ '-d', '--dir' ].member? k
|
383
|
-
@hash['dir'] = shift(k, argv)
|
384
|
-
next
|
385
|
-
end
|
386
|
-
|
387
|
-
if [ '-i', '--interfaces' ].member? k
|
388
|
-
@hash['interfaces'] = shift(k, argv)
|
389
|
-
next
|
390
|
-
end
|
391
|
-
|
392
|
-
if [ '-h', '--help' ].member? k
|
393
|
-
help
|
394
|
-
exit
|
395
|
-
end
|
396
|
-
|
397
|
-
if [ '-l', '--delay' ].member? k
|
398
|
-
@hash['delay'] = shift(k, argv)
|
399
|
-
next
|
400
|
-
end
|
401
|
-
|
402
|
-
if [ '-m', '--mu_string' ].member? k
|
403
|
-
mu_string = shift(k, argv)
|
404
|
-
if mu_string =~ /(.+?):(.+?)@(.*)/
|
405
|
-
@@mu_admin_user = $1
|
406
|
-
@@mu_admin_pass = $2
|
407
|
-
@@mu_ip = $3
|
408
|
-
end
|
409
|
-
next
|
410
|
-
end
|
411
|
-
|
412
|
-
if [ '-n', '--no_verify' ].member? k
|
413
|
-
@hash['no_verify'] = true
|
414
|
-
next
|
415
|
-
end
|
416
|
-
|
417
|
-
if [ '-o', '--output'].member? k
|
418
|
-
$stdout.reopen(shift(k, argv), "w")
|
419
|
-
next
|
420
|
-
end
|
421
|
-
|
422
|
-
if [ '-p', '--pattern' ].member? k
|
423
|
-
patterns = Array.new
|
424
|
-
pattern_string = shift(k, argv)
|
425
|
-
pstrings = pattern_string.split(",")
|
426
|
-
pstrings.each do | p |
|
427
|
-
if p =~ /(.+?)-(.+?):(.*)/ # e.g. 1-10000:60
|
428
|
-
start_vol = $1
|
429
|
-
end_vol = $2
|
430
|
-
duration = $3
|
431
|
-
patterns << "{\"iterations\":1, \"end\":#{end_vol}, \"start\":#{start_vol}, \"duration\":#{duration} }"
|
432
|
-
end
|
7
|
+
class Command
|
8
|
+
class Cmd_appid < Command
|
9
|
+
|
10
|
+
attr_accessor :api, :params, :hosts, :addr_indexes, :hash
|
11
|
+
|
12
|
+
# displays command-line help
|
13
|
+
def cmd_help argv
|
14
|
+
help
|
433
15
|
end
|
434
|
-
|
435
|
-
|
436
|
-
|
16
|
+
|
17
|
+
# returns a boolean indicating whether the scale test is running or not
|
18
|
+
# * argv = command-line arguments
|
19
|
+
def cmd_running? argv
|
20
|
+
if @api.nil?
|
21
|
+
msg "false"
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
status = @api.status
|
26
|
+
if !status.nil?
|
27
|
+
if !status["status"].nil?
|
28
|
+
msg status["status"]["running"]
|
29
|
+
end
|
30
|
+
else
|
31
|
+
msg "false"
|
32
|
+
end
|
437
33
|
end
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
34
|
+
|
35
|
+
# runs a single Studio Scale test
|
36
|
+
# * argv = command-line arguments, requires a scenario (-s) argument
|
37
|
+
def cmd_run_file argv
|
38
|
+
puts "\n***WARNING***\nThe following commands will be deprecated in a future release:"
|
39
|
+
puts "cmd_runscale:run_file, cmd_runscale:run_dir, cmd_appid\n"
|
40
|
+
puts "Please use 'cmd_runscale:run_files'"
|
41
|
+
setup argv
|
42
|
+
|
43
|
+
if not @hash['scenario']
|
44
|
+
raise "*** Error: scenario required, using -s option"
|
45
|
+
else
|
46
|
+
scenario = @hash['scenario']
|
47
|
+
end
|
48
|
+
|
49
|
+
if !File.exists?(scenario)
|
50
|
+
raise "*** Error: Scenario file #{scenario} was not found"
|
51
|
+
end
|
52
|
+
|
53
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
54
|
+
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
55
|
+
|
56
|
+
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
57
|
+
@api.configure("pattern", @cmd_line_pattern)
|
58
|
+
@params = {}
|
59
|
+
@params["msl"] = scenario
|
60
|
+
@params["hosts"] = @cmd_line_hosts
|
61
|
+
run(scenario)
|
62
|
+
@api.release
|
63
|
+
end
|
64
|
+
|
65
|
+
# runs through a directory of msl files and executes a Studio Scale test for each one
|
66
|
+
# * argv = command-line arguments, require a directory (-d) argument
|
67
|
+
# * optional -r argument for recursive directory search (default is a flat directory)
|
68
|
+
def cmd_run_dir argv
|
69
|
+
puts "\n***WARNING***\nThe following commands will be deprecated in a future release:"
|
70
|
+
puts "cmd_runscale:run_file, cmd_runscale:run_dir, cmd_appid\n"
|
71
|
+
puts "Please use 'cmd_runscale:run_files'"
|
72
|
+
setup argv
|
73
|
+
|
74
|
+
if not @hash['dir']
|
75
|
+
raise "*** Error: directory required, using -d option"
|
76
|
+
else
|
77
|
+
dir = @hash['dir']
|
78
|
+
end
|
79
|
+
|
80
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
81
|
+
File.delete("app_id_stats.csv") if File.exists?("app_id_stats.csv")
|
82
|
+
|
83
|
+
@api = Scale.new(@@mu_ip, @@mu_admin_user, @@mu_admin_pass)
|
84
|
+
@api.configure("pattern", @cmd_line_pattern)
|
85
|
+
@params = {}
|
86
|
+
@params["dir"] = dir
|
87
|
+
@params["hosts"] = @cmd_line_hosts
|
88
|
+
Dir.chdir(@params["dir"])
|
89
|
+
File.delete("app_id_status.json") if File.exists?("app_id_status.json")
|
90
|
+
if @hash['recursive'].nil?
|
91
|
+
files = Dir.glob("*.msl")
|
92
|
+
else
|
93
|
+
files = Dir.glob("**/*.msl")
|
94
|
+
end
|
95
|
+
if !files.empty?
|
96
|
+
files.sort.each do | f |
|
97
|
+
run(f)
|
98
|
+
output_csv(f)
|
99
|
+
sleep 2
|
100
|
+
end
|
101
|
+
else
|
102
|
+
msg "no msl files found in #{dir}"
|
103
|
+
end
|
104
|
+
@api.release
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def setup argv
|
110
|
+
parse_cli argv
|
111
|
+
@params = {}
|
112
|
+
@peak_throughput = 0.0
|
113
|
+
|
114
|
+
if @hash['test']
|
115
|
+
@verify_only = true
|
116
|
+
else
|
117
|
+
@verify_only = false
|
118
|
+
end
|
119
|
+
|
120
|
+
if not @hash['pattern']
|
121
|
+
@cmd_line_pattern = "{ \"iterations\": 1, \"intervals\": [ {\"iterations\":1, \"end\":100, \"start\":1, \"duration\":20 } ] }"
|
122
|
+
else
|
123
|
+
@cmd_line_pattern = @hash['pattern']
|
124
|
+
end
|
125
|
+
|
126
|
+
if not @hash['interfaces']
|
127
|
+
@cmd_line_hosts = "b1,b2"
|
128
|
+
else
|
129
|
+
@cmd_line_hosts = @hash['interfaces']
|
130
|
+
end
|
131
|
+
|
132
|
+
if not @hash['testset']
|
133
|
+
@testset = ""
|
134
|
+
else
|
135
|
+
@testset = @hash['testset']
|
136
|
+
end
|
137
|
+
|
138
|
+
if not @hash['delay']
|
139
|
+
@delay = 0
|
140
|
+
else
|
141
|
+
@delay = @hash['delay'].to_i
|
142
|
+
end
|
143
|
+
|
144
|
+
if not @hash['no_verify']
|
145
|
+
@no_verify = false
|
146
|
+
else
|
147
|
+
@no_verify = true
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
def run(msl)
|
153
|
+
if !File.exists?(msl)
|
154
|
+
return "file not found: #{msl}"
|
155
|
+
end
|
156
|
+
|
157
|
+
@api.configure("musl", File.read(msl))
|
158
|
+
|
159
|
+
unless @testset.empty?
|
160
|
+
if @testset.include?("/") # assume it is the full path in this case
|
161
|
+
csv_file = @testset
|
162
|
+
else
|
163
|
+
csv_file = @params["dir"] + "/" + @testset
|
164
|
+
end
|
165
|
+
@api.configure("csv", File.read(csv_file))
|
166
|
+
end
|
167
|
+
|
168
|
+
set_global_hosts
|
169
|
+
all_hosts = get_all_hosts_from_musl(msl)
|
170
|
+
@hosts_config = map_all_hosts_to_json(all_hosts)
|
171
|
+
@api.configure("hosts", @hosts_config)
|
172
|
+
@api.configure("delay", @delay)
|
173
|
+
if @no_verify == false # don't do verify if no_verify==true
|
174
|
+
msg "verifying #{msl} ..."
|
175
|
+
response = @api.verify
|
176
|
+
# sleep 3
|
177
|
+
v = parse_verify_response(response)
|
178
|
+
if v.nil?
|
179
|
+
msg "error in verify"
|
180
|
+
return
|
181
|
+
end
|
182
|
+
if @verify_only
|
183
|
+
msg v
|
184
|
+
return
|
185
|
+
end
|
186
|
+
end
|
187
|
+
msg "starting #{msl} ..."
|
188
|
+
@api.start
|
189
|
+
start_time = Time.now.to_i
|
190
|
+
while true
|
191
|
+
sleep 5
|
192
|
+
status = @api.status
|
193
|
+
if !status.nil?
|
194
|
+
if !status["status"].nil?
|
195
|
+
if status["status"]["running"] == false
|
196
|
+
msg "running = #{status["status"]["running"]}", Logger::DEBUG
|
197
|
+
r = parse_status(status)
|
198
|
+
dump_status(status, msl)
|
199
|
+
return
|
200
|
+
else
|
201
|
+
r = parse_status(status)
|
202
|
+
end
|
203
|
+
else # status['status'].nil? ... no bonafide status was returned
|
204
|
+
time_now = Time.now.to_i
|
205
|
+
if time_now - start_time > 20
|
206
|
+
# puts "\nError: timing out after 20 seconds. Test had failed to start or verify"
|
207
|
+
break
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
ensure
|
213
|
+
msg "stopping #{msl} ..."
|
214
|
+
end
|
215
|
+
|
216
|
+
def set_global_hosts
|
217
|
+
@hosts = Array.new
|
218
|
+
@addr_indexes = Array.new
|
219
|
+
hosts = @params["hosts"]
|
220
|
+
if !hosts.nil?
|
221
|
+
p = hosts.split(",")
|
222
|
+
p.each do | h |
|
223
|
+
if h.include?("-") # b1-1000,b2-1 to indicate addr_count
|
224
|
+
q = h.split("-")
|
225
|
+
@hosts << q[0]
|
226
|
+
@addr_indexes << q[1]
|
227
|
+
else # default to the 1st addr index
|
228
|
+
@hosts << h
|
229
|
+
@addr_indexes << 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
else
|
233
|
+
@hosts = ['b1','b2']
|
234
|
+
@addr_indexes = [1,1]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def dump_status(status, msl)
|
239
|
+
filename = "app_id_status.json"
|
240
|
+
f = File.open(filename, "a")
|
241
|
+
status["filename"] = msl
|
242
|
+
str = JSON.pretty_generate(status)
|
243
|
+
f.write(",") if !File.zero?(f) # if appending, we need to insert a comma
|
244
|
+
f.write(str)
|
245
|
+
f.close
|
246
|
+
end
|
247
|
+
|
248
|
+
def output_csv(msl_file)
|
249
|
+
filename = "app_id_stats.csv"
|
250
|
+
doc = "#{msl_file},#{@executed},#{@errors.to_i},#{@timeouts.to_i},#{@client_tx_bytes},#{@client_tx_msgs},#{@client_rx_bytes},#{@client_rx_msgs},#{@server_tx_bytes},#{@server_tx_msgs},#{@server_rx_bytes},#{@server_rx_msgs}\n"
|
251
|
+
File.open(filename, File::RDWR|File::TRUNC|File::CREAT) {|f| f.write(doc) }
|
252
|
+
end
|
253
|
+
|
254
|
+
# finds all the hosts in the musl file
|
255
|
+
def get_all_hosts_from_musl(msl)
|
256
|
+
f = IO.read(msl)
|
257
|
+
hosts = f.scan(/host_\d+/)
|
258
|
+
hosts.uniq!
|
259
|
+
return hosts
|
260
|
+
end
|
261
|
+
|
262
|
+
# maps host_0 to the client interface
|
263
|
+
# maps all other hosts to the server interface
|
264
|
+
def map_all_hosts_to_json(hosts=[])
|
265
|
+
new_hosts = Array.new
|
266
|
+
hosts.each_with_index do | h, i |
|
267
|
+
if i == 0
|
268
|
+
new_hosts << @hosts[0] + "/*,#{@addr_indexes[0]}"
|
269
|
+
else
|
270
|
+
new_hosts << @hosts[1] + "/*,#{@addr_indexes[1]}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
hosts_config = {}
|
275
|
+
|
276
|
+
# assign hosts to consecutive string keys, host_0, host_1, etc ...
|
277
|
+
new_hosts.each_with_index do | h, i |
|
278
|
+
hosts_config["host_#{i}"] = h # new_hosts[i]
|
279
|
+
end
|
280
|
+
|
281
|
+
# convert keys to symbols
|
282
|
+
new_hosts_config = {}
|
283
|
+
hosts_config.each_key { |k| new_hosts_config[k.to_sym] = hosts_config[k] }
|
284
|
+
|
285
|
+
return new_hosts_config
|
286
|
+
end
|
287
|
+
|
288
|
+
def parse_verify_response(response)
|
289
|
+
if response.nil? # || response.empty?
|
290
|
+
msg "*** error = no response received from /verify ***\n\n"
|
291
|
+
return nil
|
292
|
+
end
|
293
|
+
begin
|
294
|
+
msg JSON.pretty_generate(response), Logger::DEBUG
|
295
|
+
if !response["status"].nil?
|
296
|
+
if response["status"]["error"] == true
|
297
|
+
@error = response["status"]["error"]
|
298
|
+
@reason = response["status"]["reason"]
|
299
|
+
dump_status(response)
|
300
|
+
msg "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
301
|
+
return nil
|
302
|
+
end
|
303
|
+
end
|
304
|
+
msg "*** verify: okay ***", Logger::DEBUG
|
305
|
+
return "*** verify: okay ***"
|
306
|
+
rescue
|
307
|
+
# could nbe json parse error
|
308
|
+
return nil
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def parse_status(status)
|
313
|
+
return nil if status.nil?
|
314
|
+
msg JSON.pretty_generate(status), Logger::DEBUG
|
315
|
+
@reported_volume = 0
|
316
|
+
if !status["status"]["error"].nil?
|
317
|
+
if status["status"]["error"] == true
|
318
|
+
@error = status["status"]["error"]
|
319
|
+
@reason = status["status"]["reason"]
|
320
|
+
# puts "*** Error = #{@error}, reason = #{@reason} ***\n\n"
|
321
|
+
return nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
@stats_summary = status["status"]["statistics"]["summary"]
|
326
|
+
@duration = @stats_summary["duration"]
|
327
|
+
@instances = @stats_summary["instances"]
|
328
|
+
@total_instances = @instances["total"]
|
329
|
+
@executed = @instances["executed"]
|
330
|
+
@timeouts = @instances["timeouts"]
|
331
|
+
@errors = @instances["errors"]
|
332
|
+
@asserts_failed = @stats_summary["asserts"]["failed"]
|
333
|
+
@server = @stats_summary["server"]
|
334
|
+
@server_tx_bytes = @server["tx"]["bytes"]
|
335
|
+
@server_tx_msgs = @server["tx"]["msgs"]
|
336
|
+
@server_rx_bytes = @server["rx"]["bytes"]
|
337
|
+
@server_rx_msgs = @server["rx"]["msgs"]
|
338
|
+
@client = @stats_summary["client"]
|
339
|
+
@client_tx_bytes = @client["tx"]["bytes"]
|
340
|
+
@client_tx_msgs = @client["tx"]["msgs"]
|
341
|
+
@client_rx_bytes = @client["rx"]["bytes"]
|
342
|
+
@client_rx_msgs = @client["rx"]["msgs"]
|
343
|
+
@scenarios = status["status"]["statistics"]["scenarios"]
|
344
|
+
@scenarios.each do | scenario |
|
345
|
+
@reported_volume = @reported_volume + scenario["volume"]
|
346
|
+
end
|
347
|
+
|
348
|
+
bits1 = (@client_tx_bytes.to_i + @client_rx_bytes.to_i) * 8
|
349
|
+
dur1 = @duration.to_f
|
350
|
+
thruput = format_float(2, bits1.to_f / dur1)
|
351
|
+
|
352
|
+
if thruput.to_f > @peak_throughput
|
353
|
+
@peak_throughput = thruput.to_f
|
354
|
+
end
|
355
|
+
|
356
|
+
msg ""
|
357
|
+
msg "duration: #{format_float(2, @duration)}"
|
358
|
+
msg "concurrency: #{@reported_volume}"
|
359
|
+
msg "tests/sec: #{format_float(2, @executed.to_f / @duration)}" if @duration.to_i > 0
|
360
|
+
msg "bits/sec: #{thruput}" if @duration.to_i > 0
|
361
|
+
msg "passed: #{@executed}"
|
362
|
+
msg "errors: #{@errors}"
|
363
|
+
msg "timeouts: #{@timeouts}"
|
364
|
+
msg "client tx bytes/sec #{format_float(2, @client_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
365
|
+
msg "client tx msgs/sec #{format_float(2, @client_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
366
|
+
msg "client rx bytes/sec #{format_float(2, @client_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
367
|
+
msg "client rx msgs/sec #{format_float(2, @client_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
368
|
+
msg "server tx bytes/sec #{format_float(2, @server_tx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
369
|
+
msg "server tx msgs/sec #{format_float(2, @server_tx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
370
|
+
msg "server rx bytes/sec #{format_float(2, @server_rx_bytes.to_f / @duration)}" if @duration.to_i > 0
|
371
|
+
msg "server rx msgs/sec #{format_float(2, @server_rx_msgs.to_f / @duration)}" if @duration.to_i > 0
|
372
|
+
msg ""
|
373
|
+
end
|
374
|
+
|
375
|
+
def parse_cli argv
|
376
|
+
@hash = Hash.new
|
377
|
+
while not argv.empty?
|
378
|
+
break if argv.first[0,1] != '-'
|
379
|
+
|
380
|
+
k = argv.shift
|
381
|
+
|
382
|
+
if [ '-c', '--csv' ].member? k
|
383
|
+
@hash['testset'] = shift(k, argv)
|
384
|
+
next
|
385
|
+
end
|
386
|
+
|
387
|
+
if [ '-d', '--dir' ].member? k
|
388
|
+
@hash['dir'] = shift(k, argv)
|
389
|
+
next
|
390
|
+
end
|
391
|
+
|
392
|
+
if [ '-i', '--interfaces' ].member? k
|
393
|
+
@hash['interfaces'] = shift(k, argv)
|
394
|
+
next
|
395
|
+
end
|
396
|
+
|
397
|
+
if [ '-h', '--help' ].member? k
|
398
|
+
help
|
399
|
+
exit
|
400
|
+
end
|
401
|
+
|
402
|
+
if [ '-l', '--delay' ].member? k
|
403
|
+
@hash['delay'] = shift(k, argv)
|
404
|
+
next
|
405
|
+
end
|
406
|
+
|
407
|
+
if [ '-m', '--mu_string' ].member? k
|
408
|
+
mu_string = shift(k, argv)
|
409
|
+
if mu_string =~ /(.+?):(.+?)@(.*)/
|
410
|
+
@@mu_admin_user = $1
|
411
|
+
@@mu_admin_pass = $2
|
412
|
+
@@mu_ip = $3
|
413
|
+
end
|
414
|
+
next
|
415
|
+
end
|
416
|
+
|
417
|
+
if [ '-n', '--no_verify' ].member? k
|
418
|
+
@hash['no_verify'] = true
|
419
|
+
next
|
420
|
+
end
|
421
|
+
|
422
|
+
if [ '-o', '--output'].member? k
|
423
|
+
$stdout.reopen(shift(k, argv), "w")
|
424
|
+
next
|
425
|
+
end
|
426
|
+
|
427
|
+
if [ '-p', '--pattern' ].member? k
|
428
|
+
patterns = Array.new
|
429
|
+
pattern_string = shift(k, argv)
|
430
|
+
pstrings = pattern_string.split(",")
|
431
|
+
pstrings.each do | p |
|
432
|
+
if p =~ /(.+?)-(.+?):(.*)/ # e.g. 1-10000:60
|
433
|
+
start_vol = $1
|
434
|
+
end_vol = $2
|
435
|
+
duration = $3
|
436
|
+
patterns << "{\"iterations\":1, \"end\":#{end_vol}, \"start\":#{start_vol}, \"duration\":#{duration} }"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
ps = "{ \"iterations\": 1, \"intervals\": ["
|
440
|
+
patterns.each do | p |
|
441
|
+
ps = ps + p + ","
|
442
|
+
end
|
443
|
+
ps = ps[0..ps.length-2] # remove final comma
|
444
|
+
ps = ps + "] }"
|
445
|
+
@hash['pattern'] = ps
|
446
|
+
next
|
447
|
+
end
|
448
|
+
|
449
|
+
if [ '-r', '--recursive'].member? k
|
450
|
+
@hash['recursive'] = true
|
451
|
+
next
|
452
|
+
end
|
453
|
+
|
454
|
+
if [ '-s', '--scenario' ].member? k
|
455
|
+
@hash['scenario'] = shift(k, argv)
|
456
|
+
next
|
457
|
+
end
|
458
|
+
|
459
|
+
if [ '-t', '--test' ].member? k
|
460
|
+
@hash['test'] = true
|
461
|
+
next
|
462
|
+
end
|
463
|
+
|
464
|
+
if [ '-v', '--verbose' ].member? k
|
465
|
+
$log.level = Logger::DEBUG
|
466
|
+
next
|
467
|
+
end
|
468
|
+
|
469
|
+
raise ArgumentError, "Unknown option #{k}"
|
470
|
+
end
|
471
|
+
|
472
|
+
hash
|
473
|
+
end
|
474
|
+
|
475
|
+
def help
|
476
|
+
helps = [
|
477
|
+
{ :short => '-c', :long => '--csv', :value => '<string>', :help => 'name of the csv testset to run' },
|
478
|
+
{ :short => '-d', :long => '--dir', :value => '<string>', :help => 'directory containing msl files, required for run_dir' },
|
479
|
+
{ :short => '-h', :long => '--help', :value => '', :help => 'help on command line options' },
|
480
|
+
{ :short => '-i', :long => '--interfaces', :value => '<string>', :help => 'comma-separated list of interfaces, e.g. b1,b2 or b1-1000:0,b2 for ip range and offset' },
|
481
|
+
{ :short => '-l', :long => '--delay', :value => '<string>', :help => 'intra-scenario delay value' },
|
482
|
+
{ :short => '-m', :long => '--mu_string', :value => '<string>', :help => 'user, password, mu_ip in the form of admin:admin@10.9.8.7' },
|
483
|
+
{ :short => '-n', :long => '--no_verify', :value => '', :help => 'do not do verify before start' },
|
484
|
+
{ :short => '-o', :long => '--output', :value => '<string>', :help => 'output logging to this file' },
|
485
|
+
{ :short => '-p', :long => '--pattern', :value => '<string>', :help => 'pattern in the form of comma-separated concurrency_start-end:duration strings, e.g. 1-10000:60,10000-1:30. Duration is in seconds' },
|
486
|
+
{ :short => '-r', :long => '--recursive', :value => '', :help => 'for run_dir, recurse through sub-directories' },
|
487
|
+
{ :short => '-s', :long => '--scenario', :value => '<string>', :help => 'msl file, required for run_msl' },
|
488
|
+
{ :short => '-t', :long => '--test', :value => '', :help => 'do verify only' },
|
489
|
+
{ :short => '-v', :long => '--verbose', :value => '', :help => 'set Logger::DEBUG level' }
|
490
|
+
]
|
491
|
+
|
492
|
+
cmds = [
|
493
|
+
"mu cmd_appid:help",
|
494
|
+
"mu cmd_appid:run_file -s <file>",
|
495
|
+
"mu cmd_appid:run_dir -d <dir> [-r]",
|
496
|
+
"mu cmd_appid:running?"
|
497
|
+
]
|
498
|
+
|
499
|
+
max_long_size = helps.inject(0) { |memo, obj| [ obj[:long].size, memo ].max }
|
500
|
+
max_value_size = helps.inject(0) { |memo, obj| [ obj[:value].size, memo ].max }
|
501
|
+
puts "\n***WARNING***\nThe following commands will be deprecated in a future release:"
|
502
|
+
puts "cmd_runscale:run_file, cmd_runscale:run_dir, cmd_appid\n"
|
503
|
+
puts "Please use 'cmd_runscale:run_files'"
|
504
|
+
puts
|
505
|
+
puts "Usage: mu cmd_appid:<command> <options>"
|
506
|
+
puts
|
507
|
+
helps.each do |h|
|
508
|
+
puts "%-*s %*s %-*s %s" % [max_long_size, h[:long], 2, h[:short], max_value_size, h[:value], h[:help]]
|
509
|
+
end
|
510
|
+
puts
|
511
|
+
puts "Available Commands"
|
512
|
+
puts
|
513
|
+
cmds.each do | c |
|
514
|
+
puts c
|
515
|
+
end
|
516
|
+
puts
|
517
|
+
puts "Outputs"
|
518
|
+
puts
|
519
|
+
puts "app_id_stats.csv"
|
520
|
+
puts "scenario_name , passed , errors , timeouts,"
|
521
|
+
puts "client tx bytes/sec , client tx msgs/sec , client rx bytes/sec , client rx msgs/src,"
|
522
|
+
puts "server tx bytes/sec , server tx msgs/sec , server rx bytes/sec , server rx msgs/src"
|
523
|
+
puts
|
524
|
+
puts "app_id_status.json"
|
525
|
+
puts "contains the last status json object returned from polling, per scenario"
|
526
|
+
end
|
527
|
+
|
507
528
|
end
|
508
|
-
|
509
|
-
puts "Outputs"
|
510
|
-
puts
|
511
|
-
puts "app_id_stats.csv"
|
512
|
-
puts "scenario_name , passed , errors , timeouts,"
|
513
|
-
puts "client tx bytes/sec , client tx msgs/sec , client rx bytes/sec , client rx msgs/src,"
|
514
|
-
puts "server tx bytes/sec , server tx msgs/sec , server rx bytes/sec , server rx msgs/src"
|
515
|
-
puts
|
516
|
-
puts "app_id_status.json"
|
517
|
-
puts "contains the last status json object returned from polling, per scenario"
|
518
|
-
end
|
519
|
-
|
520
|
-
end
|
521
|
-
end # Command
|
529
|
+
end # Command
|
522
530
|
end # Mu
|
523
531
|
|