3b0eeb8b28d4a0b63a1bcdfe9c0dfb807f28a178
[samplevnf.git] / VNFs / DPPD-PROX / helper-scripts / rapid / runrapid.py
1 #!/usr/bin/python
2
3 ##
4 ## Copyright (c) 2010-2017 Intel Corporation
5 ##
6 ## Licensed under the Apache License, Version 2.0 (the "License");
7 ## you may not use this file except in compliance with the License.
8 ## You may obtain a copy of the License at
9 ##
10 ##     http://www.apache.org/licenses/LICENSE-2.0
11 ##
12 ## Unless required by applicable law or agreed to in writing, software
13 ## distributed under the License is distributed on an "AS IS" BASIS,
14 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 ## See the License for the specific language governing permissions and
16 ## limitations under the License.
17 ##
18
19 from __future__ import print_function
20
21 import os
22 import stat
23 import sys
24 import time
25 import subprocess
26 import getopt
27 import re
28 import logging
29 from logging.handlers import RotatingFileHandler
30 from logging import handlers
31 from prox_ctrl import prox_ctrl
32 import ConfigParser
33 import ast
34 import atexit
35 import csv
36
37 version="19.4.15"
38 env = "rapid" #Default string for environment
39 test = "basicrapid" #Default string for test
40 machine_map_file = "MachineMap" #Default string for machine map file
41 loglevel="DEBUG" # sets log level for writing to file
42 runtime=10 # time in seconds for 1 test run
43 configonly = False # IF True, the system will upload all the necessary config fiels to the VMs, but not start PROX and the actual testing
44
45 def usage():
46         print("usage: runrapid    [--version] [-v]")
47         print("                   [--env ENVIRONMENT_NAME]")
48         print("                   [--test TEST_NAME]")
49         print("                   [--map MACHINE_MAP_FILE]")
50         print("                   [--runtime TIME_FOR_TEST]")
51         print("                   [--configonly False|True]")
52         print("                   [--log DEBUG|INFO|WARNING|ERROR|CRITICAL]")
53         print("                   [-h] [--help]")
54         print("")
55         print("Command-line interface to runrapid")
56         print("")
57         print("optional arguments:")
58         print("  -v,  --version                 Show program's version number and exit")
59         print("  --env ENVIRONMENT_NAME         Parameters will be read from ENVIRONMENT_NAME.env Default is %s."%env)
60         print("  --test TEST_NAME               Test cases will be read from TEST_NAME.test Default is %s."%test)
61         print("  --map MACHINE_MAP_FILE Machine mapping will be read from MACHINE_MAP_FILE.cfg Default is %s."%machine_map_file)
62         print("  --runtime                      Specify time in seconds for 1 test run")
63         print("  --configonly                   If True, only upload all config files to the VMs, do not run the tests. Default is %s."%configonly)
64         print("  --log                          Specify logging level for log file output, screen output level is hard coded")
65         print("  -h, --help                     Show help message and exit.")
66         print("")
67
68 try:
69         opts, args = getopt.getopt(sys.argv[1:], "vh", ["version","help", "env=", "test=", "map=", "runtime=","configonly=","log="])
70 except getopt.GetoptError as err:
71         print("===========================================")
72         print(str(err))
73         print("===========================================")
74         usage()
75         sys.exit(2)
76 if args:
77         usage()
78         sys.exit(2)
79 for opt, arg in opts:
80         if opt in ("-h", "--help"):
81                 usage()
82                 sys.exit()
83         if opt in ("-v", "--version"):
84                 print("Rapid Automated Performance Indication for Dataplane "+version)
85                 sys.exit()
86         if opt in ("--env"):
87                 env = arg
88                 print ("Using '"+env+"' as name for the environment")
89         if opt in ("--test"):
90                 test = arg
91                 print ("Using '"+test+".test' for test case definition")
92         if opt in ("--map"):
93                 machine_map_file = arg
94                 print ("Using '"+machine_map_file+".cfg' for machine mapping")
95         if opt in ("--runtime"):
96                 runtime = arg
97                 print ("Runtime: "+ runtime)
98         if opt in ("--configonly"):
99                 configonly = arg
100                 print ("configonly: "+ configonly)
101         if opt in ("--log"):
102                 loglevel = arg
103                 print ("Log level: "+ loglevel)
104
105 class bcolors:
106     HEADER = '\033[95m'
107     OKBLUE = '\033[94m'
108     OKGREEN = '\033[92m'
109     WARNING = '\033[93m'
110     FAIL = '\033[91m'
111     ENDC = '\033[0m'
112     BOLD = '\033[1m'
113     UNDERLINE = '\033[4m'
114
115 # create formatters
116 screen_formatter = logging.Formatter("%(message)s")
117 file_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
118
119 # get a top-level logger,
120 # set its log level,
121 # BUT PREVENT IT from propagating messages to the root logger
122 #
123 log = logging.getLogger()
124 numeric_level = getattr(logging, loglevel.upper(), None)
125 if not isinstance(numeric_level, int):
126     raise ValueError('Invalid log level: %s' % loglevel)
127 log.setLevel(numeric_level)
128 log.propagate = 0
129
130 # create a console handler
131 # and set its log level to the command-line option 
132
133 console_handler = logging.StreamHandler(sys.stdout)
134 console_handler.setLevel(logging.INFO)
135 console_handler.setFormatter(screen_formatter)
136
137 # create a file handler
138 # and set its log level to DEBUG
139 #
140 log_file = 'RUN{}.{}.log'.format(env,test)
141 file_handler = logging.handlers.RotatingFileHandler(log_file, backupCount=10)
142 #file_handler = log.handlers.TimedRotatingFileHandler(log_file, 'D', 1, 5)
143 file_handler.setLevel(numeric_level)
144 file_handler.setFormatter(file_formatter)
145
146 # add handlers to the logger
147 #
148 log.addHandler(file_handler)
149 log.addHandler(console_handler)
150
151 # Check if log exists and should therefore be rolled
152 needRoll = os.path.isfile(log_file)
153
154
155 # This is a stale log, so roll it
156 if needRoll:    
157     # Add timestamp
158     log.debug('\n---------\nLog closed on %s.\n---------\n' % time.asctime())
159
160     # Roll over on application start
161     log.handlers[0].doRollover()
162
163 # Add timestamp
164 log.debug('\n---------\nLog started on %s.\n---------\n' % time.asctime())
165
166 log.debug("runrapid.py version: "+version)
167 #========================================================================
168 def connect_socket(client):
169         attempts = 1
170         log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
171         sock = None
172         while True:
173                 sock = client.prox_sock()
174                 if sock is not None:
175                         break
176                 attempts += 1
177                 if attempts > 20:
178                         log.exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
179                         raise Exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
180                 time.sleep(2)
181                 log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
182         log.info("Connected to PROX on %s" % client.ip())
183         return sock
184
185 def connect_client(client):
186         attempts = 1
187         log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
188         while True:
189                 try:
190                         client.connect()
191                         break
192                 except RuntimeWarning, ex:
193                         attempts += 1
194                         if attempts > 20:
195                                 log.exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
196                                 raise Exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
197                         time.sleep(2)
198                         log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
199         log.debug("Connected to VM on %s" % client.ip())
200
201 def run_iteration(gensock,sutsock):
202         sleep_time = 3
203         # Sleep_time is needed to be able to do accurate measurements to check for packet loss. We need to make this time large enough so that we do not take the first measurement while some packets from the previous tests migth still be in flight
204         time.sleep(sleep_time)
205         abs_old_rx, abs_old_tx, abs_old_drop, abs_old_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
206         gensock.start(gencores)
207         time.sleep(sleep_time)
208         if sutsock!='none':
209                 old_sut_rx, old_sut_tx, old_sut_drop, old_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
210         old_rx, old_tx, old_drop, old_tsc, tsc_hz = gensock.core_stats(genstatcores)
211         # Measure latency statistics per second
212         n_loops = 0
213         lat_min = 0
214         lat_max = 0
215         lat_avg = 0
216         used_avg = 0
217         while n_loops < float(runtime):
218                 n_loops +=1
219                 time.sleep(1)
220                 lat_min_sample, lat_max_sample, lat_avg_sample, used_sample = gensock.lat_stats(latcores)
221                 if lat_min > lat_min_sample:
222                         lat_min = lat_min_sample
223                 if lat_max < lat_max_sample:
224                         lat_max = lat_max_sample
225                 lat_avg = lat_avg + lat_avg_sample
226                 used_avg = used_avg + used_sample
227         lat_avg = lat_avg / n_loops
228         used_avg = used_avg / n_loops
229         # Get statistics after some execution time
230         new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
231         if sutsock!='none':
232                 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
233         #Stop generating
234         gensock.stop(gencores)
235         time.sleep(sleep_time)
236         abs_new_rx, abs_new_tx, abs_new_drop, abs_new_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
237         drop = new_drop-old_drop # drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
238         rx = new_rx - old_rx     # rx is all packets received by the nop task = all packets received in the gen VM
239         tx = new_tx - old_tx     # tx is all generated packets actually accepted by the interface
240         abs_dropped = (abs_new_tx - abs_old_tx) - (abs_new_rx - abs_old_rx)
241         tsc = new_tsc - old_tsc  # time difference between the 2 measurements, expressed in cycles.
242         pps_req_tx = (tx+drop-rx)*tsc_hz*1.0/(tsc*1000000)
243         pps_tx = tx*tsc_hz*1.0/(tsc*1000000)
244         pps_rx = rx*tsc_hz*1.0/(tsc*1000000)
245         if sutsock!='none':
246                 sut_rx = new_sut_rx - old_sut_rx
247                 sut_tx = new_sut_tx - old_sut_tx
248                 sut_tsc = new_sut_tsc - old_sut_tsc
249                 pps_sut_tx = sut_tx*sut_tsc_hz*1.0/(sut_tsc*1000000)
250                 pps_sut_tx_str = '{:>9.3f}'.format(pps_sut_tx)
251         else:
252                 pps_sut_tx = 0
253                 pps_sut_tx_str = 'NO MEAS.'
254         if (tx == 0):
255                 log.critical("TX = 0. Test interrupted since no packet has been sent.")
256                 raise Exception("TX = 0")
257         return(pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max,abs_dropped,(abs_new_tx - abs_old_tx),lat_min,used_avg)
258
259 def new_speed(speed,minspeed,maxspeed,success):
260         if success:
261                 minspeed = speed
262         else:
263                 maxspeed = speed
264         newspeed = (maxspeed+minspeed)/2.0
265         return (newspeed,minspeed,maxspeed)
266
267 def get_pps(speed,size):
268         return (speed * 100.0 / (8*(size+24)))
269
270 def run_speedtest(gensock,sutsock):
271         maxspeed = speed = STARTSPEED
272         minspeed = 0
273         size=PACKETSIZE-4
274         attempts = 0
275         log.info("+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
276         log.info("| Generator is sending UDP (1 flow) packets ("+ '{:>5}'.format(size+4) +" bytes) to SUT. SUT sends packets back                                                                                       |")
277         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
278         log.info("| Test   |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio | Result     |")
279         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
280         endpps_sut_tx_str = 'NO_RESULTS'
281         gensock.set_size(gencores,0,size) # This is setting the frame size
282         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
283         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
284         # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
285         gensock.start(latcores)
286         while (maxspeed-minspeed > ACCURACY):
287                 attempts += 1
288                 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
289                 sys.stdout.flush()
290                 # Start generating packets at requested speed (in % of a 10Gb/s link)
291                 gensock.speed(speed / len(gencores), gencores)
292                 time.sleep(1)
293                 # Get statistics now that the generation is stable and initial ARP messages are dealt with.
294                 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx, lat_min, lat_used = run_iteration(gensock,sutsock)
295                 drop_rate = 100.0*abs_dropped/abs_tx
296                 if lat_used < 0.95:
297                         lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
298                 else:
299                         lat_warning = ''
300                 if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
301                         log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+  '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  | SUCCESS    |'+lat_warning)
302                         endspeed = speed
303                         endpps_req_tx = pps_req_tx
304                         endpps_tx = pps_tx
305                         endpps_sut_tx_str = pps_sut_tx_str
306                         endpps_rx = pps_rx
307                         endlat_avg = lat_avg
308                         endlat_max = lat_max
309                         endabs_dropped = abs_dropped
310                         enddrop_rate = drop_rate
311                         endwarning = lat_warning
312                         success = True 
313                 else:
314                         abs_drop_rate_prefix = bcolors.ENDC
315                         if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
316                                 abs_drop_rate_prefix = bcolors.FAIL
317                         if (drop_rate < DROP_RATE_TRESHOLD):
318                                 drop_rate_prefix = bcolors.ENDC
319                         else:
320                                 drop_rate_prefix = bcolors.FAIL
321                         if (lat_avg< LAT_AVG_TRESHOLD):
322                                 lat_avg_prefix = bcolors.ENDC
323                         else:
324                                 lat_avg_prefix = bcolors.FAIL
325                         if (lat_max< LAT_MAX_TRESHOLD):
326                                 lat_max_prefix = bcolors.ENDC
327                         else:
328                                 lat_max_prefix = bcolors.FAIL
329                         if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
330                                 speed_prefix = bcolors.ENDC
331                         else:
332                                 speed_prefix = bcolors.FAIL
333                         log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% '+speed_prefix +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | ' + '{:>9.3f}'.format(pps_tx) +' Mpps | '+ bcolors.ENDC  + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+lat_avg_prefix+ '{:>9.0f}'.format(lat_avg)+' us   | '+lat_max_prefix+ '{:>9.0f}'.format(lat_max)+' us   | '+ abs_drop_rate_prefix + '{:>14d}'.format(abs_dropped)+drop_rate_prefix+ ' |''{:>9.2f}'.format(drop_rate)+bcolors.ENDC+ '%  | FAILED     |'+lat_warning)
334                         success = False 
335                 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
336         if endpps_sut_tx_str <>  'NO_RESULTS':
337                 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
338                 log.info('|{:>7}'.format('END')+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+ '{:>9.0f}'.format(endlat_max)+' us   | '+'{:>14d}'.format(endabs_dropped)+ ' |''{:>9.2f}'.format(enddrop_rate)+ '%  | SUCCESS    |'+endwarning)
339                 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
340                 writer.writerow({'flow':'1','size':(size+4),'endspeed':endspeed,'endspeedpps':get_pps(endspeed,size),'endpps_req_tx':endpps_req_tx,'endpps_tx':endpps_tx,'endpps_sut_tx_str':endpps_sut_tx_str,'endpps_rx':endpps_rx,'endlat_avg':endlat_avg,'endlat_max':endlat_max,'endabs_dropped':endabs_dropped,'enddrop_rate':enddrop_rate})
341         else:
342                 log.info('| Speed 0 or close to 0')
343         gensock.stop(latcores)
344
345 def run_flowtest(gensock,sutsock):
346         size=PACKETSIZE-4
347         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
348         log.info("| UDP, "+ '{:>5}'.format(size+4) +" bytes, different number of flows by randomizing SRC & DST UDP port                                                                                   |")
349         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
350         log.info("| Flows  |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
351         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
352         # To generate a desired number of flows, PROX will randomize the bits in source and destination ports, as specified by the bit masks in the flows variable. 
353         flows={\
354         1:      ['1000000000000000','1000000000000000'],\
355         2:      ['1000000000000000','100000000000000X'],\
356         4:      ['100000000000000X','100000000000000X'],\
357         8:      ['100000000000000X','10000000000000XX'],\
358         16:     ['10000000000000XX','10000000000000XX'],\
359         32:     ['10000000000000XX','1000000000000XXX'],\
360         64:     ['1000000000000XXX','1000000000000XXX'],\
361         128:    ['1000000000000XXX','100000000000XXXX'],\
362         256:    ['100000000000XXXX','100000000000XXXX'],\
363         512:    ['100000000000XXXX','10000000000XXXXX'],\
364         1024:   ['10000000000XXXXX','10000000000XXXXX'],\
365         2048:   ['10000000000XXXXX','1000000000XXXXXX'],\
366         4096:   ['1000000000XXXXXX','1000000000XXXXXX'],\
367         8192:   ['1000000000XXXXXX','100000000XXXXXXX'],\
368         16384:  ['100000000XXXXXXX','100000000XXXXXXX'],\
369         32768:  ['100000000XXXXXXX','10000000XXXXXXXX'],\
370         65535:  ['10000000XXXXXXXX','10000000XXXXXXXX'],\
371         131072: ['10000000XXXXXXXX','1000000XXXXXXXXX'],\
372         262144: ['1000000XXXXXXXXX','1000000XXXXXXXXX'],\
373         524280: ['1000000XXXXXXXXX','100000XXXXXXXXXX'],\
374         1048576:['100000XXXXXXXXXX','100000XXXXXXXXXX'],}
375         gensock.set_size(gencores,0,size) # This is setting the frame size
376         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
377         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
378         # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
379         gensock.start(latcores)
380         for flow_number in flow_size_list:
381                 attempts = 0
382                 gensock.reset_stats()
383                 if sutsock!='none':
384                         sutsock.reset_stats()
385                 source_port,destination_port = flows[flow_number]
386                 gensock.set_random(gencores,0,34,source_port,2)
387                 gensock.set_random(gencores,0,36,destination_port,2)
388                 endpps_sut_tx_str = 'NO_RESULTS'
389                 maxspeed = speed = STARTSPEED
390                 minspeed = 0
391                 while (maxspeed-minspeed > ACCURACY):
392                         attempts += 1
393                         print(str(flow_number)+' flows: Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
394                         sys.stdout.flush()
395                         # Start generating packets at requested speed (in % of a 10Gb/s link)
396                         gensock.speed(speed / len(gencores), gencores)
397                         time.sleep(1)
398                         # Get statistics now that the generation is stable and initial ARP messages are dealt with
399                         pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx, lat_min, lat_used = run_iteration(gensock,sutsock)
400                         drop_rate = 100.0*abs_dropped/abs_tx
401                         if lat_used < 0.95:
402                                 lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
403                         else:
404                                 lat_warning = ''
405                         if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
406                                 log.debug('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+  '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  | SUCCESS    |'+lat_warning)
407                                 endspeed = speed
408                                 endpps_req_tx = pps_req_tx
409                                 endpps_tx = pps_tx
410                                 endpps_sut_tx_str = pps_sut_tx_str
411                                 endpps_rx = pps_rx
412                                 endlat_avg = lat_avg 
413                                 endlat_max = lat_max 
414                                 endabs_dropped = abs_dropped
415                                 enddrop_rate = drop_rate
416                                 endwarning = lat_warning
417                                 success = True
418                         else:
419                                 abs_drop_rate_prefix = bcolors.ENDC
420                                 if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
421                                         abs_drop_rate_prefix = bcolors.FAIL
422                                 if (drop_rate < DROP_RATE_TRESHOLD):
423                                         drop_rate_prefix = bcolors.ENDC
424                                 else:
425                                         drop_rate_prefix = bcolors.FAIL
426                                 if (lat_avg< LAT_AVG_TRESHOLD):
427                                         lat_avg_prefix = bcolors.ENDC
428                                 else:
429                                         lat_avg_prefix = bcolors.FAIL
430                                 if (lat_max< LAT_MAX_TRESHOLD):
431                                         lat_max_prefix = bcolors.ENDC
432                                 else:
433                                         lat_max_prefix = bcolors.FAIL
434                                 if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
435                                         speed_prefix = bcolors.ENDC
436                                 else:
437                                         speed_prefix = bcolors.FAIL
438                                 log.debug('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% '+speed_prefix +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | ' + '{:>9.3f}'.format(pps_tx) +' Mpps | '+ bcolors.ENDC  + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+lat_avg_prefix+ '{:>9.0f}'.format(lat_avg)+' us   | '+lat_max_prefix+ '{:>9.0f}'.format(lat_max)+' us   | '+ abs_drop_rate_prefix + '{:>14d}'.format(abs_dropped)+drop_rate_prefix+ ' |''{:>9.2f}'.format(drop_rate)+bcolors.ENDC+ '%  | FAILED     |'+lat_warning)
439                                 success = False 
440                         speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
441                 if endpps_sut_tx_str <>  'NO_RESULTS':
442                         log.info('|{:>7}'.format(str(flow_number))+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+ '{:>9.0f}'.format(endlat_max)+' us   | '+ '{:>14d}'.format(endabs_dropped)+ ' |'+'{:>9.2f}'.format(enddrop_rate)+ '%  |'+endwarning)
443                         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
444                         writer.writerow({'flow':flow_number,'size':(size+4),'endspeed':endspeed,'endspeedpps':get_pps(endspeed,size),'endpps_req_tx':endpps_req_tx,'endpps_tx':endpps_tx,'endpps_sut_tx_str':endpps_sut_tx_str,'endpps_rx':endpps_rx,'endlat_avg':endlat_avg,'endlat_max':endlat_max,'endabs_dropped':endabs_dropped,'enddrop_rate':enddrop_rate})
445                 else:
446                         log.info('|{:>7}'.format(str(flow_number))+" | Speed 0 or close to 0")
447         gensock.stop(latcores)
448
449 def run_sizetest(gensock,sutsock):
450         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
451         log.info("| UDP, 1 flow, different packet sizes                                                                                                                             |")
452         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
453         log.info("| Pktsize|  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
454         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
455         gensock.start(latcores)
456         for size in packet_size_list:
457                 size = size-4
458                 attempts = 0
459                 gensock.reset_stats()
460                 if sutsock!='none':
461                         sutsock.reset_stats()
462                 gensock.set_size(gencores,0,size) # This is setting the frame size
463                 gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
464                 gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
465                 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
466                 endpps_sut_tx_str = 'NO_RESULTS'
467                 maxspeed = speed = STARTSPEED
468                 minspeed = 0
469                 while (maxspeed-minspeed > ACCURACY):
470                         attempts += 1
471                         print(str(size+4)+' bytes: Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
472                         sys.stdout.flush()
473                         # Start generating packets at requested speed (in % of a 10Gb/s link)
474                         gensock.speed(speed / len(gencores), gencores)
475                         # Get statistics now that the generation is stable and initial ARP messages are dealt with
476                         pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx, lat_min, lat_used = run_iteration(gensock,sutsock)
477                         drop_rate = 100.0*abs_dropped/abs_tx
478                         if lat_used < 0.95:
479                                 lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
480                         else:
481                                 lat_warning = ''
482                         if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
483                                 log.debug('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+  '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  | SUCCESS    |'+lat_warning)
484                                 endspeed = speed
485                                 endpps_req_tx = pps_req_tx
486                                 endpps_tx = pps_tx
487                                 endpps_sut_tx_str = pps_sut_tx_str
488                                 endpps_rx = pps_rx
489                                 endlat_avg = lat_avg 
490                                 endlat_max = lat_max 
491                                 endabs_dropped = abs_dropped
492                                 enddrop_rate = drop_rate
493                                 endwarning = lat_warning
494                                 success = True
495                         else:
496                                 abs_drop_rate_prefix = bcolors.ENDC
497                                 if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
498                                         abs_drop_rate_prefix = bcolors.FAIL
499                                 if (drop_rate < DROP_RATE_TRESHOLD):
500                                         drop_rate_prefix = bcolors.ENDC
501                                 else:
502                                         drop_rate_prefix = bcolors.FAIL
503                                 if (lat_avg< LAT_AVG_TRESHOLD):
504                                         lat_avg_prefix = bcolors.ENDC
505                                 else:
506                                         lat_avg_prefix = bcolors.FAIL
507                                 if (lat_max< LAT_MAX_TRESHOLD):
508                                         lat_max_prefix = bcolors.ENDC
509                                 else:
510                                         lat_max_prefix = bcolors.FAIL
511                                 if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
512                                         speed_prefix = bcolors.ENDC
513                                 else:
514                                         speed_prefix = bcolors.FAIL
515                                 log.debug('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% '+speed_prefix +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | ' + '{:>9.3f}'.format(pps_tx) +' Mpps | '+ bcolors.ENDC  + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+lat_avg_prefix+ '{:>9.0f}'.format(lat_avg)+' us   | '+lat_max_prefix+ '{:>9.0f}'.format(lat_max)+' us   | '+ abs_drop_rate_prefix + '{:>14d}'.format(abs_dropped)+drop_rate_prefix+ ' |''{:>9.2f}'.format(drop_rate)+bcolors.ENDC+ '%  | FAILED     |'+ lat_warning)
516                                 success = False 
517                         speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
518                 if endpps_sut_tx_str <>  'NO_RESULTS':
519                         log.info('|{:>7}'.format(size+4)+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+'{:>9.0f}'.format(endlat_max)+' us   | '+ '{:>14d}'.format(endabs_dropped)+ ' |'+'{:>9.2f}'.format(enddrop_rate)+ '%  |'+ endwarning)
520                         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
521                         writer.writerow({'flow':'1','size':(size+4),'endspeed':endspeed,'endspeedpps':get_pps(endspeed,size),'endpps_req_tx':endpps_req_tx,'endpps_tx':endpps_tx,'endpps_sut_tx_str':endpps_sut_tx_str,'endpps_rx':endpps_rx,'endlat_avg':endlat_avg,'endlat_max':endlat_max,'endabs_dropped':endabs_dropped,'enddrop_rate':enddrop_rate})
522                 else:
523                         log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
524         gensock.stop(latcores)
525
526 def run_max_frame_rate(gensock,sutsock):
527         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------+")
528         log.info("| UDP, 1 flow, different packet sizes                                                                                                                       |")
529         log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
530         log.info("|Pktsz| Speed requested  | Sent to NIC | Sent by Gen | Fwrd by SUT | Rec. by Gen | Avg. Latency| Max. Latency|   Sent    |  Received |  Lost   | Total Lost |")
531         log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
532         sleep_time = 3
533         gensock.start(latcores)
534         for size in packet_size_list:
535                 # Sleep_time is needed to be able to do accurate measurements to check for packet loss. We need to make this time large enough so that we do not take the first measurement while some packets from the previous tests migth still be in flight
536                 time.sleep(sleep_time)
537                 size = size-4
538                 gensock.reset_stats()
539                 if sutsock!='none':
540                         sutsock.reset_stats()
541                 gensock.set_size(gencores,0,size) # This is setting the frame size
542                 gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
543                 gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
544                 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
545                 pps_sut_tx_str = 'NO_RESULTS'
546                 speed = STARTSPEED
547                 # Start generating packets at requested speed (in % of a 10Gb/s link)
548                 gensock.speed(speed / len(gencores), gencores)
549                 duration = float(runtime)
550                 first = 1
551                 tot_drop = 0
552                 if sutsock!='none':
553                         old_sut_rx, old_sut_tx, old_sut_drop, old_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
554                 old_rx, old_tx, old_drop, old_tsc, tsc_hz = gensock.core_stats(genstatcores)
555                 gensock.start(gencores)
556                 while (duration > 0):
557                         time.sleep(0.5)
558                         lat_min, lat_max, lat_avg, lat_used = gensock.lat_stats(latcores)
559                         if lat_used < 0.95:
560                                 lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
561                         else:
562                                 lat_warning = ''
563                         # Get statistics after some execution time
564                         new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
565                         if sutsock!='none':
566                                 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
567                         drop = new_drop-old_drop # drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
568                         rx = new_rx - old_rx     # rx is all packets received by the nop task = all packets received in the gen VM
569                         tx = new_tx - old_tx     # tx is all generated packets actually accepted by the interface
570                         tsc = new_tsc - old_tsc  # time difference between the 2 measurements, expressed in cycles.
571                         if tsc == 0 :
572                                 continue
573                         if sutsock!='none':
574                                 sut_rx = new_sut_rx - old_sut_rx
575                                 sut_tx = new_sut_tx - old_sut_tx
576                                 sut_tsc = new_sut_tsc - old_sut_tsc
577                                 if sut_tsc == 0 :
578                                         continue
579                         duration = duration - 1
580                         old_drop = new_drop
581                         old_rx = new_rx
582                         old_tx = new_tx
583                         old_tsc = new_tsc
584                         pps_req_tx = (tx+drop-rx)*tsc_hz*1.0/(tsc*1000000)
585                         pps_tx = tx*tsc_hz*1.0/(tsc*1000000)
586                         pps_rx = rx*tsc_hz*1.0/(tsc*1000000)
587                         if sutsock!='none':
588                                 old_sut_tx = new_sut_tx
589                                 old_sut_rx = new_sut_rx
590                                 old_sut_tsc= new_sut_tsc
591                                 pps_sut_tx = sut_tx*sut_tsc_hz*1.0/(sut_tsc*1000000)
592                                 pps_sut_tx_str = '{:>7.3f}'.format(pps_sut_tx)
593                         else:
594                                 pps_sut_tx = 0
595                                 pps_sut_tx_str = 'NO MEAS.'
596                         if (tx == 0):
597                                 log.critical("TX = 0. Test interrupted since no packet has been sent.")
598                                 raise Exception("TX = 0")
599                         tot_drop = tot_drop + tx - rx
600
601                         if pps_sut_tx_str <>  'NO_RESULTS':
602                                 # First second mpps are not valid as there is no alignement between time the generator is started and per seconds stats
603                                 if (first):
604                                         log.info('|{:>4}'.format(size+4)+" |" + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps|'+'             |' +'             |'  +'             |'+ '             |'+ '{:>8.0f}'.format(lat_avg)+' us  |'+'{:>8.0f}'.format(lat_max)+' us  | ' + '{:>9.0f}'.format(tx) + ' | '+ '{:>9.0f}'.format(rx) + ' | '+ '{:>7.0f}'.format(tx-rx) + ' | '+'{:>7.0f}'.format(tot_drop) +'    |'+lat_warning)
605                                 else:
606                                         log.info('|{:>4}'.format(size+4)+" |" + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps|'+ '{:>7.3f}'.format(pps_req_tx)+' Mpps |'+ '{:>7.3f}'.format(pps_tx) +' Mpps |' + '{:>7}'.format(pps_sut_tx_str) +' Mpps |'+ '{:>7.3f}'.format(pps_rx)+' Mpps |'+ '{:>8.0f}'.format(lat_avg)+' us  |'+'{:>8.0f}'.format(lat_max)+' us  | ' + '{:>9.0f}'.format(tx) + ' | '+ '{:>9.0f}'.format(rx) + ' | '+ '{:>7.0f}'.format(tx-rx) + ' | '+ '{:>7.0f}'.format(tot_drop) +'    |'+lat_warning)
607                         else:
608                                 log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
609                         first = 0
610                         if (duration <= 0):
611                                 #Stop generating
612                                 gensock.stop(gencores)
613                                 time.sleep(sleep_time)
614                                 lat_min, lat_max, lat_avg, lat_used = gensock.lat_stats(latcores)
615                                 if lat_used < 0.95:
616                                         lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
617                                 else:
618                                         lat_warning = ''
619                                 # Get statistics after some execution time
620                                 new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
621                                 if sutsock!='none':
622                                         new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
623                                 drop = new_drop-old_drop # drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
624                                 rx = new_rx - old_rx     # rx is all packets received by the nop task = all packets received in the gen VM
625                                 tx = new_tx - old_tx     # tx is all generated packets actually accepted by the interface
626                                 tsc = new_tsc - old_tsc  # time difference between the 2 measurements, expressed in cycles.
627                                 tot_drop = tot_drop + tx - rx
628                                 if sutsock!='none':
629                                         sut_rx = new_sut_rx - old_sut_rx
630                                         sut_tx = new_sut_tx - old_sut_tx
631                                         sut_tsc = new_sut_tsc - old_sut_tsc
632                                 if pps_sut_tx_str <>  'NO_RESULTS':
633                                         log.info('|{:>4}'.format(size+4)+" |" + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps|'+'             |' +'             |'  +'             |'+ '             |'+ '{:>8.0f}'.format(lat_avg)+' us  |'+'{:>8.0f}'.format(lat_max)+' us  | ' + '{:>9.0f}'.format(tx) + ' | '+ '{:>9.0f}'.format(rx) + ' | '+ '{:>7.0f}'.format(tx-rx) + ' | '+ '{:>7.0f}'.format(tot_drop) +'    |'+lat_warning)
634                 log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
635         gensock.stop(latcores)
636
637
638
639 def run_irqtest(sock):
640         log.info("+----------------------------------------------------------------------------------------------------------------------------")
641         log.info("| Measuring time probably spent dealing with an interrupt. Interrupting DPDK cores for more than 50us might be problematic   ")
642         log.info("| and result in packet loss. The first row shows the interrupted time buckets: first number is the bucket between 0us and    ")
643         log.info("| that number expressed in us and so on. The numbers in the other rows show how many times per second, the program was       ")
644         log.info("| interrupted for a time as specified by its bucket. '0' is printed when there are no interrupts in this bucket throughout   ")
645         log.info("| the duration of the test. This is to avoid rounding errors in the case of 0.0                                              ") 
646         log.info("+----------------------------------------------------------------------------------------------------------------------------")
647         sys.stdout.flush()
648         buckets=sock.show_irq_buckets(1)
649         print('Measurement ongoing ... ',end='\r')
650         sock.stop(irqcores)
651         old_irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)] 
652         irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
653         irq[0][0] = 'bucket us' 
654         for j,bucket in enumerate(buckets,start=1):
655                 irq[0][j] = '<'+ bucket
656         irq[0][-1] = '>'+ buckets [-2]
657         sock.start(irqcores)
658         time.sleep(2)
659         for j,bucket in enumerate(buckets,start=1):
660                 for i,irqcore in enumerate(irqcores,start=1):
661                         old_irq[i][j] = sock.irq_stats(irqcore,j-1)
662         time.sleep(float(runtime))
663         sock.stop(irqcores)
664         for i,irqcore in enumerate(irqcores,start=1):
665                 irq[i][0]='core %s '%irqcore
666                 for j,bucket in enumerate(buckets,start=1):
667                         diff =  sock.irq_stats(irqcore,j-1) - old_irq[i][j]
668                         if diff == 0:
669                                 irq[i][j] = '0'
670                         else:
671                                 irq[i][j] = str(round(diff/float(runtime), 2))
672         for row in irq:
673                 log.info(''.join(['{:>12}'.format(item) for item in row]))
674 #       log.info('\n'.join([''.join(['{:>12}'.format(item) for item in row]) for row in irq]))
675
676 def run_impairtest(gensock,sutsock):
677         size=PACKETSIZE-4
678         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
679         log.info("| Generator is sending UDP (1 flow) packets ("+ '{:>5}'.format(size+4) +" bytes) to SUT via GW dropping and delaying packets. SUT sends packets back. Use ctrl-c to stop the test        |")
680         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
681         log.info("| Test   |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
682         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
683         attempts = 0
684         gensock.set_size(gencores,0,size) # This is setting the frame size
685         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
686         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
687         # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
688         gensock.start(latcores)
689         speed = STARTSPEED
690         gensock.speed(speed / len(gencores), gencores)
691         while True:
692                 attempts += 1
693                 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
694                 sys.stdout.flush()
695                 time.sleep(1)
696                 # Get statistics now that the generation is stable and NO ARP messages any more
697                 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx, lat_min, lat_used = run_iteration(gensock,sutsock)
698                 drop_rate = 100.0*abs_dropped/abs_tx
699                 if lat_used < 0.95:
700                         lat_warning = bcolors.FAIL + ' Potential latency accuracy problem: {:>3.0f}%'.format(lat_used*100) +  bcolors.ENDC
701                 else:
702                         lat_warning = ''
703                 log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+ '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  |'+lat_warning)
704                 writer.writerow({'flow':'1','size':(size+4),'endspeed':speed,'endspeedpps':get_pps(speed,size),'endpps_req_tx':pps_req_tx,'endpps_tx':pps_tx,'endpps_sut_tx_str':pps_sut_tx_str,'endpps_rx':pps_rx,'endlat_avg':lat_avg,'endlat_max':lat_max,'endabs_dropped':abs_dropped,'enddrop_rate':drop_rate})
705         gensock.stop(latcores)
706
707 def run_inittest(gensock):
708 # Running at low speed to make sure the ARP messages can get through.
709 # If not doing this, the ARP message could be dropped by a switch in overload and then the test will not give proper results
710 # Note hoever that if we would run the test steps during a very long time, the ARP would expire in the switch.
711 # PROX will send a new ARP request every seconds so chances are very low that they will all fail to get through
712         gensock.speed(0.01 / len(gencores), gencores)
713         gensock.start(genstatcores)
714         time.sleep(2)
715         gensock.stop(genstatcores)
716
717 global sutstatcores
718 global genstatcores
719 global latcores
720 global gencores
721 global irqcores
722 global PACKETSIZE
723 global packet_size_list
724 global flow_size_list
725 global required_number_of_test_machines
726 clients =[]
727 socks =[]
728 socks_control =[]
729 vmDPIP =[]
730 vmAdminIP =[]
731 vmDPmac =[]
732 hexDPIP =[]
733 config_file =[]
734 prox_socket =[]
735 prox_launch_exit =[]
736 auto_start =[]
737 mach_type =[]
738 sock_type =[]
739
740 data_file = 'RUN{}.{}.csv'.format(env,test)
741 data_csv_file = open(data_file,'w')
742 testconfig = ConfigParser.RawConfigParser()
743 testconfig.read(test+'.test')
744 required_number_of_test_machines = testconfig.get('DEFAULT', 'total_number_of_test_machines')
745 config = ConfigParser.RawConfigParser()
746 config.read(env+'.env')
747 machine_map = ConfigParser.RawConfigParser()
748 machine_map.read(machine_map_file +'.cfg')
749 key = config.get('OpenStack', 'key')
750 total_number_of_machines = config.get('rapid', 'total_number_of_machines')
751 if int(required_number_of_test_machines) > int(total_number_of_machines):
752         log.exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
753         raise Exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
754 for vm in range(1, int(total_number_of_machines)+1):
755         vmAdminIP.append(config.get('M%d'%vm, 'admin_ip'))
756         vmDPmac.append(config.get('M%d'%vm, 'dp_mac'))
757         vmDPIP.append(config.get('M%d'%vm, 'dp_ip'))
758         ip = vmDPIP[-1].split('.')
759         hexDPIP.append(hex(int(ip[0]))[2:].zfill(2) + ' ' + hex(int(ip[1]))[2:].zfill(2) + ' ' + hex(int(ip[2]))[2:].zfill(2) + ' ' + hex(int(ip[3]))[2:].zfill(2))
760 machine_index = []
761 for vm in range(1, int(required_number_of_test_machines)+1):
762         machine_index.append(int(machine_map.get('TestM%d'%vm, 'machine_index'))-1)
763         prox_socket.append(testconfig.getboolean('TestM%d'%vm, 'prox_socket'))
764 for vm in range(1, int(required_number_of_test_machines)+1):
765         if prox_socket[vm-1]:
766                 prox_launch_exit.append(testconfig.getboolean('TestM%d'%vm, 'prox_launch_exit'))
767                 config_file.append(testconfig.get('TestM%d'%vm, 'config_file'))
768                 with open('{}_{}_parameters{}.lua'.format(env,test,vm), "w") as f:
769                         f.write('name="%s"\n'% testconfig.get('TestM%d'%vm, 'name'))
770                         f.write('local_ip="%s"\n'% vmDPIP[machine_index[vm-1]])
771                         f.write('local_hex_ip="%s"\n'% hexDPIP[machine_index[vm-1]])
772                         if re.match('(l2){0,1}gen\.cfg',config_file[-1]):
773                                 gencores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'gencores'))
774                                 latcores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'latcores'))
775                                 STARTSPEED = float(testconfig.get('TestM%d'%vm, 'startspeed'))
776                                 genstatcores = gencores + latcores
777                                 auto_start.append(False)
778                                 mach_type.append('gen')
779                                 f.write('gencores="%s"\n'% ','.join(map(str, gencores)))
780                                 f.write('latcores="%s"\n'% ','.join(map(str, latcores)))
781                                 destVMindex = int(testconfig.get('TestM%d'%vm, 'dest_vm'))-1
782                                 f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
783                                 f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
784                                 f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
785                         elif re.match('(l2){0,1}gen_gw\.cfg',config_file[-1]):
786                                 gencores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'gencores'))
787                                 latcores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'latcores'))
788                                 STARTSPEED = float(testconfig.get('TestM%d'%vm, 'startspeed'))
789                                 genstatcores = gencores + latcores
790                                 auto_start.append(False)
791                                 mach_type.append('gen')
792                                 f.write('gencores="%s"\n'% ','.join(map(str, gencores)))
793                                 f.write('latcores="%s"\n'% ','.join(map(str, latcores)))
794                                 gwVMindex = int(testconfig.get('TestM%d'%vm, 'gw_vm')) -1
795                                 f.write('gw_ip="%s"\n'% vmDPIP[machine_index[gwVMindex]])
796                                 f.write('gw_hex_ip="%s"\n'% hexDPIP[machine_index[gwVMindex]])
797                                 destVMindex = int(testconfig.get('TestM%d'%vm, 'dest_vm'))-1
798                                 f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
799                                 f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
800                                 f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
801                         elif  re.match('(l2){0,1}swap.*\.cfg',config_file[-1]):
802                                 sutstatcores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'swapcores'))
803                                 auto_start.append(True)
804                                 mach_type.append('sut')
805                                 f.write('swapcores="%s"\n'% ','.join(map(str, sutstatcores)))
806                         elif config_file[-1] == 'secgw1.cfg':
807                                 auto_start.append(True)
808                                 mach_type.append('none')
809                                 f.write('secgwcores="%s"\n'% ','.join(map(str, ast.literal_eval(testconfig.get('TestM%d'%vm, 'secgwcores')))))
810                                 destVMindex = int(testconfig.get('TestM%d'%vm, 'dest_vm'))-1
811                                 f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
812                                 f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
813                                 f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
814                         elif config_file[-1] == 'secgw2.cfg':
815                                 sutstatcores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'secgwcores'))
816                                 auto_start.append(True)
817                                 mach_type.append('sut')
818                                 f.write('secgwcores="%s"\n'% ','.join(map(str, sutstatcores)))
819                         elif config_file[-1] == 'impair.cfg':
820                                 auto_start.append(True)
821                                 mach_type.append('none')
822                                 f.write('impaircores="%s"\n'% ','.join(map(str, ast.literal_eval(testconfig.get('TestM%d'%vm, 'impaircores')))))
823                         elif config_file[-1] == 'irq.cfg':
824                                 irqcores = ast.literal_eval(testconfig.get('TestM%d'%vm, 'irqcores'))
825                                 auto_start.append(False)
826                                 mach_type.append('irq')
827                                 f.write('irqcores="%s"\n'% ','.join(map(str, irqcores)))
828                 f.close
829 #####################################################################################
830 def exit_handler():
831         log.debug ('exit cleanup')
832         for index, sock in enumerate(socks):
833                 if socks_control[index]:
834                         sock.quit()
835         for client in clients:
836                 client.close()
837         data_csv_file.close
838         sys.exit(0)
839
840 atexit.register(exit_handler)
841
842 for vm in range(0, int(required_number_of_test_machines)):
843         if prox_socket[vm]:
844                 clients.append(prox_ctrl(vmAdminIP[machine_index[vm]], key+'.pem','root'))
845                 connect_client(clients[-1])
846 # Creating script to bind the right network interface to the poll mode driver
847                 devbindfile = '{}_{}_devbindvm{}.sh'.format(env,test, vm+1)
848                 with open("devbind.sh") as f:
849                         newText=f.read().replace('MACADDRESS', vmDPmac[machine_index[vm]])
850                         with open(devbindfile, "w") as f:
851                                 f.write(newText)
852                 st = os.stat(devbindfile)
853                 os.chmod(devbindfile, st.st_mode | stat.S_IEXEC)
854                 clients[-1].scp_put('./%s'%devbindfile, '/root/devbind.sh')
855                 cmd = '/root/devbind.sh'
856                 clients[-1].run_cmd(cmd)
857                 log.debug("devbind.sh running on VM%d"%(vm+1))
858                 clients[-1].scp_put('./%s'%config_file[vm], '/root/%s'%config_file[vm])
859                 clients[-1].scp_put('./{}_{}_parameters{}.lua'.format(env,test, vm+1), '/root/parameters.lua')
860                 if not configonly:
861                         if prox_launch_exit[vm]:
862                                 log.debug("Starting PROX on VM%d"%(vm+1))
863                                 if auto_start[vm]:
864                                         cmd = '/root/prox/build/prox -t -o cli -f /root/%s'%config_file[vm]
865                                 else:
866                                         cmd = '/root/prox/build/prox -e -t -o cli -f /root/%s'%config_file[vm]
867                                 clients[-1].fork_cmd(cmd, 'PROX Testing on TestM%d'%(vm+1))
868                         socks_control.append(prox_launch_exit[vm])
869                         socks.append(connect_socket(clients[-1]))
870                         sock_type.append(mach_type[vm])
871 socks.append('none')
872 socks_control.append(False)
873
874 def get_BinarySearchParams() :
875         global DROP_RATE_TRESHOLD
876         global LAT_AVG_TRESHOLD
877         global LAT_MAX_TRESHOLD
878         global ACCURACY
879         global STARTSPEED
880         DROP_RATE_TRESHOLD = float(testconfig.get('BinarySearchParams', 'drop_rate_threshold'))
881         LAT_AVG_TRESHOLD = float(testconfig.get('BinarySearchParams', 'lat_avg_threshold'))
882         LAT_MAX_TRESHOLD = float(testconfig.get('BinarySearchParams', 'lat_max_threshold'))
883         ACCURACY = float(testconfig.get('BinarySearchParams', 'accuracy'))
884         
885 if configonly:
886         sys.exit()
887 ####################################################
888 # Run test cases
889 # Best to run the flow test at the end since otherwise the tests coming after might be influenced by the big number of entries in the switch flow tables
890 ####################################################
891 gensock_index = sock_type.index('gen') if 'gen' in sock_type else -1
892 sutsock_index = sock_type.index('sut') if 'sut' in sock_type else -1
893 irqsock_index = sock_type.index('irq') if 'irq' in sock_type else -1
894 number_of_tests = testconfig.get('DEFAULT', 'number_of_tests')
895 with data_csv_file:
896         fieldnames = ['flow','size','endspeed','endspeedpps','endpps_req_tx','endpps_tx','endpps_sut_tx_str','endpps_rx','endlat_avg','endlat_max','endabs_dropped','enddrop_rate']
897         writer = csv.DictWriter(data_csv_file, fieldnames=fieldnames)
898         writer.writeheader()
899         for test_nr in range(1, int(number_of_tests)+1):
900                 test=testconfig.get('test%d'%test_nr,'test')
901                 log.info(test)
902                 if test == 'speedtest':
903                         get_BinarySearchParams()
904                         PACKETSIZE = int(testconfig.get('test%d'%test_nr, 'packetsize'))
905                         run_speedtest(socks[gensock_index],socks[sutsock_index])
906                 elif test == 'flowtest':
907                         get_BinarySearchParams()
908                         PACKETSIZE = int(testconfig.get('test%d'%test_nr, 'packetsize'))
909                         flow_size_list = ast.literal_eval(testconfig.get('test%d'%test_nr, 'flows'))
910                         run_flowtest(socks[gensock_index],socks[sutsock_index])
911                 elif test == 'sizetest':
912                         get_BinarySearchParams()
913                         packet_size_list = ast.literal_eval(testconfig.get('test%d'%test_nr, 'packetsizes'))
914                         run_sizetest(socks[gensock_index],socks[sutsock_index])
915                 elif test == 'max_frame_rate':
916 #                       PACKETSIZE = int(testconfig.get('test%d'%test_nr, 'packetsize'))
917                         packet_size_list = ast.literal_eval(testconfig.get('test%d'%test_nr, 'packetsizes'))
918                         run_max_frame_rate(socks[gensock_index],socks[sutsock_index])
919                 elif test == 'impairtest':
920                         get_BinarySearchParams()
921                         PACKETSIZE = int(testconfig.get('test%d'%test_nr, 'packetsize'))
922                         run_impairtest(socks[gensock_index],socks[sutsock_index])
923                 elif test == 'irqtest':
924                         run_irqtest(socks[irqsock_index])
925                 elif test == 'inittest':
926                         run_inittest(socks[gensock_index])
927 ####################################################