1 # Copyright (c) 2018 Intel Corporation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
25 from collections import OrderedDict, namedtuple
26 from contextlib import contextmanager
27 from itertools import repeat, chain
28 from multiprocessing import Queue
31 from six.moves import cStringIO
32 from six.moves import zip, StringIO
34 from yardstick.common import utils
35 from yardstick.common.utils import SocketTopology, join_non_strings, try_int
36 from yardstick.network_services.helpers.iniparser import ConfigParser
37 from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper
38 from yardstick.network_services.vnf_generic.vnf.sample_vnf import DpdkVnfSetupEnvHelper
39 from yardstick.network_services import constants
46 LOG = logging.getLogger(__name__)
47 LOG.setLevel(logging.DEBUG)
48 LOG_RESULT = logging.getLogger('yardstick')
49 LOG_RESULT.setLevel(logging.DEBUG)
55 CONFIGURATION_OPTIONS = (
56 # dict key section key default value
57 ('pktSizes', 'general', 'pkt_sizes', '64,128,256,512,1024,1280,1518'),
58 ('testDuration', 'general', 'test_duration', 5.0),
59 ('testPrecision', 'general', 'test_precision', 1.0),
60 ('tests', 'general', 'tests', None),
61 ('toleratedLoss', 'general', 'tolerated_loss', 0.0),
63 ('logFile', 'logging', 'file', 'dats.log'),
64 ('logDateFormat', 'logging', 'datefmt', None),
65 ('logLevel', 'logging', 'level', 'INFO'),
66 ('logOverwrite', 'logging', 'overwrite', 1),
68 ('testerIp', 'tester', 'ip', None),
69 ('testerUser', 'tester', 'user', 'root'),
70 ('testerDpdkDir', 'tester', 'rte_sdk', '/root/dpdk'),
71 ('testerDpdkTgt', 'tester', 'rte_target', 'x86_64-native-linuxapp-gcc'),
72 ('testerProxDir', 'tester', 'prox_dir', '/root/prox'),
73 ('testerSocketId', 'tester', 'socket_id', 0),
75 ('sutIp', 'sut', 'ip', None),
76 ('sutUser', 'sut', 'user', 'root'),
77 ('sutDpdkDir', 'sut', 'rte_sdk', '/root/dpdk'),
78 ('sutDpdkTgt', 'sut', 'rte_target', 'x86_64-native-linuxapp-gcc'),
79 ('sutProxDir', 'sut', 'prox_dir', '/root/prox'),
80 ('sutSocketId', 'sut', 'socket_id', 0),
84 class CoreSocketTuple(namedtuple('CoreTuple', 'core_id, socket_id, hyperthread')):
85 CORE_RE = re.compile(r"core\s+(\d+)(?:s(\d+))?(h)?$")
87 def __new__(cls, *args):
89 matches = cls.CORE_RE.search(str(args[0]))
91 args = matches.groups()
93 return super(CoreSocketTuple, cls).__new__(cls, int(args[0]), try_int(args[1], 0),
94 'h' if args[2] else '')
96 except (AttributeError, TypeError, IndexError, ValueError):
97 raise ValueError('Invalid core spec {}'.format(args))
99 def is_hyperthread(self):
100 return self.hyperthread == 'h'
104 return int(self.is_hyperthread())
106 def find_in_topology(self, cpu_topology):
108 socket_core_match = cpu_topology[self.socket_id][self.core_id]
109 sorted_match = sorted(socket_core_match.values())
110 return sorted_match[self.index][0]
111 except (KeyError, IndexError):
112 template = "Core {}{} on socket {} does not exist"
113 raise ValueError(template.format(self.core_id, self.hyperthread, self.socket_id))
116 class TotStatsTuple(namedtuple('TotStats', 'rx,tx,tsc,hz')):
117 def __new__(cls, *args):
119 assert args[0] is not str(args[0])
120 args = tuple(args[0])
121 except (AssertionError, IndexError, TypeError):
124 return super(TotStatsTuple, cls).__new__(cls, *args)
127 class ProxTestDataTuple(namedtuple('ProxTestDataTuple', 'tolerated,tsc_hz,delta_rx,'
128 'delta_tx,delta_tsc,'
129 'latency,rx_total,tx_total,'
134 return 1e2 * self.drop_total / float(self.tx_total)
135 except ZeroDivisionError:
140 # calculate the effective throughput in Mpps
141 return float(self.delta_tx) * self.tsc_hz / self.delta_tsc / 1e6
145 # calculate the effective throughput in Mpps
146 return float(self.delta_rx) * self.tsc_hz / self.delta_tsc / 1e6
149 def can_be_lost(self):
150 return int(self.tx_total * self.tolerated / 1e2)
153 def drop_total(self):
154 return self.tx_total - self.rx_total
158 return self.drop_total <= self.can_be_lost
160 def get_samples(self, pkt_size, pkt_loss=None, port_samples=None):
162 pkt_loss = self.pkt_loss
164 if port_samples is None:
174 "Throughput": self.rx_mpps,
175 "RxThroughput": self.rx_mpps,
176 "DropPackets": pkt_loss,
177 "CurrentDropPackets": pkt_loss,
178 "RequestedTxThroughput": self.requested_pps / 1e6,
179 "TxThroughput": self.tx_mpps,
183 samples.update(port_samples)
185 samples.update((key, value) for key, value in zip(latency_keys, self.latency))
188 def log_data(self, logger=None):
192 template = "RX: %d; TX: %d; dropped: %d (tolerated: %d)"
193 logger.info(template, self.rx_total, self.tx_total, self.drop_total, self.can_be_lost)
194 logger.info("Mpps configured: %f; Mpps generated %f; Mpps received %f",
195 self.requested_pps / 1e6, self.tx_mpps, self.rx_mpps)
198 class PacketDump(object):
200 def assert_func(func, value1, value2, template=None):
201 assert func(value1, value2), template.format(value1, value2)
203 def __init__(self, port_id, data_len, payload):
204 template = "Packet dump has specified length {}, but payload is {} bytes long"
205 self.assert_func(operator.eq, data_len, len(payload), template)
206 self._port_id = port_id
207 self._data_len = data_len
208 self._payload = payload
212 """Get the port id of the packet dump"""
217 """Get the length of the data received"""
218 return self._data_len
221 return '<PacketDump port: {} payload: {}>'.format(self._port_id, self._payload)
223 def payload(self, start=None, end=None):
224 """Get part of the payload as a list of ordinals.
226 Returns a list of byte values, matching the contents of the packet dump.
227 Optional start and end parameters can be specified to retrieve only a
228 part of the packet contents.
230 The number of elements in the list is equal to end - start + 1, so end
231 is the offset of the last character.
234 start (pos. int): the starting offset in the payload. If it is not
235 specified or None, offset 0 is assumed.
236 end (pos. int): the ending offset of the payload. If it is not
237 specified or None, the contents until the end of the packet are
241 [int, int, ...]. Each int represents the ordinal value of a byte in
248 end = self.data_len - 1
250 # Bounds checking on offsets
251 template = "Start offset must be non-negative"
252 self.assert_func(operator.ge, start, 0, template)
254 template = "End offset must be less than {1}"
255 self.assert_func(operator.lt, end, self.data_len, template)
257 # Adjust for splice operation: end offset must be 1 more than the offset
258 # of the last desired character.
261 return self._payload[start:end]
264 class ProxSocketHelper(object):
266 def __init__(self, sock=None):
267 """ creates new prox instance """
268 super(ProxSocketHelper, self).__init__()
271 sock = socket.socket()
275 self.master_stats = None
277 def connect(self, ip, port):
278 """Connect to the prox instance on the remote system"""
279 self._sock.connect((ip, port))
281 def get_socket(self):
282 """ get the socket connected to the remote instance """
285 def _parse_socket_data(self, decoded_data, pkt_dump_only):
286 def get_newline_index():
287 return decoded_data.find('\n', index)
291 for newline_index in iter(get_newline_index, -1):
292 ret_str = decoded_data[index:newline_index]
295 mode, port_id, data_len = ret_str.split(',', 2)
297 mode, port_id, data_len = None, None, None
299 if mode != 'pktdump':
300 # Regular 1-line message. Stop reading from the socket.
301 LOG.debug("Regular response read")
304 LOG.debug("Packet dump header read: [%s]", ret_str)
306 # The line is a packet dump header. Parse it, read the
307 # packet payload, store the dump for later retrieval.
308 # Skip over the packet dump and continue processing: a
309 # 1-line response may follow the packet dump.
311 data_len = int(data_len)
312 data_start = newline_index + 1 # + 1 to skip over \n
313 data_end = data_start + data_len
314 sub_data = decoded_data[data_start:data_end]
315 pkt_payload = array.array('B', (ord(v) for v in sub_data))
316 pkt_dump = PacketDump(int(port_id), data_len, pkt_payload)
317 self._pkt_dumps.append(pkt_dump)
320 # Return boolean instead of string to signal
321 # successful reception of the packet dump.
322 LOG.debug("Packet dump stored, returning")
327 return ret_str, False
329 def get_string(self, pkt_dump_only=False, timeout=0.01):
331 def is_ready_string():
332 # recv() is blocking, so avoid calling it when no data is waiting.
333 ready = select.select([self._sock], [], [], timeout)
334 return bool(ready[0])
338 while status is False:
339 for status in iter(is_ready_string, False):
340 decoded_data = self._sock.recv(256).decode('utf-8')
341 ret_str, done = self._parse_socket_data(decoded_data,
347 LOG.debug("Received data from socket: [%s]", ret_str)
348 return status, ret_str
350 def get_data(self, pkt_dump_only=False, timeout=0.01):
351 """ read data from the socket """
353 # This method behaves slightly differently depending on whether it is
354 # called to read the response to a command (pkt_dump_only = 0) or if
355 # it is called specifically to read a packet dump (pkt_dump_only = 1).
357 # Packet dumps look like:
358 # pktdump,<port_id>,<data_len>\n
359 # <packet contents as byte array>\n
360 # This means the total packet dump message consists of 2 lines instead
363 # - Response for a command (pkt_dump_only = 0):
364 # 1) Read response from the socket until \n (end of message)
365 # 2a) If the response is a packet dump header (starts with "pktdump,"):
366 # - Read the packet payload and store the packet dump for later
368 # - Reset the state and restart from 1). Eventually state 2b) will
369 # be reached and the function will return.
370 # 2b) If the response is not a packet dump:
371 # - Return the received message as a string
373 # - Explicit request to read a packet dump (pkt_dump_only = 1):
374 # - Read the dump header and payload
375 # - Store the packet dump for later retrieval
376 # - Return True to signify a packet dump was successfully read
379 # recv() is blocking, so avoid calling it when no data is waiting.
380 ready = select.select([self._sock], [], [], timeout)
381 return bool(ready[0])
385 for status in iter(is_ready, False):
386 decoded_data = self._sock.recv(256).decode('utf-8')
387 ret_str, done = self._parse_socket_data(decoded_data, pkt_dump_only)
391 LOG.debug("Received data from socket: [%s]", ret_str)
392 return ret_str if status else ''
394 def put_command(self, to_send):
395 """ send data to the remote instance """
396 LOG.debug("Sending data to socket: [%s]", to_send.rstrip('\n'))
398 # NOTE: sendall will block, we need a timeout
399 self._sock.sendall(to_send.encode('utf-8'))
400 except: # pylint: disable=bare-except
403 def get_packet_dump(self):
404 """ get the next packet dump """
406 return self._pkt_dumps.pop(0)
409 def stop_all_reset(self):
410 """ stop the remote instance and reset stats """
411 LOG.debug("Stop all and reset stats")
416 """ stop all cores on the remote instance """
417 LOG.debug("Stop all")
418 self.put_command("stop all\n")
420 def stop(self, cores, task=''):
421 """ stop specific cores on the remote instance """
425 if core not in tmpcores:
426 tmpcores.append(core)
428 LOG.debug("Stopping cores %s", tmpcores)
429 self.put_command("stop {} {}\n".format(join_non_strings(',', tmpcores), task))
432 """ start all cores on the remote instance """
433 LOG.debug("Start all")
434 self.put_command("start all\n")
436 def start(self, cores):
437 """ start specific cores on the remote instance """
441 if core not in tmpcores:
442 tmpcores.append(core)
444 LOG.debug("Starting cores %s", tmpcores)
445 self.put_command("start {}\n".format(join_non_strings(',', tmpcores)))
447 def reset_stats(self):
448 """ reset the statistics on the remote instance """
449 LOG.debug("Reset stats")
450 self.put_command("reset stats\n")
452 def _run_template_over_cores(self, template, cores, *args):
454 self.put_command(template.format(core, *args))
456 def set_pkt_size(self, cores, pkt_size):
457 """ set the packet size to generate on the remote instance """
458 LOG.debug("Set packet size for core(s) %s to %d", cores, pkt_size)
460 self._run_template_over_cores("pkt_size {} 0 {}\n", cores, pkt_size)
462 def set_value(self, cores, offset, value, length):
463 """ set value on the remote instance """
464 msg = "Set value for core(s) %s to '%s' (length %d), offset %d"
465 LOG.debug(msg, cores, value, length, offset)
466 template = "set value {} 0 {} {} {}\n"
467 self._run_template_over_cores(template, cores, offset, value, length)
469 def reset_values(self, cores):
470 """ reset values on the remote instance """
471 LOG.debug("Set value for core(s) %s", cores)
472 self._run_template_over_cores("reset values {} 0\n", cores)
474 def set_speed(self, cores, speed, tasks=None):
475 """ set speed on the remote instance """
477 tasks = [0] * len(cores)
478 elif len(tasks) != len(cores):
479 LOG.error("set_speed: cores and tasks must have the same len")
480 LOG.debug("Set speed for core(s)/tasks(s) %s to %g", list(zip(cores, tasks)), speed)
481 for (core, task) in list(zip(cores, tasks)):
482 self.put_command("speed {} {} {}\n".format(core, task, speed))
484 def slope_speed(self, cores_speed, duration, n_steps=0):
485 """will start to increase speed from 0 to N where N is taken from
486 a['speed'] for each a in cores_speed"""
487 # by default, each step will take 0.5 sec
489 n_steps = duration * 2
491 private_core_data = []
492 step_duration = float(duration) / n_steps
493 for core_data in cores_speed:
494 target = float(core_data['speed'])
495 private_core_data.append({
496 'cores': core_data['cores'],
498 'delta': target / n_steps,
503 deltas_keys_iter = repeat(('current', 'delta'), n_steps - 1)
504 for key1, key2 in chain(deltas_keys_iter, [('zero', 'speed')]):
505 time.sleep(step_duration)
506 for core_data in private_core_data:
507 core_data['current'] = core_data[key1] + core_data[key2]
508 self.set_speed(core_data['cores'], core_data['current'])
510 def set_pps(self, cores, pps, pkt_size,
511 line_speed=(constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT)):
512 """ set packets per second for specific cores on the remote instance """
513 msg = "Set packets per sec for core(s) %s to %g%% of line rate (packet size: %d)"
514 LOG.debug(msg, cores, pps, pkt_size)
516 # speed in percent of line-rate
517 speed = float(pps) * (pkt_size + 20) / line_speed / BITS_PER_BYTE
518 self._run_template_over_cores("speed {} 0 {}\n", cores, speed)
520 def lat_stats(self, cores, task=0):
521 """Get the latency statistics from the remote system"""
522 # 1-based index, if max core is 4, then 0, 1, 2, 3, 4 len = 5
527 self.put_command("lat stats {} {} \n".format(core, task))
528 ret = self.get_data()
531 lat_min[core], lat_max[core], lat_avg[core] = \
532 tuple(int(n) for n in ret.split(",")[:3])
534 except (AttributeError, ValueError, TypeError):
537 return lat_min, lat_max, lat_avg
539 def get_all_tot_stats(self):
540 self.put_command("tot stats\n")
541 all_stats_str = self.get_data().split(",")
542 if len(all_stats_str) != 4:
545 all_stats = TotStatsTuple(int(v) for v in all_stats_str)
546 self.master_stats = all_stats
550 return self.get_all_tot_stats()[3]
552 def core_stats(self, cores, task=0):
553 """Get the receive statistics from the remote system"""
554 rx = tx = drop = tsc = 0
556 self.put_command("core stats {} {}\n".format(core, task))
557 ret = self.get_data().split(",")
562 return rx, tx, drop, tsc
564 def irq_core_stats(self, cores_tasks):
565 """ get IRQ stats per core"""
570 for core, task in cores_tasks:
571 self.put_command("stats task.core({}).task({}).max_irq,task.core({}).task({}).irq(0),"
572 "task.core({}).task({}).irq(1),task.core({}).task({}).irq(2),"
573 "task.core({}).task({}).irq(3),task.core({}).task({}).irq(4),"
574 "task.core({}).task({}).irq(5),task.core({}).task({}).irq(6),"
575 "task.core({}).task({}).irq(7),task.core({}).task({}).irq(8),"
576 "task.core({}).task({}).irq(9),task.core({}).task({}).irq(10),"
577 "task.core({}).task({}).irq(11),task.core({}).task({}).irq(12)"
578 "\n".format(core, task, core, task, core, task, core, task,
579 core, task, core, task, core, task, core, task,
580 core, task, core, task, core, task, core, task,
581 core, task, core, task))
582 in_data_str = self.get_data().split(",")
583 ret = [try_int(s, 0) for s in in_data_str]
584 key = "core_" + str(core)
586 stat[key] = {"cpu": core, "max_irq": ret[0], "bucket_0" : ret[1],
587 "bucket_1" : ret[2], "bucket_2" : ret[3],
588 "bucket_3" : ret[4], "bucket_4" : ret[5],
589 "bucket_5" : ret[6], "bucket_6" : ret[7],
590 "bucket_7" : ret[8], "bucket_8" : ret[9],
591 "bucket_9" : ret[10], "bucket_10" : ret[11],
592 "bucket_11" : ret[12], "bucket_12" : ret[13],
593 "overflow": ret[10] + ret[11] + ret[12] + ret[13]}
594 except (KeyError, IndexError):
595 LOG.error("Corrupted PACKET %s", in_data_str)
599 def multi_port_stats(self, ports):
600 """get counter values from all ports at once"""
602 ports_str = ",".join(map(str, ports))
604 tot_result = [0] * len(ports)
607 while (len(ports) is not len(ports_all_data)):
608 self.put_command("multi port stats {}\n".format(ports_str))
609 status, ports_all_data_str = self.get_string()
614 ports_all_data = ports_all_data_str.split(";")
616 if len(ports) is len(ports_all_data):
617 for port_data_str in ports_all_data:
621 tmpdata = [try_int(s, 0) for s in port_data_str.split(",")]
622 except (IndexError, TypeError):
623 LOG.error("Unpacking data error %s", port_data_str)
626 if (len(tmpdata) < 6) or tmpdata[0] not in ports:
627 LOG.error("Corrupted PACKET %s - retrying", port_data_str)
630 tot_result[port_index] = tmpdata
631 port_index = port_index + 1
633 LOG.error("Empty / too much data - retry -%s-", ports_all_data)
636 LOG.debug("Multi port packet ..OK.. %s", tot_result)
637 return True, tot_result
639 def port_stats(self, ports):
640 """get counter values from a specific port"""
641 tot_result = [0] * 12
643 self.put_command("port_stats {}\n".format(port))
644 ret = [try_int(s, 0) for s in self.get_data().split(",")]
645 tot_result = [sum(x) for x in zip(tot_result, ret)]
649 def measure_tot_stats(self):
650 start = self.get_all_tot_stats()
651 container = {'start_tot': start}
655 container['end_tot'] = end = self.get_all_tot_stats()
657 container['delta'] = TotStatsTuple(e - s for s, e in zip(start, end))
660 """Get the total statistics from the remote system"""
661 stats = self.get_all_tot_stats()
664 def tot_ierrors(self):
665 """Get the total ierrors from the remote system"""
666 self.put_command("tot ierrors tot\n")
667 recv = self.get_data().split(',')
668 tot_ierrors = int(recv[0])
670 return tot_ierrors, tsc
672 def set_count(self, count, cores):
673 """Set the number of packets to send on the specified core"""
674 self._run_template_over_cores("count {} 0 {}\n", cores, count)
676 def dump_rx(self, core_id, task_id=0, count=1):
677 """Activate dump on rx on the specified core"""
678 LOG.debug("Activating dump on RX for core %d, task %d, count %d", core_id, task_id, count)
679 self.put_command("dump_rx {} {} {}\n".format(core_id, task_id, count))
680 time.sleep(1.5) # Give PROX time to set up packet dumping
688 """ stop all cores on the remote instance """
689 LOG.debug("Quit prox")
690 self.put_command("quit\n")
693 def force_quit(self):
694 """ stop all cores on the remote instance """
695 LOG.debug("Force Quit prox")
696 self.put_command("quit_force\n")
699 _LOCAL_OBJECT = object()
702 class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
703 # the actual app is lowercase
705 # not used for Prox but added for consistency
708 LUA_PARAMETER_NAME = ""
709 LUA_PARAMETER_PEER = {
714 CONFIG_QUEUE_TIMEOUT = 120
716 def __init__(self, vnfd_helper, ssh_helper, scenario_helper):
717 self.remote_path = None
718 super(ProxDpdkVnfSetupEnvHelper, self).__init__(vnfd_helper, ssh_helper, scenario_helper)
719 self.remote_prox_file_name = None
720 self._prox_config_data = None
721 self.additional_files = {}
722 self.config_queue = Queue()
723 # allow_exit_without_flush
724 self.config_queue.cancel_join_thread()
725 self._global_section = None
728 def prox_config_data(self):
729 if self._prox_config_data is None:
730 # this will block, but it needs too
731 self._prox_config_data = self.config_queue.get(True, self.CONFIG_QUEUE_TIMEOUT)
732 return self._prox_config_data
735 def global_section(self):
736 if self._global_section is None and self.prox_config_data:
737 self._global_section = self.find_section("global")
738 return self._global_section
740 def find_section(self, name, default=_LOCAL_OBJECT):
741 result = next((value for key, value in self.prox_config_data if key == name), default)
742 if result is _LOCAL_OBJECT:
743 raise KeyError('{} not found in Prox config'.format(name))
746 def find_in_section(self, section_name, section_key, default=_LOCAL_OBJECT):
747 section = self.find_section(section_name, [])
748 result = next((value for key, value in section if key == section_key), default)
749 if result is _LOCAL_OBJECT:
750 template = '{} not found in {} section of Prox config'
751 raise KeyError(template.format(section_key, section_name))
754 def copy_to_target(self, config_file_path, prox_file):
755 remote_path = os.path.join("/tmp", prox_file)
756 self.ssh_helper.put(config_file_path, remote_path)
760 def _get_tx_port(section, sections):
762 for item in sections[section]:
763 if item[0] == "tx port":
764 iface_port = re.findall(r'\d+', item[1])
765 # do we want the last one?
766 # if yes, then can we reverse?
767 return int(iface_port[0])
770 def _replace_quoted_with_value(quoted, value, count=1):
771 new_string = re.sub('"[^"]*"', '"{}"'.format(value), quoted, count)
774 def _insert_additional_file(self, value):
775 file_str = value.split('"')
776 base_name = os.path.basename(file_str[1])
777 file_str[1] = self.additional_files[base_name]
778 return '"'.join(file_str)
780 def generate_prox_config_file(self, config_path):
782 prox_config = ConfigParser(config_path, sections)
785 # Ensure MAC is set "hardware"
786 all_ports = self.vnfd_helper.port_pairs.all_ports
787 # use dpdk port number
788 for port_name in all_ports:
789 port_num = self.vnfd_helper.port_num(port_name)
790 port_section_name = "port {}".format(port_num)
791 for section_name, section in sections:
792 if port_section_name != section_name:
795 for section_data in section:
796 if section_data[0] == "mac":
797 section_data[1] = "hardware"
800 for _, section in sections:
801 for section_data in section:
802 item_key, item_val = section_data
803 if item_val.startswith("@@dst_mac"):
804 tx_port_iter = re.finditer(r'\d+', item_val)
805 tx_port_no = int(next(tx_port_iter).group(0))
806 intf = self.vnfd_helper.find_interface_by_port(tx_port_no)
807 mac = intf["virtual-interface"]["dst_mac"]
808 section_data[1] = mac.replace(":", " ", 6)
810 if item_key == "dst mac" and item_val.startswith("@@"):
811 tx_port_iter = re.finditer(r'\d+', item_val)
812 tx_port_no = int(next(tx_port_iter).group(0))
813 intf = self.vnfd_helper.find_interface_by_port(tx_port_no)
814 mac = intf["virtual-interface"]["dst_mac"]
815 section_data[1] = mac
817 if item_val.startswith("@@src_mac"):
818 tx_port_iter = re.finditer(r'\d+', item_val)
819 tx_port_no = int(next(tx_port_iter).group(0))
820 intf = self.vnfd_helper.find_interface_by_port(tx_port_no)
821 mac = intf["virtual-interface"]["local_mac"]
822 section_data[1] = mac.replace(":", " ", 6)
824 if item_key == "src mac" and item_val.startswith("@@"):
825 tx_port_iter = re.finditer(r'\d+', item_val)
826 tx_port_no = int(next(tx_port_iter).group(0))
827 intf = self.vnfd_helper.find_interface_by_port(tx_port_no)
828 mac = intf["virtual-interface"]["local_mac"]
829 section_data[1] = mac
831 # if addition file specified in prox config
832 if not self.additional_files:
835 for section_name, section in sections:
836 for section_data in section:
838 if section_data[0].startswith("dofile"):
839 section_data[0] = self._insert_additional_file(section_data[0])
841 if section_data[1].startswith("dofile"):
842 section_data[1] = self._insert_additional_file(section_data[1])
843 except: # pylint: disable=bare-except
849 def write_prox_lua(lua_config):
851 Write an .ini-format config file for PROX (parameters.lua)
852 PROX does not allow a space before/after the =, so we need
856 for key in lua_config:
857 value = '"' + lua_config[key] + '"'
858 if key == "__name__":
860 if value is not None and value != '@':
861 key = "=".join((key, str(value).replace('\n', '\n\t')))
864 key = str(key).replace('\n', '\n\t')
866 return os.linesep.join(out)
869 def write_prox_config(prox_config):
871 Write an .ini-format config file for PROX
872 PROX does not allow a space before/after the =, so we need
876 for (section_name, section) in prox_config:
877 out.append("[{}]".format(section_name))
880 if key == "__name__":
882 if value is not None and value != '@':
883 key = "=".join((key, str(value).replace('\n', '\n\t')))
886 key = str(key).replace('\n', '\n\t')
888 return os.linesep.join(out)
890 def put_string_to_file(self, s, remote_path):
891 file_obj = cStringIO(s)
892 self.ssh_helper.put_file_obj(file_obj, remote_path)
895 def generate_prox_lua_file(self):
897 all_ports = self.vnfd_helper.port_pairs.all_ports
898 for port_name in all_ports:
899 port_num = self.vnfd_helper.port_num(port_name)
900 intf = self.vnfd_helper.find_interface(name=port_name)
901 vintf = intf['virtual-interface']
902 p["tester_mac{0}".format(port_num)] = vintf["dst_mac"]
903 p["src_mac{0}".format(port_num)] = vintf["local_mac"]
907 def upload_prox_lua(self, config_file, lua_data):
908 # prox can't handle spaces around ' = ' so use custom method
909 out = StringIO(self.write_prox_lua(lua_data))
911 remote_path = os.path.join("/tmp", config_file)
912 self.ssh_helper.put_file_obj(out, remote_path)
916 def upload_prox_config(self, config_file, prox_config_data):
917 # prox can't handle spaces around ' = ' so use custom method
918 out = StringIO(self.write_prox_config(prox_config_data))
920 remote_path = os.path.join("/tmp", config_file)
921 self.ssh_helper.put_file_obj(out, remote_path)
925 def build_config_file(self):
926 task_path = self.scenario_helper.task_path
927 options = self.scenario_helper.options
928 config_path = options['prox_config']
929 config_file = os.path.basename(config_path)
930 config_path = utils.find_relative_file(config_path, task_path)
931 self.additional_files = {}
934 if options['prox_generate_parameter']:
936 self.lua = self.generate_prox_lua_file()
937 if len(self.lua) > 0:
938 self.upload_prox_lua("parameters.lua", self.lua)
939 except: # pylint: disable=bare-except
942 prox_files = options.get('prox_files', [])
943 if isinstance(prox_files, six.string_types):
944 prox_files = [prox_files]
945 for key_prox_file in prox_files:
946 base_prox_file = os.path.basename(key_prox_file)
947 key_prox_path = utils.find_relative_file(key_prox_file, task_path)
948 remote_prox_file = self.copy_to_target(key_prox_path, base_prox_file)
949 self.additional_files[base_prox_file] = remote_prox_file
951 self._prox_config_data = self.generate_prox_config_file(config_path)
952 # copy config to queue so we can read it from traffic_runner process
953 self.config_queue.put(self._prox_config_data)
954 self.remote_path = self.upload_prox_config(config_file, self._prox_config_data)
956 def build_config(self):
957 self.build_config_file()
959 options = self.scenario_helper.options
960 prox_args = options['prox_args']
961 tool_path = self.ssh_helper.join_bin_path(self.APP_NAME)
963 self.pipeline_kwargs = {
964 'tool_path': tool_path,
965 'tool_dir': os.path.dirname(tool_path),
966 'cfg_file': self.remote_path,
967 'args': ' '.join(' '.join([str(k), str(v) if v else ''])
968 for k, v in prox_args.items())
971 cmd_template = ("sudo bash -c 'cd {tool_dir}; {tool_path} -o cli "
972 "{args} -f {cfg_file} '")
973 return cmd_template.format(**self.pipeline_kwargs)
976 # this might be bad, sometimes we want regular ResourceHelper methods, like collect_kpi
977 class ProxResourceHelper(ClientResourceHelper):
979 RESOURCE_WORD = 'prox'
986 def find_pci(pci, bound_pci):
987 # we have to substring match PCI bus address from the end
988 return any(b.endswith(pci) for b in bound_pci)
990 def __init__(self, setup_helper):
991 super(ProxResourceHelper, self).__init__(setup_helper)
992 self.mgmt_interface = self.vnfd_helper.mgmt_interface
993 self._user = self.mgmt_interface["user"]
994 self._ip = self.mgmt_interface["ip"]
997 self._vpci_to_if_name_map = None
998 self.additional_file = {}
999 self.remote_prox_file_name = None
1003 self.step_time = 0.5
1004 self._test_type = None
1009 self.client = self._connect()
1013 def test_type(self):
1014 if self._test_type is None:
1015 self._test_type = self.setup_helper.find_in_section('global', 'name', None)
1016 return self._test_type
1018 def run_traffic(self, traffic_profile, *args):
1019 self._queue.cancel_join_thread()
1023 traffic_profile.init(self._queue)
1024 # this frees up the run_traffic loop
1025 self.client_started.value = 1
1027 while not self._terminated.value:
1028 # move it all to traffic_profile
1029 self._run_traffic_once(traffic_profile)
1031 def _run_traffic_once(self, traffic_profile):
1032 traffic_profile.execute_traffic(self)
1033 if traffic_profile.done.is_set():
1034 self._queue.put({'done': True})
1035 LOG.debug("tg_prox done")
1036 self._terminated.value = 1
1038 # For VNF use ResourceHelper method to collect KPIs directly.
1039 # for TG leave the superclass ClientResourceHelper collect_kpi_method intact
1040 def collect_collectd_kpi(self):
1041 return self._collect_resource_kpi()
1043 def collect_kpi(self):
1044 result = super(ProxResourceHelper, self).collect_kpi()
1045 # add in collectd kpis manually
1047 result['collect_stats'] = self._collect_resource_kpi()
1050 def terminate(self):
1051 # should not be called, use VNF terminate
1052 raise NotImplementedError()
1055 return self.sut # force connection
1057 def execute(self, cmd, *args, **kwargs):
1058 func = getattr(self.sut, cmd, None)
1060 return func(*args, **kwargs)
1063 def _connect(self, client=None):
1064 """Run and connect to prox on the remote system """
1065 # De-allocating a large amount of hugepages takes some time. If a new
1066 # PROX instance is started immediately after killing the previous one,
1067 # it might not be able to allocate hugepages, because they are still
1068 # being freed. Hence the -w switch.
1069 # self.connection.execute("sudo killall -w Prox 2>/dev/null")
1070 # prox_cmd = "export TERM=xterm; cd "+ self.bin_path +"; ./Prox -t
1071 # -f ./handle_none-4.cfg"
1072 # prox_cmd = "export TERM=xterm; export RTE_SDK=" + self._dpdk_dir +
1074 # + "export RTE_TARGET=" + self._dpdk_target + ";" \
1075 # + " cd " + self._prox_dir + "; make HW_DIRECT_STATS=y -j50;
1077 # + "./build/Prox " + prox_args
1078 # log.debug("Starting PROX with command [%s]", prox_cmd)
1079 # thread.start_new_thread(self.ssh_check_quit, (self, self._user,
1080 # self._ip, prox_cmd))
1082 client = ProxSocketHelper()
1084 # try connecting to Prox for 60s
1085 for _ in range(RETRY_SECONDS):
1086 time.sleep(RETRY_INTERVAL)
1088 client.connect(self._ip, PROX_PORT)
1089 except (socket.gaierror, socket.error):
1094 msg = "Failed to connect to prox, please check if system {} accepts connections on port {}"
1095 raise Exception(msg.format(self._ip, PROX_PORT))
1098 class ProxDataHelper(object):
1100 def __init__(self, vnfd_helper, sut, pkt_size, value, tolerated_loss, line_speed):
1101 super(ProxDataHelper, self).__init__()
1102 self.vnfd_helper = vnfd_helper
1104 self.pkt_size = pkt_size
1106 self.line_speed = line_speed
1107 self.tolerated_loss = tolerated_loss
1108 self.port_count = len(self.vnfd_helper.port_pairs.all_ports)
1110 self.measured_stats = None
1112 self._totals_and_pps = None
1113 self.result_tuple = None
1116 def totals_and_pps(self):
1117 if self._totals_and_pps is None:
1118 rx_total = tx_total = 0
1120 timeout = time.time() + constants.RETRY_TIMEOUT
1122 ok, all_ports = self.sut.multi_port_stats([
1123 self.vnfd_helper.port_num(port_name)
1124 for port_name in self.vnfd_helper.port_pairs.all_ports])
1125 if time.time() > timeout:
1128 for port in all_ports:
1129 rx_total = rx_total + port[1]
1130 tx_total = tx_total + port[2]
1131 requested_pps = self.value / 100.0 * self.line_rate_to_pps()
1132 self._totals_and_pps = rx_total, tx_total, requested_pps
1133 return self._totals_and_pps
1138 ret_val = self.totals_and_pps[0]
1139 except (AttributeError, ValueError, TypeError, LookupError):
1146 ret_val = self.totals_and_pps[1]
1147 except (AttributeError, ValueError, TypeError, LookupError):
1152 def requested_pps(self):
1154 ret_val = self.totals_and_pps[2]
1155 except (AttributeError, ValueError, TypeError, LookupError):
1164 for port_name, port_num in self.vnfd_helper.ports_iter():
1165 ports.append(port_num)
1166 port_names[port_num] = port_name
1169 timeout = time.time() + constants.RETRY_TIMEOUT
1171 ok, results = self.sut.multi_port_stats(ports)
1172 if time.time() > timeout:
1175 for result in results:
1176 port_num = result[0]
1178 samples[port_names[port_num]] = {
1179 "in_packets": result[1],
1180 "out_packets": result[2]}
1181 except (IndexError, KeyError):
1185 def __enter__(self):
1186 self.check_interface_count()
1189 def __exit__(self, exc_type, exc_val, exc_tb):
1192 def make_tuple(self):
1193 if self.result_tuple:
1196 self.result_tuple = ProxTestDataTuple(
1197 self.tolerated_loss,
1199 self.measured_stats['delta'].rx,
1200 self.measured_stats['delta'].tx,
1201 self.measured_stats['delta'].tsc,
1207 self.result_tuple.log_data()
1210 def measure_tot_stats(self):
1211 with self.sut.measure_tot_stats() as self.measured_stats:
1214 def check_interface_count(self):
1215 # do this assert in init? unless we expect interface count to
1216 # change from one run to another run...
1217 assert self.port_count in {1, 2, 4}, \
1218 "Invalid number of ports: 1, 2 or 4 ports only supported at this time"
1220 def capture_tsc_hz(self):
1221 self.tsc_hz = float(self.sut.hz())
1223 def line_rate_to_pps(self):
1224 return self.port_count * self.line_speed / BITS_PER_BYTE / (self.pkt_size + 20)
1226 class ProxProfileHelper(object):
1228 __prox_profile_type__ = "Generic"
1230 PROX_CORE_GEN_MODE = "gen"
1231 PROX_CORE_LAT_MODE = "lat"
1234 def get_cls(cls, helper_type):
1235 """Return class of specified type."""
1237 return ProxProfileHelper
1239 for profile_helper_class in utils.itersubclasses(cls):
1240 if helper_type == profile_helper_class.__prox_profile_type__:
1241 return profile_helper_class
1243 return ProxProfileHelper
1246 def make_profile_helper(cls, resource_helper):
1247 return cls.get_cls(resource_helper.test_type)(resource_helper)
1249 def __init__(self, resource_helper):
1250 super(ProxProfileHelper, self).__init__()
1251 self.resource_helper = resource_helper
1252 self._cpu_topology = None
1253 self._test_cores = None
1254 self._latency_cores = None
1257 def cpu_topology(self):
1258 if not self._cpu_topology:
1259 stdout = io.BytesIO()
1260 self.ssh_helper.get_file_obj("/proc/cpuinfo", stdout)
1261 self._cpu_topology = SocketTopology.parse_cpuinfo(stdout.getvalue().decode('utf-8'))
1262 return self._cpu_topology
1265 def test_cores(self):
1266 if not self._test_cores:
1267 self._test_cores = self.get_cores(self.PROX_CORE_GEN_MODE)
1268 return self._test_cores
1271 def latency_cores(self):
1272 if not self._latency_cores:
1273 self._latency_cores = self.get_cores(self.PROX_CORE_LAT_MODE)
1274 return self._latency_cores
1277 def traffic_context(self, pkt_size, value):
1279 self.sut.reset_stats()
1281 self.sut.set_pkt_size(self.test_cores, pkt_size)
1282 self.sut.set_speed(self.test_cores, value)
1283 self.sut.start_all()
1289 def get_cores(self, mode):
1292 for section_name, section in self.setup_helper.prox_config_data:
1293 if not section_name.startswith("core"):
1296 for key, value in section:
1297 if key == "mode" and value == mode:
1298 core_tuple = CoreSocketTuple(section_name)
1299 core = core_tuple.core_id
1304 def pct_10gbps(self, percent, line_speed):
1305 """Get rate in percent of 10 Gbps.
1307 Returns the rate in percent of 10 Gbps.
1308 For instance 100.0 = 10 Gbps; 400.0 = 40 Gbps.
1310 This helper method isrequired when setting interface_speed option in
1311 the testcase because NSB/PROX considers 10Gbps as 100% of line rate,
1312 this means that the line rate must be expressed as a percentage of
1315 :param percent: (float) Percent of line rate (100.0 = line rate).
1316 :param line_speed: (int) line rate speed, in bits per second.
1318 :return: (float) Represents the rate in percent of 10Gbps.
1320 return (percent * line_speed / (
1321 constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT))
1323 def run_test(self, pkt_size, duration, value, tolerated_loss=0.0,
1324 line_speed=(constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT)):
1325 data_helper = ProxDataHelper(self.vnfd_helper, self.sut, pkt_size,
1326 value, tolerated_loss, line_speed)
1328 with data_helper, self.traffic_context(pkt_size,
1329 self.pct_10gbps(value, line_speed)):
1330 with data_helper.measure_tot_stats():
1331 time.sleep(duration)
1332 # Getting statistics to calculate PPS at right speed....
1333 data_helper.capture_tsc_hz()
1334 data_helper.latency = self.get_latency()
1336 return data_helper.result_tuple, data_helper.samples
1338 def get_latency(self):
1340 :return: return lat_min, lat_max, lat_avg
1344 if not self._latency_cores:
1345 self._latency_cores = self.get_cores(self.PROX_CORE_LAT_MODE)
1347 if self._latency_cores:
1348 return self.sut.lat_stats(self._latency_cores)
1351 def terminate(self):
1354 def __getattr__(self, item):
1355 return getattr(self.resource_helper, item)
1358 class ProxMplsProfileHelper(ProxProfileHelper):
1360 __prox_profile_type__ = "MPLS tag/untag"
1362 def __init__(self, resource_helper):
1363 super(ProxMplsProfileHelper, self).__init__(resource_helper)
1364 self._cores_tuple = None
1367 def mpls_cores(self):
1368 if not self._cores_tuple:
1369 self._cores_tuple = self.get_cores_mpls()
1370 return self._cores_tuple
1373 def tagged_cores(self):
1374 return self.mpls_cores[0]
1377 def plain_cores(self):
1378 return self.mpls_cores[1]
1380 def get_cores_mpls(self):
1383 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1384 if not section_name.startswith("core"):
1387 if all(key != "mode" or value != self.PROX_CORE_GEN_MODE for key, value in section):
1390 for item_key, item_value in section:
1391 if item_key != 'name':
1394 if item_value.startswith("tag"):
1395 core_tuple = CoreSocketTuple(section_name)
1396 core_tag = core_tuple.core_id
1397 cores_tagged.append(core_tag)
1399 elif item_value.startswith("udp"):
1400 core_tuple = CoreSocketTuple(section_name)
1401 core_udp = core_tuple.core_id
1402 cores_plain.append(core_udp)
1404 return cores_tagged, cores_plain
1407 def traffic_context(self, pkt_size, value):
1409 self.sut.reset_stats()
1411 self.sut.set_pkt_size(self.tagged_cores, pkt_size)
1412 self.sut.set_pkt_size(self.plain_cores, pkt_size - 4)
1413 self.sut.set_speed(self.tagged_cores, value)
1414 ratio = 1.0 * (pkt_size - 4 + 20) / (pkt_size + 20)
1415 self.sut.set_speed(self.plain_cores, value * ratio)
1416 self.sut.start_all()
1423 class ProxBngProfileHelper(ProxProfileHelper):
1425 __prox_profile_type__ = "BNG gen"
1427 def __init__(self, resource_helper):
1428 super(ProxBngProfileHelper, self).__init__(resource_helper)
1429 self._cores_tuple = None
1432 def bng_cores(self):
1433 if not self._cores_tuple:
1434 self._cores_tuple = self.get_cores_gen_bng_qos()
1435 return self._cores_tuple
1438 def cpe_cores(self):
1439 return self.bng_cores[0]
1442 def inet_cores(self):
1443 return self.bng_cores[1]
1446 def arp_cores(self):
1447 return self.bng_cores[2]
1450 def arp_task_cores(self):
1451 return self.bng_cores[3]
1454 def all_rx_cores(self):
1455 return self.latency_cores
1457 def get_cores_gen_bng_qos(self):
1461 arp_tasks_core = [0]
1462 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1463 if not section_name.startswith("core"):
1466 if all(key != "mode" or value != self.PROX_CORE_GEN_MODE for key, value in section):
1469 for item_key, item_value in section:
1470 if item_key != 'name':
1473 if item_value.startswith("cpe"):
1474 core_tuple = CoreSocketTuple(section_name)
1475 cpe_core = core_tuple.core_id
1476 cpe_cores.append(cpe_core)
1478 elif item_value.startswith("inet"):
1479 core_tuple = CoreSocketTuple(section_name)
1480 inet_core = core_tuple.core_id
1481 inet_cores.append(inet_core)
1483 elif item_value.startswith("arp"):
1484 core_tuple = CoreSocketTuple(section_name)
1485 arp_core = core_tuple.core_id
1486 arp_cores.append(arp_core)
1488 # We check the tasks/core separately
1489 if item_value.startswith("arp_task"):
1490 core_tuple = CoreSocketTuple(section_name)
1491 arp_task_core = core_tuple.core_id
1492 arp_tasks_core.append(arp_task_core)
1494 return cpe_cores, inet_cores, arp_cores, arp_tasks_core
1497 def traffic_context(self, pkt_size, value):
1498 # Tester is sending packets at the required speed already after
1499 # setup_test(). Just get the current statistics, sleep the required
1500 # amount of time and calculate packet loss.
1501 inet_pkt_size = pkt_size
1502 cpe_pkt_size = pkt_size - 24
1503 ratio = 1.0 * (cpe_pkt_size + 20) / (inet_pkt_size + 20)
1505 curr_up_speed = curr_down_speed = 0
1506 max_up_speed = max_down_speed = value
1508 max_down_speed = value * ratio
1510 max_up_speed = value / ratio
1516 # Flush any packets in the NIC RX buffers, otherwise the stats will be
1518 self.sut.start(self.all_rx_cores)
1520 self.sut.stop(self.all_rx_cores)
1522 self.sut.reset_stats()
1524 self.sut.set_pkt_size(self.inet_cores, inet_pkt_size)
1525 self.sut.set_pkt_size(self.cpe_cores, cpe_pkt_size)
1527 self.sut.reset_values(self.cpe_cores)
1528 self.sut.reset_values(self.inet_cores)
1530 # Set correct IP and UDP lengths in packet headers
1532 # IP length (byte 24): 26 for MAC(12), EthType(2), QinQ(8), CRC(4)
1533 self.sut.set_value(self.cpe_cores, 24, cpe_pkt_size - 26, 2)
1534 # UDP length (byte 46): 46 for MAC(12), EthType(2), QinQ(8), IP(20), CRC(4)
1535 self.sut.set_value(self.cpe_cores, 46, cpe_pkt_size - 46, 2)
1538 # IP length (byte 20): 22 for MAC(12), EthType(2), MPLS(4), CRC(4)
1539 self.sut.set_value(self.inet_cores, 20, inet_pkt_size - 22, 2)
1540 # IP length (byte 48): 50 for MAC(12), EthType(2), MPLS(4), IP(20), GRE(8), CRC(4)
1541 self.sut.set_value(self.inet_cores, 48, inet_pkt_size - 50, 2)
1542 # UDP length (byte 70): 70 for MAC(12), EthType(2), MPLS(4), IP(20), GRE(8), IP(20), CRC(4)
1543 self.sut.set_value(self.inet_cores, 70, inet_pkt_size - 70, 2)
1545 # Sending ARP to initialize tables - need a few seconds of generation
1546 # to make sure all CPEs are initialized
1547 LOG.info("Initializing SUT: sending ARP packets")
1548 self.sut.set_speed(self.arp_cores, 1, self.arp_task_cores)
1549 self.sut.set_speed(self.inet_cores, curr_up_speed)
1550 self.sut.set_speed(self.cpe_cores, curr_down_speed)
1551 self.sut.start(self.arp_cores)
1554 # Ramp up the transmission speed. First go to the common speed, then
1555 # increase steps for the faster one.
1556 self.sut.start(self.cpe_cores + self.inet_cores + self.latency_cores)
1558 LOG.info("Ramping up speed to %s up, %s down", max_up_speed, max_down_speed)
1560 while (curr_up_speed < max_up_speed) or (curr_down_speed < max_down_speed):
1561 # The min(..., ...) takes care of 1) floating point rounding errors
1562 # that could make curr_*_speed to be slightly greater than
1563 # max_*_speed and 2) max_*_speed not being an exact multiple of
1565 if curr_up_speed < max_up_speed:
1566 curr_up_speed = min(curr_up_speed + self.step_delta, max_up_speed)
1567 if curr_down_speed < max_down_speed:
1568 curr_down_speed = min(curr_down_speed + self.step_delta, max_down_speed)
1570 self.sut.set_speed(self.inet_cores, curr_up_speed)
1571 self.sut.set_speed(self.cpe_cores, curr_down_speed)
1572 time.sleep(self.step_time)
1574 LOG.info("Target speeds reached. Starting real test.")
1578 self.sut.stop(self.arp_cores + self.cpe_cores + self.inet_cores)
1579 LOG.info("Test ended. Flushing NIC buffers")
1580 self.sut.start(self.all_rx_cores)
1582 self.sut.stop(self.all_rx_cores)
1584 def run_test(self, pkt_size, duration, value, tolerated_loss=0.0,
1585 line_speed=(constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT)):
1586 data_helper = ProxDataHelper(self.vnfd_helper, self.sut, pkt_size,
1587 value, tolerated_loss, line_speed)
1589 with data_helper, self.traffic_context(pkt_size,
1590 self.pct_10gbps(value, line_speed)):
1591 with data_helper.measure_tot_stats():
1592 time.sleep(duration)
1593 # Getting statistics to calculate PPS at right speed....
1594 data_helper.capture_tsc_hz()
1595 data_helper.latency = self.get_latency()
1597 return data_helper.result_tuple, data_helper.samples
1600 class ProxVpeProfileHelper(ProxProfileHelper):
1602 __prox_profile_type__ = "vPE gen"
1604 def __init__(self, resource_helper):
1605 super(ProxVpeProfileHelper, self).__init__(resource_helper)
1606 self._cores_tuple = None
1607 self._ports_tuple = None
1610 def vpe_cores(self):
1611 if not self._cores_tuple:
1612 self._cores_tuple = self.get_cores_gen_vpe()
1613 return self._cores_tuple
1616 def cpe_cores(self):
1617 return self.vpe_cores[0]
1620 def inet_cores(self):
1621 return self.vpe_cores[1]
1624 def all_rx_cores(self):
1625 return self.latency_cores
1628 def vpe_ports(self):
1629 if not self._ports_tuple:
1630 self._ports_tuple = self.get_ports_gen_vpe()
1631 return self._ports_tuple
1634 def cpe_ports(self):
1635 return self.vpe_ports[0]
1638 def inet_ports(self):
1639 return self.vpe_ports[1]
1641 def get_cores_gen_vpe(self):
1644 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1645 if not section_name.startswith("core"):
1648 if all(key != "mode" or value != self.PROX_CORE_GEN_MODE for key, value in section):
1651 for item_key, item_value in section:
1652 if item_key != 'name':
1655 if item_value.startswith("cpe"):
1656 core_tuple = CoreSocketTuple(section_name)
1657 core_tag = core_tuple.core_id
1658 cpe_cores.append(core_tag)
1660 elif item_value.startswith("inet"):
1661 core_tuple = CoreSocketTuple(section_name)
1662 inet_core = core_tuple.core_id
1663 inet_cores.append(inet_core)
1665 return cpe_cores, inet_cores
1667 def get_ports_gen_vpe(self):
1671 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1672 if not section_name.startswith("port"):
1674 tx_port_iter = re.finditer(r'\d+', section_name)
1675 tx_port_no = int(next(tx_port_iter).group(0))
1677 for item_key, item_value in section:
1678 if item_key != 'name':
1681 if item_value.startswith("cpe"):
1682 cpe_ports.append(tx_port_no)
1684 elif item_value.startswith("inet"):
1685 inet_ports.append(tx_port_no)
1687 return cpe_ports, inet_ports
1690 def traffic_context(self, pkt_size, value):
1691 # Calculate the target upload and download speed. The upload and
1692 # download packets have different packet sizes, so in order to get
1693 # equal bandwidth usage, the ratio of the speeds has to match the ratio
1694 # of the packet sizes.
1695 cpe_pkt_size = pkt_size
1696 inet_pkt_size = pkt_size - 4
1697 ratio = 1.0 * (cpe_pkt_size + 20) / (inet_pkt_size + 20)
1699 curr_up_speed = curr_down_speed = 0
1700 max_up_speed = max_down_speed = value
1702 max_down_speed = value * ratio
1704 max_up_speed = value / ratio
1706 # Adjust speed when multiple cores per port are used to generate traffic
1707 if len(self.cpe_ports) != len(self.cpe_cores):
1708 max_down_speed *= 1.0 * len(self.cpe_ports) / len(self.cpe_cores)
1709 if len(self.inet_ports) != len(self.inet_cores):
1710 max_up_speed *= 1.0 * len(self.inet_ports) / len(self.inet_cores)
1716 # Flush any packets in the NIC RX buffers, otherwise the stats will be
1718 self.sut.start(self.all_rx_cores)
1720 self.sut.stop(self.all_rx_cores)
1722 self.sut.reset_stats()
1724 self.sut.set_pkt_size(self.inet_cores, inet_pkt_size)
1725 self.sut.set_pkt_size(self.cpe_cores, cpe_pkt_size)
1727 self.sut.reset_values(self.cpe_cores)
1728 self.sut.reset_values(self.inet_cores)
1730 # Set correct IP and UDP lengths in packet headers
1731 # CPE: IP length (byte 24): 26 for MAC(12), EthType(2), QinQ(8), CRC(4)
1732 self.sut.set_value(self.cpe_cores, 24, cpe_pkt_size - 26, 2)
1733 # UDP length (byte 46): 46 for MAC(12), EthType(2), QinQ(8), IP(20), CRC(4)
1734 self.sut.set_value(self.cpe_cores, 46, cpe_pkt_size - 46, 2)
1736 # INET: IP length (byte 20): 22 for MAC(12), EthType(2), MPLS(4), CRC(4)
1737 self.sut.set_value(self.inet_cores, 20, inet_pkt_size - 22, 2)
1738 # UDP length (byte 42): 42 for MAC(12), EthType(2), MPLS(4), IP(20), CRC(4)
1739 self.sut.set_value(self.inet_cores, 42, inet_pkt_size - 42, 2)
1741 self.sut.set_speed(self.inet_cores, curr_up_speed)
1742 self.sut.set_speed(self.cpe_cores, curr_down_speed)
1744 # Ramp up the transmission speed. First go to the common speed, then
1745 # increase steps for the faster one.
1746 self.sut.start(self.cpe_cores + self.inet_cores + self.all_rx_cores)
1748 LOG.info("Ramping up speed to %s up, %s down", max_up_speed, max_down_speed)
1750 while (curr_up_speed < max_up_speed) or (curr_down_speed < max_down_speed):
1751 # The min(..., ...) takes care of 1) floating point rounding errors
1752 # that could make curr_*_speed to be slightly greater than
1753 # max_*_speed and 2) max_*_speed not being an exact multiple of
1755 if curr_up_speed < max_up_speed:
1756 curr_up_speed = min(curr_up_speed + self.step_delta, max_up_speed)
1757 if curr_down_speed < max_down_speed:
1758 curr_down_speed = min(curr_down_speed + self.step_delta, max_down_speed)
1760 self.sut.set_speed(self.inet_cores, curr_up_speed)
1761 self.sut.set_speed(self.cpe_cores, curr_down_speed)
1762 time.sleep(self.step_time)
1764 LOG.info("Target speeds reached. Starting real test.")
1768 self.sut.stop(self.cpe_cores + self.inet_cores)
1769 LOG.info("Test ended. Flushing NIC buffers")
1770 self.sut.start(self.all_rx_cores)
1772 self.sut.stop(self.all_rx_cores)
1774 def run_test(self, pkt_size, duration, value, tolerated_loss=0.0,
1775 line_speed=(constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT)):
1776 data_helper = ProxDataHelper(self.vnfd_helper, self.sut, pkt_size,
1777 value, tolerated_loss, line_speed)
1779 with data_helper, self.traffic_context(pkt_size,
1780 self.pct_10gbps(value, line_speed)):
1781 with data_helper.measure_tot_stats():
1782 time.sleep(duration)
1783 # Getting statistics to calculate PPS at right speed....
1784 data_helper.capture_tsc_hz()
1785 data_helper.latency = self.get_latency()
1787 return data_helper.result_tuple, data_helper.samples
1790 class ProxlwAFTRProfileHelper(ProxProfileHelper):
1792 __prox_profile_type__ = "lwAFTR gen"
1794 def __init__(self, resource_helper):
1795 super(ProxlwAFTRProfileHelper, self).__init__(resource_helper)
1796 self._cores_tuple = None
1797 self._ports_tuple = None
1799 self.step_time = 0.5
1802 def _lwaftr_cores(self):
1803 if not self._cores_tuple:
1804 self._cores_tuple = self._get_cores_gen_lwaftr()
1805 return self._cores_tuple
1808 def tun_cores(self):
1809 return self._lwaftr_cores[0]
1812 def inet_cores(self):
1813 return self._lwaftr_cores[1]
1816 def _lwaftr_ports(self):
1817 if not self._ports_tuple:
1818 self._ports_tuple = self._get_ports_gen_lw_aftr()
1819 return self._ports_tuple
1822 def tun_ports(self):
1823 return self._lwaftr_ports[0]
1826 def inet_ports(self):
1827 return self._lwaftr_ports[1]
1830 def all_rx_cores(self):
1831 return self.latency_cores
1833 def _get_cores_gen_lwaftr(self):
1836 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1837 if not section_name.startswith("core"):
1840 if all(key != "mode" or value != self.PROX_CORE_GEN_MODE for key, value in section):
1843 core_tuple = CoreSocketTuple(section_name)
1844 core_tag = core_tuple.core_id
1845 for item_value in (v for k, v in section if k == 'name'):
1846 if item_value.startswith('tun'):
1847 tun_cores.append(core_tag)
1848 elif item_value.startswith('inet'):
1849 inet_cores.append(core_tag)
1851 return tun_cores, inet_cores
1853 def _get_ports_gen_lw_aftr(self):
1857 re_port = re.compile(r'port (\d+)')
1858 for section_name, section in self.resource_helper.setup_helper.prox_config_data:
1859 match = re_port.search(section_name)
1863 tx_port_no = int(match.group(1))
1864 for item_value in (v for k, v in section if k == 'name'):
1865 if item_value.startswith('lwB4'):
1866 tun_ports.append(tx_port_no)
1867 elif item_value.startswith('inet'):
1868 inet_ports.append(tx_port_no)
1870 return tun_ports, inet_ports
1873 def _resize(len1, len2):
1876 return 1.0 * len1 / len2
1879 def traffic_context(self, pkt_size, value):
1880 # Tester is sending packets at the required speed already after
1881 # setup_test(). Just get the current statistics, sleep the required
1882 # amount of time and calculate packet loss.
1883 tun_pkt_size = pkt_size
1884 inet_pkt_size = pkt_size - 40
1885 ratio = 1.0 * (tun_pkt_size + 20) / (inet_pkt_size + 20)
1887 curr_up_speed = curr_down_speed = 0
1888 max_up_speed = max_down_speed = value
1890 max_up_speed = value / ratio
1892 # Adjust speed when multiple cores per port are used to generate traffic
1893 if len(self.tun_ports) != len(self.tun_cores):
1894 max_down_speed *= self._resize(len(self.tun_ports), len(self.tun_cores))
1895 if len(self.inet_ports) != len(self.inet_cores):
1896 max_up_speed *= self._resize(len(self.inet_ports), len(self.inet_cores))
1902 # Flush any packets in the NIC RX buffers, otherwise the stats will be
1904 self.sut.start(self.all_rx_cores)
1906 self.sut.stop(self.all_rx_cores)
1908 self.sut.reset_stats()
1910 self.sut.set_pkt_size(self.inet_cores, inet_pkt_size)
1911 self.sut.set_pkt_size(self.tun_cores, tun_pkt_size)
1913 self.sut.reset_values(self.tun_cores)
1914 self.sut.reset_values(self.inet_cores)
1916 # Set correct IP and UDP lengths in packet headers
1918 # IPv6 length (byte 18): 58 for MAC(12), EthType(2), IPv6(40) , CRC(4)
1919 self.sut.set_value(self.tun_cores, 18, tun_pkt_size - 58, 2)
1920 # IP length (byte 56): 58 for MAC(12), EthType(2), CRC(4)
1921 self.sut.set_value(self.tun_cores, 56, tun_pkt_size - 58, 2)
1922 # UDP length (byte 78): 78 for MAC(12), EthType(2), IP(20), UDP(8), CRC(4)
1923 self.sut.set_value(self.tun_cores, 78, tun_pkt_size - 78, 2)
1926 # IP length (byte 20): 22 for MAC(12), EthType(2), CRC(4)
1927 self.sut.set_value(self.inet_cores, 16, inet_pkt_size - 18, 2)
1928 # UDP length (byte 42): 42 for MAC(12), EthType(2), IP(20), UPD(8), CRC(4)
1929 self.sut.set_value(self.inet_cores, 38, inet_pkt_size - 38, 2)
1931 LOG.info("Initializing SUT: sending lwAFTR packets")
1932 self.sut.set_speed(self.inet_cores, curr_up_speed)
1933 self.sut.set_speed(self.tun_cores, curr_down_speed)
1936 # Ramp up the transmission speed. First go to the common speed, then
1937 # increase steps for the faster one.
1938 self.sut.start(self.tun_cores + self.inet_cores + self.latency_cores)
1940 LOG.info("Ramping up speed to %s up, %s down", max_up_speed, max_down_speed)
1942 while (curr_up_speed < max_up_speed) or (curr_down_speed < max_down_speed):
1943 # The min(..., ...) takes care of 1) floating point rounding errors
1944 # that could make curr_*_speed to be slightly greater than
1945 # max_*_speed and 2) max_*_speed not being an exact multiple of
1947 if curr_up_speed < max_up_speed:
1948 curr_up_speed = min(curr_up_speed + self.step_delta, max_up_speed)
1949 if curr_down_speed < max_down_speed:
1950 curr_down_speed = min(curr_down_speed + self.step_delta, max_down_speed)
1952 self.sut.set_speed(self.inet_cores, curr_up_speed)
1953 self.sut.set_speed(self.tun_cores, curr_down_speed)
1954 time.sleep(self.step_time)
1956 LOG.info("Target speeds reached. Starting real test.")
1960 self.sut.stop(self.tun_cores + self.inet_cores)
1961 LOG.info("Test ended. Flushing NIC buffers")
1962 self.sut.start(self.all_rx_cores)
1964 self.sut.stop(self.all_rx_cores)
1966 def run_test(self, pkt_size, duration, value, tolerated_loss=0.0,
1967 line_speed=(constants.ONE_GIGABIT_IN_BITS * constants.NIC_GBPS_DEFAULT)):
1968 data_helper = ProxDataHelper(self.vnfd_helper, self.sut, pkt_size,
1969 value, tolerated_loss, line_speed)
1971 with data_helper, self.traffic_context(pkt_size,
1972 self.pct_10gbps(value, line_speed)):
1973 with data_helper.measure_tot_stats():
1974 time.sleep(duration)
1975 # Getting statistics to calculate PPS at right speed....
1976 data_helper.capture_tsc_hz()
1977 data_helper.latency = self.get_latency()
1979 return data_helper.result_tuple, data_helper.samples
1982 class ProxIrqProfileHelper(ProxProfileHelper):
1984 __prox_profile_type__ = "IRQ Query"
1986 def __init__(self, resource_helper):
1987 super(ProxIrqProfileHelper, self).__init__(resource_helper)
1988 self._cores_tuple = None
1989 self._ports_tuple = None
1991 self.step_time = 0.5