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 log.info("+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
263 log.info("| Generator is sending UDP (1 flow) packets (64 bytes) to SUT. SUT sends packets back |")
264 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
265 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 |")
266 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
267 maxspeed = speed = STARTSPEED
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 NO ARP messages any more
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 | '+ bcolors.ENDC + '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>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()):
343 gensock.reset_stats()
345 sutsock.reset_stats()
346 source_port,destination_port = flows[flow_number]
347 gensock.set_random(gencores,0,34,source_port,2)
348 gensock.set_random(gencores,0,36,destination_port,2)
349 endpps_sut_tx_str = 'NO_RESULTS'
350 maxspeed = speed = STARTSPEED
352 while (maxspeed-minspeed > ACCURACY):
353 print(str(flow_number)+' flows: Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
355 # Start generating packets at requested speed (in % of a 10Gb/s link)
356 gensock.speed(speed, gencores)
358 # Get statistics now that the generation is stable and NO ARP messages any more
359 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
360 drop_rate = 100.0*abs_dropped/abs_tx
361 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):
363 endpps_req_tx = pps_req_tx
365 endpps_sut_tx_str = pps_sut_tx_str
369 endabs_dropped = abs_dropped
370 enddrop_rate = drop_rate
374 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
375 if endpps_sut_tx_str <> 'NO_RESULTS':
376 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)+ '% |')
377 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
378 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})
380 log.info('|{:>7}'.format(str(flow_number))+" | Speed 0 or close to 0")
382 def run_sizetest(gensock,sutsock):
383 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
384 log.info("| UDP, 1 flow, different packet sizes |")
385 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
386 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 |")
387 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
388 # PROX will use different packet sizes as defined in sizes[]
389 sizes=[1496,1020,508,252,124,60]
390 # sizes=[1020,508,252,124,60]
393 gensock.reset_stats()
395 sutsock.reset_stats()
396 gensock.set_size(gencores,0,size) # This is setting the frame size
397 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)
398 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)
399 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
400 endpps_sut_tx_str = 'NO_RESULTS'
401 maxspeed = speed = STARTSPEED
403 while (maxspeed-minspeed > ACCURACY):
404 print(str(size+4)+' bytes: Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
406 # Start generating packets at requested speed (in % of a 10Gb/s link)
407 gensock.speed(speed, gencores)
408 # Get statistics now that the generation is stable and NO ARP messages any more
409 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
410 drop_rate = 100.0*abs_dropped/abs_tx
411 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):
413 endpps_req_tx = pps_req_tx
415 endpps_sut_tx_str = pps_sut_tx_str
419 endabs_dropped = abs_dropped
420 enddrop_rate = drop_rate
424 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
425 if endpps_sut_tx_str <> 'NO_RESULTS':
426 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)+ '% |')
427 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
428 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})
430 log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
433 def run_irqtest(sock):
434 log.info("+----------------------------------------------------------------------------------------------------------------------------")
435 log.info("| Measuring time probably spent dealing with an interrupt. Interrupting DPDK cores for more than 50us might be problematic ")
436 log.info("| and result in packet loss. The first row shows the interrupted time buckets: first number is the bucket between 0us and ")
437 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 ")
438 log.info("| interrupted for a time as specified by its bucket. '0' is printed when there are no interrupts in this bucket throughout ")
439 log.info("| the duration of the test. This is to avoid rounding errors in the case of 0.0 ")
440 log.info("+----------------------------------------------------------------------------------------------------------------------------")
442 buckets=sock.show_irq_buckets(1)
443 print('Measurement ongoing ... ',end='\r')
445 old_irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
446 irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
447 irq[0][0] = 'bucket us'
448 for j,bucket in enumerate(buckets,start=1):
449 irq[0][j] = '<'+ bucket
450 irq[0][-1] = '>'+ buckets [-2]
453 for j,bucket in enumerate(buckets,start=1):
454 for i,irqcore in enumerate(irqcores,start=1):
455 old_irq[i][j] = sock.irq_stats(irqcore,j-1)
456 time.sleep(float(runtime))
458 for i,irqcore in enumerate(irqcores,start=1):
459 irq[i][0]='core %s '%irqcore
460 for j,bucket in enumerate(buckets,start=1):
461 diff = sock.irq_stats(irqcore,j-1) - old_irq[i][j]
465 irq[i][j] = str(round(diff/float(runtime), 2))
466 log.info('\n'.join([''.join(['{:>12}'.format(item) for item in row]) for row in irq]))
468 def run_impairtest(gensock,sutsock,speed):
469 log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
470 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 |")
471 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
472 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 |")
473 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
476 gensock.set_size(gencores,0,size) # This is setting the frame size
477 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)
478 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)
479 # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
480 gensock.speed(speed, gencores)
483 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '% ',end='\r')
486 # Get statistics now that the generation is stable and NO ARP messages any more
487 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
488 drop_rate = 100.0*abs_dropped/abs_tx
489 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)+ '% |')
492 # Running at low speed to make sure the ARP messages can get through.
493 # If not doing this, the ARP message could be dropped by a switch in overload and then the test will not give proper results
494 # Note hoever that if we would run the test steps during a very long time, the ARP would expire in the switch.
495 # PROX will send a new ARP request every seconds so chances are very low that they will all fail to get through
496 socks[0].speed(0.01, gencores)
497 socks[0].start(genstatcores)
499 socks[0].stop(gencores)
506 global DROP_RATE_TRESHOLD
507 global LAT_AVG_TRESHOLD
508 global LAT_MAX_TRESHOLD
511 global required_number_of_test_machines
521 testconfig = ConfigParser.RawConfigParser()
522 testconfig.read(test+'.test')
523 required_number_of_test_machines = testconfig.get('DEFAULT', 'total_number_of_test_machines')
524 DROP_RATE_TRESHOLD = float(testconfig.get('DEFAULT', 'drop_rate_treshold'))
525 LAT_AVG_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_avg_treshold'))
526 LAT_MAX_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_max_treshold'))
527 ACCURACY = float(testconfig.get('DEFAULT', 'accuracy'))
528 STARTSPEED = float(testconfig.get('DEFAULT', 'startspeed'))
529 config = ConfigParser.RawConfigParser()
530 config.read(env+'.env')
531 key = config.get('OpenStack', 'key')
532 total_number_of_machines = config.get('rapid', 'total_number_of_machines')
533 if int(required_number_of_test_machines) > int(total_number_of_machines):
534 log.exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
535 raise Exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
536 for vm in range(1, int(total_number_of_machines)+1):
537 vmAdminIP.append(config.get('M%d'%vm, 'admin_ip'))
538 vmDPmac.append(config.get('M%d'%vm, 'dp_mac'))
539 vmDPIP.append(config.get('M%d'%vm, 'dp_ip'))
540 ip = vmDPIP[-1].split('.')
541 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))
543 for vm in range(1, int(required_number_of_test_machines)+1):
544 machine_index.append(int(testconfig.get('TestM%d'%vm, 'machine_index'))-1)
545 for vm in range(1, int(required_number_of_test_machines)+1):
546 config_file.append(testconfig.get('TestM%d'%vm, 'config_file'))
547 script_control.append(testconfig.get('TestM%d'%vm, 'script_control'))
548 group1cores=testconfig.get('TestM%d'%vm, 'group1cores')
549 if group1cores <> 'not_used':
550 group1cores=ast.literal_eval(group1cores)
551 group2cores=testconfig.get('TestM%d'%vm, 'group2cores')
552 if group2cores <> 'not_used':
553 group2cores=ast.literal_eval(group2cores)
554 group3cores=testconfig.get('TestM%d'%vm, 'group3cores')
555 if group3cores <> 'not_used':
556 group3cores=ast.literal_eval(group3cores)
557 with open("parameters%d.lua"%vm, "w") as f:
558 f.write('name="%s"\n'% testconfig.get('TestM%d'%vm, 'name'))
559 f.write('local_ip="%s"\n'% vmDPIP[machine_index[vm-1]])
560 f.write('local_hex_ip="%s"\n'% hexDPIP[machine_index[vm-1]])
561 gwVM = testconfig.get('TestM%d'%vm, 'gw_vm')
562 if gwVM <> 'not_used':
563 gwVMindex = int(gwVM)-1
564 f.write('gw_ip="%s"\n'% vmDPIP[machine_index[gwVMindex]])
565 f.write('gw_hex_ip="%s"\n'% hexDPIP[machine_index[gwVMindex]])
566 destVM = testconfig.get('TestM%d'%vm, 'dest_vm')
567 if destVM <> 'not_used':
568 destVMindex = int(destVM)-1
569 f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
570 f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
571 f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
572 if group1cores <> 'not_used':
573 f.write('group1="%s"\n'% ','.join(map(str, group1cores)))
574 if group2cores <> 'not_used':
575 f.write('group2="%s"\n'% ','.join(map(str, group2cores)))
576 if group3cores <> 'not_used':
577 f.write('group3="%s"\n'% ','.join(map(str, group3cores)))
578 if re.match('(l2){0,1}gen.*\.cfg',config_file[-1]):
579 gencores = group1cores
580 latcores = group2cores
581 genstatcores = group3cores
582 elif config_file[-1] == 'gen_gw.cfg':
583 gencores = group1cores
584 latcores = group2cores
585 genstatcores = group3cores
586 elif re.match('(l2){0,1}swap.*\.cfg',config_file[-1]):
587 sutstatcores = group1cores
588 elif config_file[-1] == 'secgw2.cfg':
589 sutstatcores = group1cores
590 elif config_file[-1] == 'irq.cfg':
591 irqcores = group1cores
593 #####################################################################################
595 log.debug ('exit cleanup')
598 for client in clients:
602 atexit.register(exit_handler)
604 for vm in range(0, int(required_number_of_test_machines)):
605 clients.append(prox_ctrl(vmAdminIP[machine_index[vm]], key+'.pem','root'))
606 connect_client(clients[-1])
607 # Creating script to bind the right network interface to the poll mode driver
608 devbindfile = "devbindvm%d.sh"%(vm+1)
609 with open("devbind.sh") as f:
610 newText=f.read().replace('MACADDRESS', vmDPmac[machine_index[vm]])
611 with open(devbindfile, "w") as f:
613 st = os.stat(devbindfile)
614 os.chmod(devbindfile, st.st_mode | stat.S_IEXEC)
615 clients[-1].scp_put('./%s'%devbindfile, '/root/devbind.sh')
616 cmd = '/root/devbind.sh'
617 clients[-1].run_cmd(cmd)
618 log.debug("devbind.sh running on VM%d"%(vm+1))
619 clients[-1].scp_put('./%s'%config_file[vm], '/root/%s'%config_file[vm])
620 clients[-1].scp_put('./parameters%d.lua'%(vm+1), '/root/parameters.lua')
621 log.debug("Starting PROX on VM%d"%(vm+1))
622 if script_control[vm] == 'true':
623 cmd = '/root/prox/build/prox -e -t -o cli -f /root/%s'%config_file[vm]
625 cmd = '/root/prox/build/prox -t -o cli -f /root/%s'%config_file[vm]
626 if configonly == False:
627 clients[-1].fork_cmd(cmd, 'PROX Testing on TestM%d'%(vm+1))
628 socks.append(connect_socket(clients[-1]))
631 init_code = testconfig.get('DEFAULT', 'init_code')
632 if init_code <> 'not_used':
634 ####################################################
636 # 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
637 ####################################################
638 number_of_tests = testconfig.get('DEFAULT', 'number_of_tests')
639 data_file = 'RUN' +env+'.'+test+'.csv'
640 data_csv_file = open(data_file,'w')
642 fieldnames = ['flow','size','endspeed','endspeedpps','endpps_req_tx','endpps_tx','endpps_sut_tx_str','endpps_rx','endlat_avg','endlat_max','endabs_dropped','enddrop_rate']
643 writer = csv.DictWriter(data_csv_file, fieldnames=fieldnames)
645 for vm in range(1, int(number_of_tests)+1):
646 cmd=testconfig.get('test%d'%vm,'cmd')
648 ####################################################