4 ## Copyright (c) 2010-2017 Intel Corporation
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
10 ## http://www.apache.org/licenses/LICENSE-2.0
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.
19 from __future__ import print_function
29 from logging.handlers import RotatingFileHandler
30 from logging import handlers
31 from prox_ctrl import prox_ctrl
38 env = "rapid" #Default string for environment
39 test = "basicrapid" #Default string for test
40 loglevel="DEBUG" # sets log level for writing to file
41 runtime=10 # time in seconds for 1 test run
42 configonly = False # IF True, the system will upload all the necessary config fiels to the VMs, but not start PROX and the actual testing
45 print("usage: runrapid [--version] [-v]")
46 print(" [--env ENVIRONMENT_NAME]")
47 print(" [--test TEST_NAME]")
48 print(" [--runtime TIME_FOR_TEST]")
49 print(" [--configonly False|True]")
50 print(" [--log DEBUG|INFO|WARNING|ERROR|CRITICAL]")
51 print(" [-h] [--help]")
53 print("Command-line interface to runrapid")
55 print("optional arguments:")
56 print(" -v, --version Show program's version number and exit")
57 print(" --env ENVIRONMENT_NAME Parameters will be read from ENVIRONMENT_NAME.env Default is %s."%env)
58 print(" --test TEST_NAME Test cases will be read from TEST_NAME.test Default is %s."%test)
59 print(" --runtime Specify time in seconds for 1 test run")
60 print(" --configonly If True, only upload all config files to the VMs, do not run the tests. Default is %s."%configonly)
61 print(" --log Specify logging level for log file output, screen output level is hard coded")
62 print(" -h, --help Show help message and exit.")
66 opts, args = getopt.getopt(sys.argv[1:], "vh", ["version","help", "env=", "test=","runtime=","configonly=","log="])
67 except getopt.GetoptError as err:
68 print("===========================================")
70 print("===========================================")
77 if opt in ("-h", "--help"):
80 if opt in ("-v", "--version"):
81 print("Rapid Automated Performance Indication for Dataplane "+version)
85 print ("Using '"+env+"' as name for the environment")
88 print ("Using '"+test+".test' for test case definition")
89 if opt in ("--runtime"):
91 print ("Runtime: "+ runtime)
92 if opt in ("--configonly"):
94 print ("configonly: "+ configonly)
97 print ("Log level: "+ loglevel)
107 UNDERLINE = '\033[4m'
110 screen_formatter = logging.Formatter("%(message)s")
111 file_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
113 # get a top-level logger,
115 # BUT PREVENT IT from propagating messages to the root logger
117 log = logging.getLogger()
118 numeric_level = getattr(logging, loglevel.upper(), None)
119 if not isinstance(numeric_level, int):
120 raise ValueError('Invalid log level: %s' % loglevel)
121 log.setLevel(numeric_level)
124 # create a console handler
125 # and set its log level to the command-line option
127 console_handler = logging.StreamHandler(sys.stdout)
128 console_handler.setLevel(logging.INFO)
129 console_handler.setFormatter(screen_formatter)
131 # create a file handler
132 # and set its log level to DEBUG
134 log_file = 'RUN' +env+'.'+test+'.log'
135 data_file = 'RUN' +env+'.'+test+'.csv'
136 file_handler = logging.handlers.RotatingFileHandler(log_file, backupCount=10)
137 #file_handler = log.handlers.TimedRotatingFileHandler(log_file, 'D', 1, 5)
138 file_handler.setLevel(numeric_level)
139 file_handler.setFormatter(file_formatter)
141 # add handlers to the logger
143 log.addHandler(file_handler)
144 log.addHandler(console_handler)
146 # Check if log exists and should therefore be rolled
147 needRoll = os.path.isfile(log_file)
150 # This is a stale log, so roll it
153 log.debug('\n---------\nLog closed on %s.\n---------\n' % time.asctime())
155 # Roll over on application start
156 log.handlers[0].doRollover()
159 log.debug('\n---------\nLog started on %s.\n---------\n' % time.asctime())
161 log.debug("runrapid.py version: "+version)
162 #========================================================================
163 def connect_socket(client):
165 log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
168 sock = client.prox_sock()
173 log.exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
174 raise Exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
176 log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
177 log.info("Connected to PROX on %s" % client.ip())
180 def connect_client(client):
182 log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
187 except RuntimeWarning, ex:
190 log.exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
191 raise Exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
193 log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
194 log.debug("Connected to VM on %s" % client.ip())
196 def run_iteration(gensock,sutsock):
198 # 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
199 time.sleep(sleep_time)
200 abs_old_rx, abs_old_tx, abs_old_drop, abs_old_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
201 gensock.start(gencores)
202 time.sleep(sleep_time)
204 old_sut_rx, old_sut_tx, old_sut_drop, old_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
205 old_rx, old_tx, old_drop, old_tsc, tsc_hz = gensock.core_stats(genstatcores)
206 time.sleep(float(runtime))
207 lat_min, lat_max, lat_avg = gensock.lat_stats(latcores)
208 # Get statistics after some execution time
209 new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
211 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
213 gensock.stop(gencores)
214 time.sleep(sleep_time)
215 abs_new_rx, abs_new_tx, abs_new_drop, abs_new_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
216 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
217 rx = new_rx - old_rx # rx is all packets received by the nop task = all packets received in the gen VM
218 tx = new_tx - old_tx # tx is all generated packets actually accepted by the interface
219 abs_dropped = (abs_new_tx - abs_old_tx) - (abs_new_rx - abs_old_rx)
220 tsc = new_tsc - old_tsc # time difference between the 2 measurements, expressed in cycles.
221 pps_req_tx = (tx+drop-rx)*tsc_hz*1.0/(tsc*1000000)
222 pps_tx = tx*tsc_hz*1.0/(tsc*1000000)
223 pps_rx = rx*tsc_hz*1.0/(tsc*1000000)
225 sut_rx = new_sut_rx - old_sut_rx
226 sut_tx = new_sut_tx - old_sut_tx
227 sut_tsc = new_sut_tsc - old_sut_tsc
228 pps_sut_tx = sut_tx*sut_tsc_hz*1.0/(sut_tsc*1000000)
229 pps_sut_tx_str = '{:>9.3f}'.format(pps_sut_tx)
232 pps_sut_tx_str = 'NO MEAS.'
234 log.critical("TX = 0. Test interrupted since no packet has been sent.")
235 raise Exception("TX = 0")
236 return(pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max,abs_dropped,(abs_new_tx - abs_old_tx))
238 def new_speed(speed,minspeed,maxspeed,success):
239 # Following calculates the ratio for the new speed to be applied
240 # On the Y axis, we will find the ratio, a number between 0 and 1
241 # On the x axis, we find the % of dropped packets, a number between 0 and 100
242 # 2 lines are drawn and we take the minumun of these lines to calculate the ratio
243 # One line goes through (0,y0) and (p,q)
244 # The second line goes through (p,q) and (100,y100)
249 # ratio = min((q-y0)/p*drop_rate+y0,(q-y100)/(p-100)*drop_rate+q-p*(q-y100)/(p-100))
250 # return (int(speed*ratio*100)+0.5)/100.0
255 newspeed = (maxspeed+minspeed)/2.0
256 return (newspeed,minspeed,maxspeed)
258 def get_pps(speed,size):
259 return (speed * 100.0 / (8*(size+24)))
261 def run_speedtest(gensock,sutsock):
262 maxspeed = speed = STARTSPEED
266 log.info("+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
267 log.info("| Generator is sending UDP (1 flow) packets ("+ '{:>5}'.format(size+4) +" bytes) to SUT. SUT sends packets back |")
268 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
269 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 |")
270 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
271 endpps_sut_tx_str = 'NO_RESULTS'
272 gensock.set_size(gencores,0,size) # This is setting the frame size
273 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)
274 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)
275 # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
276 while (maxspeed-minspeed > ACCURACY):
278 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
280 # Start generating packets at requested speed (in % of a 10Gb/s link)
281 gensock.speed(speed, gencores)
283 # Get statistics now that the generation is stable and initial ARP messages are dealt with.
284 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
285 drop_rate = 100.0*abs_dropped/abs_tx
286 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):
287 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 |')
289 endpps_req_tx = pps_req_tx
291 endpps_sut_tx_str = pps_sut_tx_str
295 endabs_dropped = abs_dropped
296 enddrop_rate = drop_rate
299 abs_drop_rate_prefix = bcolors.ENDC
300 if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
301 abs_drop_rate_prefix = bcolors.FAIL
302 if (drop_rate < DROP_RATE_TRESHOLD):
303 drop_rate_prefix = bcolors.ENDC
305 drop_rate_prefix = bcolors.FAIL
306 if (lat_avg< LAT_AVG_TRESHOLD):
307 lat_avg_prefix = bcolors.ENDC
309 lat_avg_prefix = bcolors.FAIL
310 if (lat_max< LAT_MAX_TRESHOLD):
311 lat_max_prefix = bcolors.ENDC
313 lat_max_prefix = bcolors.FAIL
314 if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
315 speed_prefix = bcolors.ENDC
317 speed_prefix = bcolors.FAIL
318 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 |')
320 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
321 if endpps_sut_tx_str <> 'NO_RESULTS':
322 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
323 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 |')
324 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
326 log.info('| Speed 0 or close to 0')
328 def run_flowtest(gensock,sutsock):
329 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
330 log.info("| UDP, 64 bytes, different number of flows by randomizing SRC & DST UDP port |")
331 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
332 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 |")
333 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
335 # 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.
336 flows={128:['1000000000000XXX','100000000000XXXX'],1024:['10000000000XXXXX','10000000000XXXXX'],8192:['1000000000XXXXXX','100000000XXXXXXX'],65535:['10000000XXXXXXXX','10000000XXXXXXXX'],524280:['1000000XXXXXXXXX','100000XXXXXXXXXX']}
337 # flows={524280:['1000000XXXXXXXXX','100000XXXXXXXXXX']}
338 gensock.set_size(gencores,0,size) # This is setting the frame size
339 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)
340 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)
341 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
342 for flow_number in sorted(flows.iterkeys()):
344 gensock.reset_stats()
346 sutsock.reset_stats()
347 source_port,destination_port = flows[flow_number]
348 gensock.set_random(gencores,0,34,source_port,2)
349 gensock.set_random(gencores,0,36,destination_port,2)
350 endpps_sut_tx_str = 'NO_RESULTS'
351 maxspeed = speed = STARTSPEED
353 while (maxspeed-minspeed > ACCURACY):
355 print(str(flow_number)+' flows: Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
357 # Start generating packets at requested speed (in % of a 10Gb/s link)
358 gensock.speed(speed, gencores)
360 # Get statistics now that the generation is stable and initial ARP messages are dealt with
361 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
362 drop_rate = 100.0*abs_dropped/abs_tx
363 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):
364 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 |')
366 endpps_req_tx = pps_req_tx
368 endpps_sut_tx_str = pps_sut_tx_str
372 endabs_dropped = abs_dropped
373 enddrop_rate = drop_rate
376 abs_drop_rate_prefix = bcolors.ENDC
377 if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
378 abs_drop_rate_prefix = bcolors.FAIL
379 if (drop_rate < DROP_RATE_TRESHOLD):
380 drop_rate_prefix = bcolors.ENDC
382 drop_rate_prefix = bcolors.FAIL
383 if (lat_avg< LAT_AVG_TRESHOLD):
384 lat_avg_prefix = bcolors.ENDC
386 lat_avg_prefix = bcolors.FAIL
387 if (lat_max< LAT_MAX_TRESHOLD):
388 lat_max_prefix = bcolors.ENDC
390 lat_max_prefix = bcolors.FAIL
391 if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
392 speed_prefix = bcolors.ENDC
394 speed_prefix = bcolors.FAIL
395 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 |')
397 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
398 if endpps_sut_tx_str <> 'NO_RESULTS':
399 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)+ '% |')
400 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
401 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})
403 log.info('|{:>7}'.format(str(flow_number))+" | Speed 0 or close to 0")
405 def run_sizetest(gensock,sutsock):
406 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
407 log.info("| UDP, 1 flow, different packet sizes |")
408 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
409 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 |")
410 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
411 # PROX will use different packet sizes as defined in sizes[]
412 sizes=[1496,1020,508,252,124,60]
413 # sizes=[1020,508,252,124,60]
417 gensock.reset_stats()
419 sutsock.reset_stats()
420 gensock.set_size(gencores,0,size) # This is setting the frame size
421 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)
422 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)
423 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
424 endpps_sut_tx_str = 'NO_RESULTS'
425 maxspeed = speed = STARTSPEED
427 while (maxspeed-minspeed > ACCURACY):
429 print(str(size+4)+' bytes: Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
431 # Start generating packets at requested speed (in % of a 10Gb/s link)
432 gensock.speed(speed, gencores)
433 # Get statistics now that the generation is stable and initial ARP messages are dealt with
434 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
435 drop_rate = 100.0*abs_dropped/abs_tx
436 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):
437 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 |')
439 endpps_req_tx = pps_req_tx
441 endpps_sut_tx_str = pps_sut_tx_str
445 endabs_dropped = abs_dropped
446 enddrop_rate = drop_rate
449 abs_drop_rate_prefix = bcolors.ENDC
450 if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
451 abs_drop_rate_prefix = bcolors.FAIL
452 if (drop_rate < DROP_RATE_TRESHOLD):
453 drop_rate_prefix = bcolors.ENDC
455 drop_rate_prefix = bcolors.FAIL
456 if (lat_avg< LAT_AVG_TRESHOLD):
457 lat_avg_prefix = bcolors.ENDC
459 lat_avg_prefix = bcolors.FAIL
460 if (lat_max< LAT_MAX_TRESHOLD):
461 lat_max_prefix = bcolors.ENDC
463 lat_max_prefix = bcolors.FAIL
464 if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
465 speed_prefix = bcolors.ENDC
467 speed_prefix = bcolors.FAIL
468 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 |')
470 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
471 if endpps_sut_tx_str <> 'NO_RESULTS':
472 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)+ '% |')
473 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
474 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})
476 log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
479 def run_max_frame_rate(gensock,sutsock):
480 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------+")
481 log.info("| UDP, 1 flow, different packet sizes |")
482 log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
483 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 |")
484 log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
485 # PROX will use different packet sizes as defined in sizes[]
486 sizes=[1496,1020,508,252,124,60]
489 # 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
490 time.sleep(sleep_time)
491 gensock.reset_stats()
493 sutsock.reset_stats()
494 gensock.set_size(gencores,0,size) # This is setting the frame size
495 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)
496 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)
497 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
498 pps_sut_tx_str = 'NO_RESULTS'
500 # Start generating packets at requested speed (in % of a 10Gb/s link)
501 gensock.speed(speed / len(gencores), gencores)
502 duration = float(runtime)
506 old_sut_rx, old_sut_tx, old_sut_drop, old_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
507 old_rx, old_tx, old_drop, old_tsc, tsc_hz = gensock.core_stats(genstatcores)
508 gensock.start(gencores)
509 while (duration > 0):
510 duration = duration - 1
512 lat_min, lat_max, lat_avg = gensock.lat_stats(latcores)
513 # Get statistics after some execution time
514 new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
516 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
517 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
518 rx = new_rx - old_rx # rx is all packets received by the nop task = all packets received in the gen VM
519 tx = new_tx - old_tx # tx is all generated packets actually accepted by the interface
520 tsc = new_tsc - old_tsc # time difference between the 2 measurements, expressed in cycles.
525 pps_req_tx = (tx+drop-rx)*tsc_hz*1.0/(tsc*1000000)
526 pps_tx = tx*tsc_hz*1.0/(tsc*1000000)
527 pps_rx = rx*tsc_hz*1.0/(tsc*1000000)
529 sut_rx = new_sut_rx - old_sut_rx
530 sut_tx = new_sut_tx - old_sut_tx
531 sut_tsc = new_sut_tsc - old_sut_tsc
532 old_sut_tx = new_sut_tx
533 old_sut_rx = new_sut_rx
534 old_sut_tsc= new_sut_tsc
535 pps_sut_tx = sut_tx*sut_tsc_hz*1.0/(sut_tsc*1000000)
536 pps_sut_tx_str = '{:>7.3f}'.format(pps_sut_tx)
539 pps_sut_tx_str = 'NO MEAS.'
541 log.critical("TX = 0. Test interrupted since no packet has been sent.")
542 raise Exception("TX = 0")
543 tot_drop = tot_drop + tx - rx
545 if pps_sut_tx_str <> 'NO_RESULTS':
546 # First second mpps are not valid as there is no alignement between time the generator is started and per seconds stats
548 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) +' | ')
550 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) +' | ')
552 log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
556 gensock.stop(gencores)
557 time.sleep(sleep_time)
558 lat_min, lat_max, lat_avg = gensock.lat_stats(latcores)
559 # Get statistics after some execution time
560 new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
562 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
563 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
564 rx = new_rx - old_rx # rx is all packets received by the nop task = all packets received in the gen VM
565 tx = new_tx - old_tx # tx is all generated packets actually accepted by the interface
566 tsc = new_tsc - old_tsc # time difference between the 2 measurements, expressed in cycles.
567 tot_drop = tot_drop + tx - rx
569 sut_rx = new_sut_rx - old_sut_rx
570 sut_tx = new_sut_tx - old_sut_tx
571 sut_tsc = new_sut_tsc - old_sut_tsc
572 if pps_sut_tx_str <> 'NO_RESULTS':
573 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) +' | ')
574 log.info("+-----+------------------+-------------+-------------+-------------+-------------+-------------+-------------+-----------+-----------+---------+------------+")
576 def run_irqtest(sock):
577 log.info("+----------------------------------------------------------------------------------------------------------------------------")
578 log.info("| Measuring time probably spent dealing with an interrupt. Interrupting DPDK cores for more than 50us might be problematic ")
579 log.info("| and result in packet loss. The first row shows the interrupted time buckets: first number is the bucket between 0us and ")
580 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 ")
581 log.info("| interrupted for a time as specified by its bucket. '0' is printed when there are no interrupts in this bucket throughout ")
582 log.info("| the duration of the test. This is to avoid rounding errors in the case of 0.0 ")
583 log.info("+----------------------------------------------------------------------------------------------------------------------------")
585 buckets=sock.show_irq_buckets(1)
586 print('Measurement ongoing ... ',end='\r')
588 old_irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
589 irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
590 irq[0][0] = 'bucket us'
591 for j,bucket in enumerate(buckets,start=1):
592 irq[0][j] = '<'+ bucket
593 irq[0][-1] = '>'+ buckets [-2]
596 for j,bucket in enumerate(buckets,start=1):
597 for i,irqcore in enumerate(irqcores,start=1):
598 old_irq[i][j] = sock.irq_stats(irqcore,j-1)
599 time.sleep(float(runtime))
601 for i,irqcore in enumerate(irqcores,start=1):
602 irq[i][0]='core %s '%irqcore
603 for j,bucket in enumerate(buckets,start=1):
604 diff = sock.irq_stats(irqcore,j-1) - old_irq[i][j]
608 irq[i][j] = str(round(diff/float(runtime), 2))
609 log.info('\n'.join([''.join(['{:>12}'.format(item) for item in row]) for row in irq]))
611 def run_impairtest(gensock,sutsock):
612 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
613 log.info("| Generator is sending UDP (1 flow) packets (64 bytes) to SUT via GW dropping and delaying packets. SUT sends packets back. Use ctrl-c to stop the test |")
614 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
615 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 |")
616 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
619 gensock.set_size(gencores,0,size) # This is setting the frame size
620 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)
621 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)
622 # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
624 gensock.speed(speed, gencores)
627 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
630 # Get statistics now that the generation is stable and NO ARP messages any more
631 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
632 drop_rate = 100.0*abs_dropped/abs_tx
633 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)+ '% |')
636 # Running at low speed to make sure the ARP messages can get through.
637 # If not doing this, the ARP message could be dropped by a switch in overload and then the test will not give proper results
638 # Note hoever that if we would run the test steps during a very long time, the ARP would expire in the switch.
639 # PROX will send a new ARP request every seconds so chances are very low that they will all fail to get through
640 socks[0].speed(0.01, gencores)
641 socks[0].start(genstatcores)
643 socks[0].stop(gencores)
650 global DROP_RATE_TRESHOLD
651 global LAT_AVG_TRESHOLD
652 global LAT_MAX_TRESHOLD
655 global required_number_of_test_machines
665 testconfig = ConfigParser.RawConfigParser()
666 testconfig.read(test+'.test')
667 required_number_of_test_machines = testconfig.get('DEFAULT', 'total_number_of_test_machines')
668 DROP_RATE_TRESHOLD = float(testconfig.get('DEFAULT', 'drop_rate_treshold'))
669 LAT_AVG_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_avg_treshold'))
670 LAT_MAX_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_max_treshold'))
671 ACCURACY = float(testconfig.get('DEFAULT', 'accuracy'))
672 STARTSPEED = float(testconfig.get('DEFAULT', 'startspeed'))
673 config = ConfigParser.RawConfigParser()
674 config.read(env+'.env')
675 key = config.get('OpenStack', 'key')
676 total_number_of_machines = config.get('rapid', 'total_number_of_machines')
677 if int(required_number_of_test_machines) > int(total_number_of_machines):
678 log.exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
679 raise Exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
680 for vm in range(1, int(total_number_of_machines)+1):
681 vmAdminIP.append(config.get('M%d'%vm, 'admin_ip'))
682 vmDPmac.append(config.get('M%d'%vm, 'dp_mac'))
683 vmDPIP.append(config.get('M%d'%vm, 'dp_ip'))
684 ip = vmDPIP[-1].split('.')
685 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))
687 for vm in range(1, int(required_number_of_test_machines)+1):
688 machine_index.append(int(testconfig.get('TestM%d'%vm, 'machine_index'))-1)
689 for vm in range(1, int(required_number_of_test_machines)+1):
690 config_file.append(testconfig.get('TestM%d'%vm, 'config_file'))
691 script_control.append(testconfig.get('TestM%d'%vm, 'script_control'))
692 group1cores=testconfig.get('TestM%d'%vm, 'group1cores')
693 if group1cores <> 'not_used':
694 group1cores=ast.literal_eval(group1cores)
695 group2cores=testconfig.get('TestM%d'%vm, 'group2cores')
696 if group2cores <> 'not_used':
697 group2cores=ast.literal_eval(group2cores)
698 group3cores=testconfig.get('TestM%d'%vm, 'group3cores')
699 if group3cores <> 'not_used':
700 group3cores=ast.literal_eval(group3cores)
701 with open("parameters%d.lua"%vm, "w") as f:
702 f.write('name="%s"\n'% testconfig.get('TestM%d'%vm, 'name'))
703 f.write('local_ip="%s"\n'% vmDPIP[machine_index[vm-1]])
704 f.write('local_hex_ip="%s"\n'% hexDPIP[machine_index[vm-1]])
705 gwVM = testconfig.get('TestM%d'%vm, 'gw_vm')
706 if gwVM <> 'not_used':
707 gwVMindex = int(gwVM)-1
708 f.write('gw_ip="%s"\n'% vmDPIP[machine_index[gwVMindex]])
709 f.write('gw_hex_ip="%s"\n'% hexDPIP[machine_index[gwVMindex]])
710 destVM = testconfig.get('TestM%d'%vm, 'dest_vm')
711 if destVM <> 'not_used':
712 destVMindex = int(destVM)-1
713 f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
714 f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
715 f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
716 if group1cores <> 'not_used':
717 f.write('group1="%s"\n'% ','.join(map(str, group1cores)))
718 if group2cores <> 'not_used':
719 f.write('group2="%s"\n'% ','.join(map(str, group2cores)))
720 if group3cores <> 'not_used':
721 f.write('group3="%s"\n'% ','.join(map(str, group3cores)))
722 if re.match('(l2){0,1}gen.*\.cfg',config_file[-1]):
723 gencores = group1cores
724 latcores = group2cores
725 genstatcores = group3cores
726 elif config_file[-1] == 'gen_gw.cfg':
727 gencores = group1cores
728 latcores = group2cores
729 genstatcores = group3cores
730 elif re.match('(l2){0,1}swap.*\.cfg',config_file[-1]):
731 sutstatcores = group1cores
732 elif config_file[-1] == 'secgw2.cfg':
733 sutstatcores = group1cores
734 elif config_file[-1] == 'irq.cfg':
735 irqcores = group1cores
737 #####################################################################################
739 log.debug ('exit cleanup')
742 for client in clients:
746 atexit.register(exit_handler)
748 for vm in range(0, int(required_number_of_test_machines)):
749 clients.append(prox_ctrl(vmAdminIP[machine_index[vm]], key+'.pem','root'))
750 connect_client(clients[-1])
751 # Creating script to bind the right network interface to the poll mode driver
752 devbindfile = "devbindvm%d.sh"%(vm+1)
753 with open("devbind.sh") as f:
754 newText=f.read().replace('MACADDRESS', vmDPmac[machine_index[vm]])
755 with open(devbindfile, "w") as f:
757 st = os.stat(devbindfile)
758 os.chmod(devbindfile, st.st_mode | stat.S_IEXEC)
759 clients[-1].scp_put('./%s'%devbindfile, '/root/devbind.sh')
760 cmd = '/root/devbind.sh'
761 clients[-1].run_cmd(cmd)
762 log.debug("devbind.sh running on VM%d"%(vm+1))
763 clients[-1].scp_put('./%s'%config_file[vm], '/root/%s'%config_file[vm])
764 clients[-1].scp_put('./parameters%d.lua'%(vm+1), '/root/parameters.lua')
765 log.debug("Starting PROX on VM%d"%(vm+1))
766 if script_control[vm] == 'true':
767 cmd = '/root/prox/build/prox -e -t -o cli -f /root/%s'%config_file[vm]
769 cmd = '/root/prox/build/prox -t -o cli -f /root/%s'%config_file[vm]
770 if configonly == False:
771 clients[-1].fork_cmd(cmd, 'PROX Testing on TestM%d'%(vm+1))
772 socks.append(connect_socket(clients[-1]))
775 init_code = testconfig.get('DEFAULT', 'init_code')
776 if init_code <> 'not_used':
778 ####################################################
780 # 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
781 ####################################################
782 number_of_tests = testconfig.get('DEFAULT', 'number_of_tests')
783 data_file = 'RUN' +env+'.'+test+'.csv'
784 data_csv_file = open(data_file,'w')
786 fieldnames = ['flow','size','endspeed','endspeedpps','endpps_req_tx','endpps_tx','endpps_sut_tx_str','endpps_rx','endlat_avg','endlat_max','endabs_dropped','enddrop_rate']
787 writer = csv.DictWriter(data_csv_file, fieldnames=fieldnames)
789 for vm in range(1, int(number_of_tests)+1):
790 cmd=testconfig.get('test%d'%vm,'cmd')
792 ####################################################