def setUp(self): super(TrexTest, self).setUp() trex_server_addr = testutils.test_param_get("trex_server_addr") self.trex_client = STLClient(server=trex_server_addr) self.trex_client.connect() self.trex_client.acquire() self.trex_client.reset() # Resets configs from all ports self.trex_client.clear_stats() # Clear status from all ports # Put all ports to promiscuous mode, otherwise they will drop all # incoming packets if the destination mac is not the port mac address. self.trex_client.set_port_attr(self.trex_client.get_all_ports(), promiscuous=True)
def connect(self): """Connect to the TRex server.""" server_ip = self.generator_config.ip LOG.info("Connecting to TRex (%s)...", server_ip) # Connect to TRex server self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port, async_port=self.generator_config.zmq_pub_port) try: self.__connect(self.client) if server_ip == '127.0.0.1': config_updated = self.__check_config() if config_updated or self.config.restart: self.__restart() except (TimeoutError, STLError) as e: if server_ip == '127.0.0.1': self.__start_local_server() else: raise TrafficGeneratorException(e.message) ports = list(self.generator_config.ports) self.port_handle = ports # Prepare the ports self.client.reset(ports) # Read HW information from each port # this returns an array of dict (1 per port) """ Example of output for Intel XL710 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True, 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes', u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']}, u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE', u'description': u'Ethernet Controller XL710 for 40GbE QSFP+', 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-', u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match', 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0', 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off', 'layer_mode': 'Ethernet', u'numa': 0}, ...] """ self.port_info = self.client.get_port_info(ports) LOG.info('Connected to TRex') for id, port in enumerate(self.port_info): LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s', id, port['description'], port['speed'], port['src_mac'], port['pci_addr'], port['driver']) # Make sure the 2 ports have the same speed if self.port_info[0]['speed'] != self.port_info[1]['speed']: raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' % (self.port_info[0]['speed'], self.port_info[1]['speed']))
def main(): """Check server info and quit.""" client = STLClient() try: # connect to server client.connect() # get server info print(client.get_server_system_info()) except STLError as ex_error: print(ex_error, file=sys.stderr) sys.exit(1) finally: client.disconnect()
def __init__(self, config): update_config(config) validate_config(config) self.clients = [] self.servers = [] self.tests = config["tests"] self.test_config = None self.statistics = {} self._server_by_name = {} self._server_by_ip = {} self._port_by_ip = {} for test_config in self.tests: test_name = test_config["name"] self.statistics[test_name] = {} for iteration in range(1, test_config["iterations"] + 1): self.statistics[test_name][iteration] = {} for server_config in config["servers"]: server_name = server_config["name"] server_ip = server_config["management_ip"] sync_port = server_config["sync_port"] async_port = server_config["async_port"] client = STLClient( server=server_ip, sync_port=sync_port, async_port=async_port, verbose_level="error", ) ports = server_config["ports"] server = {"name": server_name, "client": client, "ports": ports} for port in ports: port_ip = port["ip"] self._port_by_ip[port_ip] = port self._server_by_ip[port_ip] = server self.clients.append(client) self.servers.append(server) self._server_by_name[server_name] = server
def __init__(self, **kwargs): super(TRex, self).__init__(**kwargs) self.hostname = kwargs.pop("hostname", "localhost") self.__trex_client = STLClient(server=self.hostname)
class TRex(TrafficGeneratorChassis): def __init__(self, **kwargs): super(TRex, self).__init__(**kwargs) self.hostname = kwargs.pop("hostname", "localhost") self.__trex_client = STLClient(server=self.hostname) def _verify_port_action(self, port_name): if self.is_connected() and self._verify_port_string(port_name) and \ port_name in self.port_data: return (True) return (False) def _verify_port_string(self, port_name): try: if int(port_name) < 0: return False except ValueError: return False return True def connect(self): if not self.is_connected(): self.__trex_client.connect() return self.is_connected() def disconnect(self): if self.is_connected: for port in list(self.port_data.keys()): self.port_data[port] = self.release_port(port) self.__trex_client.disconnect() return True def is_connected(self): return self.__trex_client.is_connected() def reserve_port(self, port_name): if not self._verify_port_string(port_name): return False try: self.__trex_client.acquire(ports=[int(port_name)], force=True) except STLError: return False try: self.__trex_client.reset(ports=[int(port_name)]) except STLError: self.__trex_client.release(ports=[int(port_name)]) return False tport = _TRexPort(port_name, self.__trex_client) if tport is None: return False return super(TRex, self).reserve_port(port_name, tport) def release_port(self, port_name): if not self._verify_port_string(port_name) or \ port_name not in self.port_data: return False try: self.__trex_client.release(ports=[port_name]) except STLError: pass return super(TRex, self).release_port(port_name) # # FIXME: All the port specific functions should be re factored to use the # base class so the shared code in _xena and _trex can be removed. # def clear_statistics(self, port_name): if self._verify_port_action(port_name): self.port_data[port_name].clear_statistics() def take_tx_statistics_snapshot(self, port_name): if self._verify_port_action(port_name): self.port_data[port_name].take_tx_statistics_snapshot() def take_rx_statistics_snapshot(self, port_name): if self._verify_port_action(port_name): self.port_data[port_name].take_rx_statistics_snapshot() def get_tx_statistics_snapshots(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].get_tx_statistics_snapshots() return None def get_rx_statistics_snapshots(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].get_rx_statistics_snapshots() return None def start_traffic(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].start_traffic() return False def stop_traffic(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].stop_traffic() return False def configure_traffic_stream(self, port_name, traffic_flows, nr_of_flows, packet_size, **kwargs): if self._verify_port_action(port_name): return self.port_data[port_name].configure_traffic_stream( traffic_flows, nr_of_flows, packet_size, **kwargs) return False def next_traffic_stream(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].next_traffic_stream() return False def get_port_limits(self, port_name): if self._verify_port_action(port_name): return self.port_data[port_name].get_port_limits() return dict()
def main(): parser = argparse.ArgumentParser( description= "STL traffic used for test the scalability of the network functions.") parser.add_argument( "--ip_src", type=str, default="192.168.17.1", help="Source IP address for all packets in the stream.", ) parser.add_argument( "--ip_dst", type=str, default="192.168.17.2", help="Destination IP address for all packets in the stream.", ) parser.add_argument("--pps", type=int, default=1e3, help="Packet per second.") parser.add_argument("--duration", type=int, default=3, help="Test duration.") # Potential lens: 64, 128, 256, 512, 1024 parser.add_argument("--ip_tot_len", type=int, default=64, help="IP total length") parser.add_argument("--num", type=int, default=1, help="Number of rounds tested") parser.add_argument( "--estimate_pps", action="store_true", help="Estimate the maximal pps without dropping packets.", ) args = parser.parse_args() print( "Test information:\n PPS: {}, Duration: {}s, IP_TOT_LEN: {}B, Number of rounds: {}" .format(args.pps, args.duration, args.ip_tot_len, args.num)) test_data = { "pps": args.pps, "duration": args.duration, "ip_tot_len": args.ip_tot_len, "ip_src": args.ip_src, "ip_dst": args.ip_dst, "num": args.num, } try: client = STLClient() client.connect() tx_port, rx_port = init_ports(client) if args.estimate_pps: run_estimate_pps(test_data, client, rx_port, tx_port) else: run_test(test_data, client, rx_port, tx_port, save_data=True) print("All tests finished! Results are in *.data files") except STLError as error: print(error) finally: client.disconnect()
lat = lat_stats["latency"] avg = lat["average"] jitter = lat["jitter"] hist = lat["histogram"] total_max = lat["total_max"] print( f"The average latency: {avg} usecs, total max: {total_max} usecs, jitter: {jitter} usecs" ) print(hist) return True if __name__ == "__main__": # Create a client for stateless tests. clt = STLClient() passed = True try: udp_payload = "A" * 50 pkt = STLPktBuilder( pkt=Ether() / IP(src="192.168.17.1", dst="192.168.17.2") / UDP(dport=8888, sport=9999, chksum=0) / udp_payload) st = STLStream( name="udp_single_burst", packet=pkt, # Packet group id flow_stats=STLFlowLatencyStats(pg_id=PG_ID), mode=STLTXSingleBurst(total_pkts=TOTAL_PKTS, pps=PPS), )
def main(): """Stop traffic if any is running. Report xstats.""" parser = argparse.ArgumentParser() parser.add_argument(u"--xstat0", type=str, default=u"", help=u"Reference xstat object if any.") parser.add_argument(u"--xstat1", type=str, default=u"", help=u"Reference xstat object if any.") args = parser.parse_args() client = STLClient() try: # connect to server client.connect() client.acquire(force=True) # TODO: Support unidirection. client.stop(ports=[0, 1]) # Read the stats after the test, # we need to update values before the last trial started. if args.xstat0: snapshot = eval(args.xstat0) client.ports[0].get_xstats().reference_stats = snapshot if args.xstat1: snapshot = eval(args.xstat1) client.ports[1].get_xstats().reference_stats = snapshot # Now we can call the official method to get differences. xstats0 = client.get_xstats(0) xstats1 = client.get_xstats(1) # If STLError happens, let the script fail with stack trace. finally: client.disconnect() print(u"##### statistics port 0 #####") print(json.dumps(xstats0, indent=4, separators=(u",", u": "))) print(u"##### statistics port 1 #####") print(json.dumps(xstats1, indent=4, separators=(u",", u": "))) tx_0, rx_0 = xstats0[u"tx_good_packets"], xstats0[u"rx_good_packets"] tx_1, rx_1 = xstats1[u"tx_good_packets"], xstats1[u"rx_good_packets"] lost_a, lost_b = tx_0 - rx_1, tx_1 - rx_0 print(f"\npackets lost from 0 --> 1: {lost_a} pkts") print(f"packets lost from 1 --> 0: {lost_b} pkts") total_rcvd, total_sent = rx_0 + rx_1, tx_0 + tx_1 total_lost = total_sent - total_rcvd print(f"rate='unknown'; " f"total_received={total_rcvd}; " f"total_sent={total_sent}; " f"frame_loss={total_lost}; " f"target_duration='manual'; " f"approximated_duration='manual'; " f"approximated_rate='unknown'; " f"latency_stream_0(usec)=-1/-1/-1; " f"latency_stream_1(usec)=-1/-1/-1; ")
def main() -> int: # Initialize the argument parser and subparsers # First we initialize general arguments. parser = argparse.ArgumentParser(description="Linerate test control plane") parser.add_argument( "--server-addr", type=str, help="The server address", default="127.0.0.1", required=False, ) parser.add_argument( "--trex-config", type=str, help="The Trex config to be placed on the server.", required=True, ) parser.add_argument( "--keep-running", action="store_true", default=False, help="Keep Trex running after the test.", ) parser.add_argument( "--force-restart", action="store_true", default=False, help="Force restart the Trex process " + "if there is one running.", ) # Second, we initialize subparsers from all test scripts subparsers = parser.add_subparsers( dest="test", help="The test profile, which is the " + "filename(without .py) in the test directory", required=True, ) test_py_list = glob.glob(join(dirname(__file__), "tests", "*.py")) test_list = [ basename(f)[:-3] for f in test_py_list if isfile(f) and not f.endswith("__init__.py") ] for test in test_list: test_class = get_test_class(test) if not test_class: continue test_parser = subparsers.add_parser(test) test_class.setup_subparser(test_parser) # Finally, we get the arguments args = parser.parse_args() # Set up the Trex server if not os.path.exists(args.trex_config): logging.error("Can not find Trex config file: %s", args.trex_config) return if not os.path.isfile(args.trex_config): logging.error("%s is not a file", args.trex_config) return 1 trex_config_file_on_server = TREX_FILES_DIR + os.path.basename( args.trex_config) trex_daemon_client = CTRexClient(args.server_addr) trex_started = False try: logging.info("Pushing Trex config %s to the server", args.trex_config) if not trex_daemon_client.push_files(args.trex_config): logging.error("Unable to push %s to Trex server", args.trex_config) return 1 if args.force_restart: logging.info("Killing all Trexes... with meteorite... Boom!") trex_daemon_client.kill_all_trexes() # Wait until Trex enter the Idle state start_time = time.time() success = False while time.time() - start_time < DEFAULT_KILL_TIMEOUT: if trex_daemon_client.is_idle(): success = True break time.sleep(1) if not success: logging.error("Unable to kill Trex process, please login " + "to the server and kill it manually.") return 1 if not trex_daemon_client.is_idle(): logging.info("The Trex server process is running") logging.warning("A Trex server process is still running, " + "use --force-restart to kill it if necessary.") return 1 test_class = get_test_class(args.test) if not test_class: logging.error("Unable to get test class for test %s", args.test) return 1 test_type = test_class.test_type() logging.info("Starting Trex with %s mode", test_class.test_type()) try: start_trex_function = getattr(trex_daemon_client, "start_{}".format(test_type)) except AttributeError: logging.error("Unkonwon test type %s", test_type) return 1 # Not checking the return value from this # call since it always return True start_trex_function(cfg=trex_config_file_on_server) trex_started = True # Start the test if test_type == "stateless": trex_client = STLClient(server=args.server_addr) elif test_type == "astf": trex_client = ASTFClient(server=args.server_addr) else: logging.error("Unknown test type %s", test_type) return 1 test = test_class(trex_client) try: logging.info("Connecting to Trex server...") trex_client.connect() logging.info("Acquaring ports...") trex_client.acquire() logging.info("Resetting and clearing port...") trex_client.reset() # Resets configs from all ports trex_client.clear_stats() # Clear status from all ports logging.info("Running the test: %s...", test) test.start(args) except STLError as e: logging.error("Got error from Trex server: %s", e) return 1 finally: logging.info("Cleaning up Trex client") trex_client.stop() trex_client.release() trex_client.disconnect() except ConnectionRefusedError: logging.error( "Unable to connect to server %s.\n" + "Did you start the Trex daemon?", args.server_addr, ) return 1 except ProtocolError as pe: logging.error("%s", pe) return 1 except TRexError as te: logging.error("TRex error: %s", te.msg) return 1 except TRexInUseError as tiue: logging.error("TRex is already taken: %s", tiue.msg) return 1 except TRexRequestDenied as trd: logging.error("Request denied: %s", trd.msg) return 1 finally: if trex_started and not args.keep_running: logging.info("Stopping Trex server") trex_daemon_client.stop_trex()
def main(): parser = argparse.ArgumentParser(description="") parser.add_argument( "--ip_src", type=str, default="192.168.17.1", help="Source IP address for all packets in the stream.", ) parser.add_argument( "--ip_dst", type=str, default="192.168.17.2", help="Destination IP address for all packets in the stream.", ) # Due to different packet sizes, it is easier to keep the PPS fixed. # 0.25 Mpps -> about 3Gbps bit rate. parser.add_argument( "--pps", type=float, default=0.25, help="Transmit L1 rate in Mpps." ) # This is "configured" to give some space for power management ;) parser.add_argument( "--tot_pkts_burst", type=int, default=50 * 10 ** 3, help="Total number of packets in each single burst.", ) parser.add_argument( "--model", type=str, default="poisson", choices=["poisson", "pareto"], help="To be used traffic model.", ) # MARK: Currently NOT implemented. parser.add_argument( "--src_num", type=int, default=1, help="Number of flow sources." ) parser.add_argument( "--burst_num", type=int, default=100, help="The number of bursts in one test round.", ) parser.add_argument("--test", action="store_true", help="Just used for debug.") parser.add_argument( "--out", type=str, default="", help="Stores file with given name" ) args = parser.parse_args() print(f"* The fastest reaction time of X-MEN: {X_MEN_REACTION_TIME} seconds.") print(f"* Traffic model: {args.model}") l3_data = {"ip_src": args.ip_src, "ip_dst": args.ip_dst} streams, flow_duration = get_streams( args.pps, args.burst_num, args.model, args.src_num, args.tot_pkts_burst, l3_data, args.test, ) if args.test: pprint.pp([s.to_json() for s in streams[:3]]) sys.exit(0) print(f"* Flow duration: {flow_duration} seconds.") try: client = STLClient() client.connect() tx_port, rx_port = init_ports(client) client.add_streams(streams, ports=[tx_port]) start_ts = time.time() client.clear_stats() client.start(ports=[tx_port], force=True) rx_delay_sec = flow_duration + 5 print(f"The estimated RX delay: {rx_delay_sec} seconds.") client.wait_on_traffic(rx_delay_ms=3000) # rx_delay_sec * 10 ** 3) end_ts = time.time() test_dur = end_ts - start_ts print(f"Total test duration: {test_dur} seconds") err_cntrs_results, latency_results = get_rx_stats( client, tx_port, rx_port, args.burst_num ) print("--- The latency results of all streams:") for m_burst in range(args.burst_num): # Include ISG duratio, packet size and time stamps into .json dump err_cntrs_results[m_burst]["isg"] = ISGS_SAVE[m_burst] err_cntrs_results[m_burst]["len"] = IP_TOT_LENS_SAVE[m_burst] err_cntrs_results[m_burst]["start_ts"] = start_ts err_cntrs_results[m_burst]["end_ts"] = end_ts print("Burst ", m_burst) print("ISG: ", ISGS_SAVE[m_burst]) print("Dropped: ", err_cntrs_results[m_burst]["dropped"]) print("Latency: ", latency_results[m_burst]["average"]) # print(err_cntrs_results[m_burst]) # print(latency_results[m_burst]) if args.out: savedir_latency = "/home/malte/malte/latency/" savedir_error = "/home/malte/malte/error/" if not os.path.exists(savedir_latency): os.mkdir(savedir_latency) if not os.path.exists(savedir_error): os.mkdir(savedir_error) savedir_latency += args.out + "_latency.json" savedir_error += args.out + "_error.json" print("\nResults: ", savedir_latency, ", ", savedir_error) save_rx_stats(err_cntrs_results, savedir_error, args.burst_num) save_rx_stats(latency_results, savedir_latency, args.burst_num) except STLError as error: print(error) finally: client.disconnect()
def simple_burst( profile_file, duration, framesize, rate, port_0, port_1, latency, async_start=False, traffic_directions=2, force=False, delay=0.0, ): """Send traffic and measure packet loss and latency. Procedure: - reads the given traffic profile with streams, - connects to the T-rex client, - resets the ports, - removes all existing streams, - adds streams from the traffic profile to the ports, - if the warm-up time is more than 0, sends the warm-up traffic, reads the statistics, - clears the statistics from the client, - starts the traffic, - waits for the defined time (or runs forever if async mode is defined), - stops the traffic, - reads and displays the statistics and - disconnects from the client. :param profile_file: A python module with T-rex traffic profile. :param framesize: Frame size. :param duration: Duration of traffic run in seconds (-1=infinite). :param rate: Traffic rate [percentage, pps, bps]. :param port_0: Port 0 on the traffic generator. :param port_1: Port 1 on the traffic generator. :param latency: With latency stats. :param async_start: Start the traffic and exit. :param traffic_directions: Bidirectional (2) or unidirectional (1) traffic. :param force: Force start regardless of ports state. :param delay: Sleep overhead [s]. :type profile_file: str :type framesize: int or str :type duration: float :type rate: str :type port_0: int :type port_1: int :type latency: bool :type async_start: bool :type traffic_directions: int :type force: bool :type delay: float """ client = None total_rcvd = 0 total_sent = 0 approximated_duration = 0.0 lost_a = 0 lost_b = 0 lat_a = u"-1/-1/-1/" lat_b = u"-1/-1/-1/" # Read the profile: try: print(f"### Profile file:\n{profile_file}") profile = STLProfile.load(profile_file, direction=0, port_id=0, framesize=framesize, rate=rate) streams = profile.get_streams() except STLError: print(f"Error while loading profile '{profile_file}'!") raise try: # Create the client: client = STLClient() # Connect to server: client.connect() # Prepare our ports (the machine has 0 <--> 1 with static route): client.reset(ports=[port_0, port_1]) client.remove_all_streams(ports=[port_0, port_1]) if u"macsrc" in profile_file: client.set_port_attr(ports=[port_0, port_1], promiscuous=True) if isinstance(framesize, int): last_stream_a = int((len(streams) - 2) / 2) last_stream_b = (last_stream_a * 2) client.add_streams(streams[0:last_stream_a], ports=[port_0]) if traffic_directions > 1: client.add_streams(streams[last_stream_a:last_stream_b], ports=[port_1]) elif isinstance(framesize, str): client.add_streams(streams[0:3], ports=[port_0]) if traffic_directions > 1: client.add_streams(streams[3:6], ports=[port_1]) if latency: try: if isinstance(framesize, int): client.add_streams(streams[last_stream_b], ports=[port_0]) if traffic_directions > 1: client.add_streams(streams[last_stream_b + 1], ports=[port_1]) elif isinstance(framesize, str): latency = False except STLError: # Disable latency if NIC does not support requested stream type print(u"##### FAILED to add latency streams #####") latency = False ports = [port_0] if traffic_directions > 1: ports.append(port_1) # Clear the stats before injecting: client.clear_stats() lost_a = 0 lost_b = 0 # Choose rate and start traffic: client.start( ports=ports, mult=rate, duration=duration, force=force, core_mask=STLClient.CORE_MASK_PIN, ) if async_start: # For async stop, we need to export the current snapshot. xsnap0 = client.ports[0].get_xstats().reference_stats print(f"Xstats snapshot 0: {xsnap0!r}") if traffic_directions > 1: xsnap1 = client.ports[1].get_xstats().reference_stats print(f"Xstats snapshot 1: {xsnap1!r}") else: time_start = time.monotonic() # wait_on_traffic fails if duration stretches by 30 seconds or more. # TRex has some overhead, wait some more. time.sleep(duration + delay) client.stop() time_stop = time.monotonic() approximated_duration = time_stop - time_start - delay # Read the stats after the traffic stopped (or time up). stats = client.get_stats() if client.get_warnings(): for warning in client.get_warnings(): print(warning) # Now finish the complete reset. client.reset() print(u"##### Statistics #####") print(json.dumps(stats, indent=4, separators=(u",", u": "))) lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"] if traffic_directions > 1: lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"] # Stats index is not a port number, but "pgid". if latency: lat_obj = stats[u"latency"][0][u"latency"] lat_a = fmt_latency(str(lat_obj[u"total_min"]), str(lat_obj[u"average"]), str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"])) if traffic_directions > 1: lat_obj = stats[u"latency"][1][u"latency"] lat_b = fmt_latency(str(lat_obj[u"total_min"]), str(lat_obj[u"average"]), str(lat_obj[u"total_max"]), str(lat_obj[u"hdrh"])) if traffic_directions > 1: total_sent = stats[0][u"opackets"] + stats[1][u"opackets"] total_rcvd = stats[0][u"ipackets"] + stats[1][u"ipackets"] else: total_sent = stats[port_0][u"opackets"] total_rcvd = stats[port_1][u"ipackets"] print(f"\npackets lost from {port_0} --> {port_1}: {lost_a} pkts") if traffic_directions > 1: print( f"packets lost from {port_1} --> {port_0}: {lost_b} pkts") except STLError: print(u"T-Rex STL runtime error!", file=sys.stderr) raise finally: if async_start: if client: client.disconnect(stop_traffic=False, release_ports=True) else: if client: client.disconnect() print(f"rate={rate!r}; " f"total_received={total_rcvd}; " f"total_sent={total_sent}; " f"frame_loss={lost_a + lost_b}; " f"target_duration={duration!r}; " f"approximated_duration={approximated_duration!r}; " f"latency_stream_0(usec)={lat_a}; " f"latency_stream_1(usec)={lat_b}; ")
def main(): global IP_TOT_LEN parser = argparse.ArgumentParser( description= "On/Off traffic of a deterministic and stateless stream profile.") parser.add_argument( "--ip_src", type=str, default="192.168.17.1", help="Source IP address for all packets in the stream.", ) parser.add_argument( "--ip_dst", type=str, default="192.168.17.2", help="Destination IP address for all packets in the stream.", ) parser.add_argument( "--max_bit_rate", type=float, default=1, help="Maximal bit rate (with the unit Gbps) of the underlying network.", ) parser.add_argument("--on_time", type=int, default=2, help="ON time in seconds.") parser.add_argument( "--init_off_on_ratio", type=float, default=0.5, help="Initial ratio between OFF and ON time.", ) parser.add_argument( "--iteration", type=int, default=1, help="Number of iterations for the ON state of each PPS.", ) parser.add_argument( "--numa_node", type=int, default=0, help="The NUMA node of cores used for TX and RX.", ) parser.add_argument("--test", action="store_true", help="Just used for debug.") parser.add_argument( "--out", type=str, default="", help= "The name of the output file, stored in /home/malte/malte/latency if given", ) parser.add_argument( "--ip_tot_len", type=int, default=IP_TOT_LEN, help="The IP total length of packets to be transmitted.", ) parser.add_argument( "--enable_second_flow", action="store_true", help="Enable the second flow, used to test two-vnf setup.", ) parser.add_argument( "--soft", action="store_true", help="Different overlap for sencond flow, it's easier to scale ;)", ) args = parser.parse_args() IP_TOT_LEN = args.ip_tot_len stream_params = create_stream_params( args.max_bit_rate, args.on_time, args.init_off_on_ratio, args.iteration, args.test, ) print("\n--- Initial stream parameters:") pprint.pp(stream_params) print() if args.enable_second_flow: print( "INFO: The second flow is enabled. Two flows share the physical link." ) # Simply reverse the link utilizations second_stream_params = copy.deepcopy(list(reversed(stream_params))) # Change ISG's of 2nd flow to 2s for s in second_stream_params: s["on_time"] = args.on_time - (args.on_time * 0.2) s["isg"] = s["isg"] + (args.on_time * 0.2) * 10**6 # Reset ISG of first stream of the 2nd flow back to 1 # -> they always start together then if args.soft: second_stream_params[0]["isg"] = 1 * 10**6 print("\n--- Updated stream parameters with the second flow:") pprint.pp(second_stream_params) # Does not work on the blackbox # core_mask = get_core_mask(args.numa_node) # print(f"The core mask for RX and TX: {hex(core_mask)}") if args.enable_second_flow: streams = create_streams_with_second_flow(stream_params, second_stream_params, args.ip_src, args.ip_dst) else: streams = create_streams(stream_params, args.ip_src, args.ip_dst) second_stream_params = None if args.test: pprint.pp([s.to_json() for s in streams]) import sys sys.exit(0) if args.enable_second_flow: RX_DELAY_S = (sum([s["on_time"] for s in stream_params])) / 2.0 + 3 else: RX_DELAY_S = sum([s["on_time"] for s in stream_params]) + 3 RX_DELAY_MS = 3 * 1000 # Time after last Tx to wait for the last packet at Rx side try: client = STLClient() client.connect() tx_port, rx_port = init_ports(client) client.add_streams(streams, ports=[tx_port]) # Start TX start_ts = time.time() client.clear_stats() # All cores in the core_mask is used by the tx_port and its adjacent # port, so it is the rx_port normally. # client.start(ports=[tx_port], core_mask=[core_mask], force=True) client.start(ports=[tx_port], force=True) print(f"The estimated RX delay: {RX_DELAY_MS / 1000} seconds.") client.wait_on_traffic(rx_delay_ms=RX_DELAY_MS) end_ts = time.time() test_dur = end_ts - start_ts print(f"Total test duration: {test_dur} seconds") # Check RX stats. # MARK: All latency results are in usec. err_cntrs_results, latency_results = get_rx_stats( client, tx_port, rx_port, stream_params, second_stream_params=second_stream_params, ) print("--- The latency results of all streams:") print(f"- Number of streams first flow: {len(latency_results[0])}") for index, _ in enumerate(stream_params): print(f"- Stream: {index}") # Add timestamps to .json dump to parse turbostat results later err_cntrs_results[0][index]["start_ts"] = start_ts err_cntrs_results[0][index]["end_ts"] = end_ts print(err_cntrs_results[0][index]) print(latency_results[0][index]) # Save stats as .json dump if args.out: savedir_latency = "/home/malte/malte/latency/flow1/" savedir_error = "/home/malte/malte/error/flow1/" if not os.path.exists(savedir_latency): os.mkdir(savedir_latency) if not os.path.exists(savedir_error): os.mkdir(savedir_error) savedir_latency += args.out + "_latency.json" savedir_error += args.out + "_error.json" print("\nResults: ", savedir_latency, ", ", savedir_error) save_rx_stats(err_cntrs_results[0], savedir_error, stream_params) save_rx_stats(latency_results[0], savedir_latency, stream_params) if second_stream_params is not None: print( f"\n\n- Number of streams second flow: {len(latency_results[1])}" ) for index, _ in enumerate(stream_params): print(f"- Stream: {index}") err_cntrs_results[1][index]["start_ts"] = start_ts err_cntrs_results[1][index]["end_ts"] = end_ts print(err_cntrs_results[1][index]) print(latency_results[1][index]) if args.out: savedir_latency = "/home/malte/malte/latency/flow2/" savedir_error = "/home/malte/malte/error/flow2/" if not os.path.exists(savedir_latency): os.mkdir(savedir_latency) if not os.path.exists(savedir_error): os.mkdir(savedir_error) savedir_latency += args.out + "_latency.json" savedir_error += args.out + "_error.json" print("\nResults: ", savedir_latency, ", ", savedir_error) save_rx_stats(err_cntrs_results[1], savedir_error, stream_params) save_rx_stats(latency_results[1], savedir_latency, stream_params) except STLError as error: print(error) finally: client.disconnect()
class TrexTest(BaseTest): """ Base test for setting up and tearing down TRex client instance for linerate tests. """ def setUp(self): super(TrexTest, self).setUp() trex_server_addr = testutils.test_param_get("trex_server_addr") self.trex_client = STLClient(server=trex_server_addr) self.trex_client.connect() self.trex_client.acquire() self.trex_client.reset() # Resets configs from all ports self.trex_client.clear_stats() # Clear status from all ports # Put all ports to promiscuous mode, otherwise they will drop all # incoming packets if the destination mac is not the port mac address. self.trex_client.set_port_attr(self.trex_client.get_all_ports(), promiscuous=True) def tearDown(self): print("Tearing down STLClient...") self.trex_client.stop() self.trex_client.release() self.trex_client.disconnect() super(TrexTest, self).tearDown()
class TRex(AbstractTrafficGenerator): """TRex traffic generator driver.""" LATENCY_PPS = 1000 CHAIN_PG_ID_MASK = 0x007F PORT_PG_ID_MASK = 0x0080 LATENCY_PG_ID_MASK = 0x0100 def __init__(self, traffic_client): """Trex driver.""" AbstractTrafficGenerator.__init__(self, traffic_client) self.client = None self.id = count() self.port_handle = [] self.chain_count = self.generator_config.service_chain_count self.rates = [] self.capture_id = None self.packet_list = [] def get_version(self): """Get the Trex version.""" return self.client.get_server_version() if self.client else '' def get_pg_id(self, port, chain_id): """Calculate the packet group IDs to use for a given port/stream type/chain_id. port: 0 or 1 chain_id: identifies to which chain the pg_id is associated (0 to 255) return: pg_id, lat_pg_id We use a bit mask to set up the 3 fields: 0x007F: chain ID (8 bits for a max of 128 chains) 0x0080: port bit 0x0100: latency bit """ pg_id = port * TRex.PORT_PG_ID_MASK | chain_id return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK def extract_stats(self, in_stats): """Extract stats from dict returned by Trex API. :param in_stats: dict as returned by TRex api """ utils.nan_replace(in_stats) # LOG.debug(in_stats) result = {} # port_handles should have only 2 elements: [0, 1] # so (1 - ph) will be the index for the far end port for ph in self.port_handle: stats = in_stats[ph] far_end_stats = in_stats[1 - ph] result[ph] = { 'tx': { 'total_pkts': cast_integer(stats['opackets']), 'total_pkt_bytes': cast_integer(stats['obytes']), 'pkt_rate': cast_integer(stats['tx_pps']), 'pkt_bit_rate': cast_integer(stats['tx_bps']) }, 'rx': { 'total_pkts': cast_integer(stats['ipackets']), 'total_pkt_bytes': cast_integer(stats['ibytes']), 'pkt_rate': cast_integer(stats['rx_pps']), 'pkt_bit_rate': cast_integer(stats['rx_bps']), # how many pkts were dropped in RX direction # need to take the tx counter on the far end port 'dropped_pkts': cast_integer( far_end_stats['opackets'] - stats['ipackets']) } } self.__combine_latencies(in_stats, result[ph]['rx'], ph) total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts'] result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec) result["flow_stats"] = in_stats["flow_stats"] result["latency"] = in_stats["latency"] return result def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx): """Extract the aggregated stats for a given chain. trex_stats: stats as returned by get_stats() if_stats: a list of 2 interface stats to update (port 0 and 1) latencies: a list of 2 Latency instances to update for this chain (port 0 and 1) latencies[p] is the latency for packets sent on port p if there are no latency streams, the Latency instances are not modified chain_idx: chain index of the interface stats The packet counts include normal and latency streams. Trex returns flows stats as follows: 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0}, 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'rx_bytes': {0: nan, 1: nan, 'total': nan}, 'rx_pkts': {0: 0, 1: 15001, 'total': 15001}, 'rx_pps': {0: 0, 1: 0, 'total': 0}, 'tx_bps': {0: 0, 1: 0, 'total': 0}, 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068}, 'tx_pkts': {0: 15001, 1: 0, 'total': 15001}, 'tx_pps': {0: 0, 1: 0, 'total': 0}}, 1: {'rx_bps': {0: 0, 1: 0, 'total': 0}, 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'rx_bytes': {0: nan, 1: nan, 'total': nan}, 'rx_pkts': {0: 0, 1: 15001, 'total': 15001}, 'rx_pps': {0: 0, 1: 0, 'total': 0}, 'tx_bps': {0: 0, 1: 0, 'total': 0}, 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068}, 'tx_pkts': {0: 15001, 1: 0, 'total': 15001}, 'tx_pps': {0: 0, 1: 0, 'total': 0}}, 128: {'rx_bps': {0: 0, 1: 0, 'total': 0}, 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'rx_bytes': {0: nan, 1: nan, 'total': nan}, 'rx_pkts': {0: 15001, 1: 0, 'total': 15001}, 'rx_pps': {0: 0, 1: 0, 'total': 0}, 'tx_bps': {0: 0, 1: 0, 'total': 0}, 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0}, 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068}, 'tx_pkts': {0: 0, 1: 15001, 'total': 15001}, 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc... the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the get_pg_id() method. packet counters for a given stream sent on port p are reported as: - tx_pkts[p] on port p - rx_pkts[1-p] on the far end port This is a tricky/critical counter transposition operation because the results are grouped by port (not by stream): tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0] rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0] tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1] rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1] or using a more generic formula: tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p] rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p] the second formula is equivalent to rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p] If there are latency streams, those same counters need to be added in the same way """ def get_latency(lval): try: return int(round(lval)) except ValueError: return 0 for ifs in if_stats: ifs.tx = ifs.rx = 0 for port in range(2): pg_id, lat_pg_id = self.get_pg_id(port, chain_idx) for pid in [pg_id, lat_pg_id]: try: pg_stats = trex_stats['flow_stats'][pid] if_stats[port].tx += pg_stats['tx_pkts'][port] if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port] except KeyError: pass try: lat = trex_stats['latency'][lat_pg_id]['latency'] # dropped_pkts += lat['err_cntrs']['dropped'] latencies[port].max_usec = get_latency(lat['total_max']) if math.isnan(lat['total_min']): latencies[port].min_usec = 0 latencies[port].avg_usec = 0 else: latencies[port].min_usec = get_latency(lat['total_min']) latencies[port].avg_usec = get_latency(lat['average']) # pick up the HDR histogram if present (otherwise will raise KeyError) latencies[port].hdrh = lat['hdrh'] except KeyError: pass def __combine_latencies(self, in_stats, results, port_handle): """Traverse TRex result dictionary and combines chosen latency stats. example of latency dict returned by trex (2 chains): 'latency': {256: {'err_cntrs': {'dropped': 0, 'dup': 0, 'out_of_order': 0, 'seq_too_high': 0, 'seq_too_low': 0}, 'latency': {'average': 26.5, 'hdrh': u'HISTFAAAAEx4nJNpmSgz...bFRgxi', 'histogram': {20: 303, 30: 320, 40: 300, 50: 73, 60: 4, 70: 1}, 'jitter': 14, 'last_max': 63, 'total_max': 63, 'total_min': 20}}, 257: {'err_cntrs': {'dropped': 0, 'dup': 0, 'out_of_order': 0, 'seq_too_high': 0, 'seq_too_low': 0}, 'latency': {'average': 29.75, 'hdrh': u'HISTFAAAAEV4nJN...CALilDG0=', 'histogram': {20: 261, 30: 431, 40: 3, 50: 80, 60: 225}, 'jitter': 23, 'last_max': 67, 'total_max': 67, 'total_min': 20}}, 384: {'err_cntrs': {'dropped': 0, 'dup': 0, 'out_of_order': 0, 'seq_too_high': 0, 'seq_too_low': 0}, 'latency': {'average': 18.0, 'hdrh': u'HISTFAAAADR4nJNpm...MjCwDDxAZG', 'histogram': {20: 987, 30: 14}, 'jitter': 0, 'last_max': 34, 'total_max': 34, 'total_min': 20}}, 385: {'err_cntrs': {'dropped': 0, 'dup': 0, 'out_of_order': 0, 'seq_too_high': 0, 'seq_too_low': 0}, 'latency': {'average': 19.0, 'hdrh': u'HISTFAAAADR4nJNpm...NkYmJgDdagfK', 'histogram': {20: 989, 30: 11}, 'jitter': 0, 'last_max': 38, 'total_max': 38, 'total_min': 20}}, 'global': {'bad_hdr': 0, 'old_flow': 0}}, """ total_max = 0 average = 0 total_min = float("inf") for chain_id in range(self.chain_count): try: _, lat_pg_id = self.get_pg_id(port_handle, chain_id) lat = in_stats['latency'][lat_pg_id]['latency'] # dropped_pkts += lat['err_cntrs']['dropped'] total_max = max(lat['total_max'], total_max) total_min = min(lat['total_min'], total_min) average += lat['average'] except KeyError: pass if total_min == float("inf"): total_min = 0 results['min_delay_usec'] = total_min results['max_delay_usec'] = total_max results['avg_delay_usec'] = int(average / self.chain_count) def _bind_vxlan(self): bind_layers(UDP, VXLAN, dport=4789) bind_layers(VXLAN, Ether) def _create_pkt(self, stream_cfg, l2frame_size): """Create a packet of given size. l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS) """ # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size frame_size = int(l2frame_size) - 4 vm_param = [] if stream_cfg['vxlan'] is True: self._bind_vxlan() encap_level = '1' pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac']) if stream_cfg['vtep_vlan'] is not None: pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan']) pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip']) pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789) pkt_base /= VXLAN(vni=stream_cfg['net_vni']) pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst']) # need to randomize the outer header UDP src port based on flow vxlan_udp_src_fv = STLVmFlowVar( name="vxlan_udp_src", min_value=1337, max_value=32767, size=2, op="random") vm_param = [vxlan_udp_src_fv, STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")] else: encap_level = '0' pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst']) if stream_cfg['vlan_tag'] is not None: pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag']) udp_args = {} if stream_cfg['udp_src_port']: udp_args['sport'] = int(stream_cfg['udp_src_port']) if stream_cfg['udp_dst_port']: udp_args['dport'] = int(stream_cfg['udp_dst_port']) pkt_base /= IP() / UDP(**udp_args) if stream_cfg['ip_addrs_step'] == 'random': src_fv = STLVmFlowVarRepeatableRandom( name="ip_src", min_value=stream_cfg['ip_src_addr'], max_value=stream_cfg['ip_src_addr_max'], size=4, seed=random.randint(0, 32767), limit=stream_cfg['ip_src_count']) dst_fv = STLVmFlowVarRepeatableRandom( name="ip_dst", min_value=stream_cfg['ip_dst_addr'], max_value=stream_cfg['ip_dst_addr_max'], size=4, seed=random.randint(0, 32767), limit=stream_cfg['ip_dst_count']) else: src_fv = STLVmFlowVar( name="ip_src", min_value=stream_cfg['ip_src_addr'], max_value=stream_cfg['ip_src_addr'], size=4, op="inc", step=stream_cfg['ip_addrs_step']) dst_fv = STLVmFlowVar( name="ip_dst", min_value=stream_cfg['ip_dst_addr'], max_value=stream_cfg['ip_dst_addr_max'], size=4, op="inc", step=stream_cfg['ip_addrs_step']) vm_param.extend([ src_fv, STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)), dst_fv, STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)) ]) for encap in range(int(encap_level), -1, -1): # Fixing the checksums for all encap levels vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap), l4_offset="UDP:{}".format(encap), l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)) pad = max(0, frame_size - len(pkt_base)) * 'x' return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param)) def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True, e2e=False): """Create a list of streams corresponding to a given chain and stream config. port: port where the streams originate (0 or 1) chain_id: the chain to which the streams are associated to stream_cfg: stream configuration l2frame: L2 frame size (including 4-byte FCS) or 'IMIX' latency: if True also create a latency stream e2e: True if performing "end to end" connectivity check """ streams = [] pg_id, lat_pg_id = self.get_pg_id(port, chain_id) if l2frame == 'IMIX': for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES): pkt = self._create_pkt(stream_cfg, l2_frame_size) if e2e: streams.append(STLStream(packet=pkt, mode=STLTXCont(pps=ratio))) else: if stream_cfg['vxlan'] is True: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id, vxlan=True), mode=STLTXCont(pps=ratio))) else: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id), mode=STLTXCont(pps=ratio))) if latency: # for IMIX, the latency packets have the average IMIX packet size pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE) else: l2frame_size = int(l2frame) pkt = self._create_pkt(stream_cfg, l2frame_size) if e2e: streams.append(STLStream(packet=pkt, mode=STLTXCont())) else: if stream_cfg['vxlan'] is True: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id, vxlan=True), mode=STLTXCont())) else: streams.append(STLStream(packet=pkt, flow_stats=STLFlowStats(pg_id=pg_id), mode=STLTXCont())) # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging # without vlan, the min l2 frame size is 64 # with vlan it is 68 # This only applies to the latency stream if latency and stream_cfg['vlan_tag'] and l2frame_size < 68: pkt = self._create_pkt(stream_cfg, 68) if latency: # TRex limitation: VXLAN skip is not supported for latency stream streams.append(STLStream(packet=pkt, flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id), mode=STLTXCont(pps=self.LATENCY_PPS))) return streams @timeout(5) def __connect(self, client): client.connect() def __connect_after_start(self): # after start, Trex may take a bit of time to initialize # so we need to retry a few times for it in xrange(self.config.generic_retry_count): try: time.sleep(1) self.client.connect() break except Exception as ex: if it == (self.config.generic_retry_count - 1): raise LOG.info("Retrying connection to TRex (%s)...", ex.message) def connect(self): """Connect to the TRex server.""" server_ip = self.generator_config.ip LOG.info("Connecting to TRex (%s)...", server_ip) # Connect to TRex server self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port, async_port=self.generator_config.zmq_pub_port) try: self.__connect(self.client) if server_ip == '127.0.0.1': config_updated = self.__check_config() if config_updated or self.config.restart: self.__restart() except (TimeoutError, STLError) as e: if server_ip == '127.0.0.1': self.__start_local_server() else: raise TrafficGeneratorException(e.message) ports = list(self.generator_config.ports) self.port_handle = ports # Prepare the ports self.client.reset(ports) # Read HW information from each port # this returns an array of dict (1 per port) """ Example of output for Intel XL710 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True, 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes', u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']}, u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE', u'description': u'Ethernet Controller XL710 for 40GbE QSFP+', 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-', u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match', 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0', 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off', 'layer_mode': 'Ethernet', u'numa': 0}, ...] """ self.port_info = self.client.get_port_info(ports) LOG.info('Connected to TRex') for id, port in enumerate(self.port_info): LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s', id, port['description'], port['speed'], port['src_mac'], port['pci_addr'], port['driver']) # Make sure the 2 ports have the same speed if self.port_info[0]['speed'] != self.port_info[1]['speed']: raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' % (self.port_info[0]['speed'], self.port_info[1]['speed'])) def __start_local_server(self): try: LOG.info("Starting TRex ...") self.__start_server() self.__connect_after_start() except (TimeoutError, STLError) as e: LOG.error('Cannot connect to TRex') LOG.error(traceback.format_exc()) logpath = '/tmp/trex.log' if os.path.isfile(logpath): # Wait for TRex to finish writing error message last_size = 0 for _ in xrange(self.config.generic_retry_count): size = os.path.getsize(logpath) if size == last_size: # probably not writing anymore break last_size = size time.sleep(1) with open(logpath, 'r') as f: message = f.read() else: message = e.message raise TrafficGeneratorException(message) def __start_server(self): server = TRexTrafficServer() server.run_server(self.generator_config) def __check_config(self): server = TRexTrafficServer() return server.check_config_updated(self.generator_config) def __restart(self): LOG.info("Restarting TRex ...") self.__stop_server() # Wait for server stopped for _ in xrange(self.config.generic_retry_count): time.sleep(1) if not self.client.is_connected(): LOG.info("TRex is stopped...") break self.__start_local_server() def __stop_server(self): if self.generator_config.ip == '127.0.0.1': ports = self.client.get_acquired_ports() LOG.info('Release ports %s and stopping TRex...', ports) try: if ports: self.client.release(ports=ports) self.client.server_shutdown() except STLError as e: LOG.warn('Unable to stop TRex. Error: %s', e) else: LOG.info('Using remote TRex. Unable to stop TRex') def resolve_arp(self): """Resolve all configured remote IP addresses. return: None if ARP failed to resolve for all IP addresses else a dict of list of dest macs indexed by port# the dest macs in the list are indexed by the chain id """ self.client.set_service_mode(ports=self.port_handle) LOG.info('Polling ARP until successful...') arp_dest_macs = {} for port, device in zip(self.port_handle, self.generator_config.devices): # there should be 1 stream config per chain stream_configs = device.get_stream_configs() chain_count = len(stream_configs) ctx = self.client.create_service_ctx(port=port) # all dest macs on this port indexed by chain ID dst_macs = [None] * chain_count dst_macs_count = 0 # the index in the list is the chain id if self.config.vxlan: arps = [ ServiceARP(ctx, src_ip=device.vtep_src_ip, dst_ip=device.vtep_dst_ip, vlan=device.vtep_vlan) for cfg in stream_configs ] else: arps = [ ServiceARP(ctx, src_ip=cfg['ip_src_tg_gw'], dst_ip=cfg['mac_discovery_gw'], # will be None if no vlan tagging vlan=cfg['vlan_tag']) for cfg in stream_configs ] for attempt in range(self.config.generic_retry_count): try: ctx.run(arps) except STLError: LOG.error(traceback.format_exc()) continue unresolved = [] for chain_id, mac in enumerate(dst_macs): if not mac: arp_record = arps[chain_id].get_record() if arp_record.dst_mac: dst_macs[chain_id] = arp_record.dst_mac dst_macs_count += 1 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s', port, chain_id, arp_record.src_ip, arp_record.dst_ip, arp_record.dst_mac) else: unresolved.append(arp_record.dst_ip) if dst_macs_count == chain_count: arp_dest_macs[port] = dst_macs LOG.info('ARP resolved successfully for port %s', port) break else: retry = attempt + 1 LOG.info('Retrying ARP for: %s (retry %d/%d)', unresolved, retry, self.config.generic_retry_count) if retry < self.config.generic_retry_count: time.sleep(self.config.generic_poll_sec) else: LOG.error('ARP timed out for port %s (resolved %d out of %d)', port, dst_macs_count, chain_count) break self.client.set_service_mode(ports=self.port_handle, enabled=False) if len(arp_dest_macs) == len(self.port_handle): return arp_dest_macs return None def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency): """Check if rate provided by user is above requirements. Applies only if latency is True.""" intf_speed = self.generator_config.intf_speed if latency: if bidirectional: mult = 2 total_rate = 0 for rate in rates: r = utils.convert_rates(l2frame_size, rate, intf_speed) total_rate += int(r['rate_pps']) else: mult = 1 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed) # rate must be enough for latency stream and at least 1 pps for base stream per chain required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult result = utils.convert_rates(l2frame_size, {'rate_pps': required_rate}, intf_speed * mult) result['result'] = total_rate >= required_rate return result return {'result': True} def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False): """Program all the streams in Trex server. l2frame_size: L2 frame size or IMIX rates: a list of 2 rates to run each direction each rate is a dict like {'rate_pps': '10kpps'} bidirectional: True if bidirectional latency: True if latency measurement is needed e2e: True if performing "end to end" connectivity check """ r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency) if not r['result']: raise TrafficGeneratorException( 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.' .format(pps=r['rate_pps'], bps=r['rate_bps'], load=r['rate_percent'])) # a dict of list of streams indexed by port# # in case of fixed size, has self.chain_count * 2 * 2 streams # (1 normal + 1 latency stream per direction per chain) # for IMIX, has self.chain_count * 2 * 4 streams # (3 normal + 1 latency stream per direction per chain) streamblock = {} for port in self.port_handle: streamblock[port] = [] stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices] self.rates = [utils.to_rate_str(rate) for rate in rates] for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)): streamblock[0].extend(self.generate_streams(self.port_handle[0], chain_id, fwd_stream_cfg, l2frame_size, latency=latency, e2e=e2e)) if len(self.rates) > 1: streamblock[1].extend(self.generate_streams(self.port_handle[1], chain_id, rev_stream_cfg, l2frame_size, latency=bidirectional and latency, e2e=e2e)) for port in self.port_handle: if self.config.vxlan: self.client.set_port_attr(ports=port, vxlan_fs=[4789]) else: self.client.set_port_attr(ports=port, vxlan_fs=None) self.client.add_streams(streamblock[port], ports=port) LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port) def clear_streamblock(self): """Clear all streams from TRex.""" self.rates = [] self.client.reset(self.port_handle) LOG.info('Cleared all existing streams') def get_stats(self): """Get stats from Trex.""" stats = self.client.get_stats() return self.extract_stats(stats) def get_macs(self): """Return the Trex local port MAC addresses. return: a list of MAC addresses indexed by the port# """ return [port['src_mac'] for port in self.port_info] def get_port_speed_gbps(self): """Return the Trex local port MAC addresses. return: a list of speed in Gbps indexed by the port# """ return [port['speed'] for port in self.port_info] def clear_stats(self): """Clear all stats in the traffic gneerator.""" if self.port_handle: self.client.clear_stats() def start_traffic(self): """Start generating traffic in all ports.""" for port, rate in zip(self.port_handle, self.rates): self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True) def stop_traffic(self): """Stop generating traffic.""" self.client.stop(ports=self.port_handle) def start_capture(self): """Capture all packets on both ports that are unicast to us.""" if self.capture_id: self.stop_capture() # Need to filter out unwanted packets so we do not end up counting # src MACs of frames that are not unicast to us src_mac_list = self.get_macs() bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1]) # ports must be set in service in order to enable capture self.client.set_service_mode(ports=self.port_handle) self.capture_id = self.client.start_capture(rx_ports=self.port_handle, bpf_filter=bpf_filter) def fetch_capture_packets(self): """Fetch capture packets in capture mode.""" if self.capture_id: self.packet_list = [] self.client.fetch_capture_packets(capture_id=self.capture_id['id'], output=self.packet_list) def stop_capture(self): """Stop capturing packets.""" if self.capture_id: self.client.stop_capture(capture_id=self.capture_id['id']) self.capture_id = None self.client.set_service_mode(ports=self.port_handle, enabled=False) def cleanup(self): """Cleanup Trex driver.""" if self.client: try: self.client.reset(self.port_handle) self.client.disconnect() except STLError: # TRex does not like a reset while in disconnected state pass
def monitor_port_stats(c: STLClient) -> dict: """ List some port stats continuously while traffic is active :parameters: c: STLClient TRex stateless client to continuously grab statistics from """ ports = [0, 1, 2, 3] results = { "duration": [], 0: { "rx_bps": [], "tx_bps": [], "rx_pps": [], "tx_pps": [] }, 1: { "rx_bps": [], "tx_bps": [], "rx_pps": [], "tx_pps": [] }, 2: { "rx_bps": [], "tx_bps": [], "rx_pps": [], "tx_pps": [] }, 3: { "rx_bps": [], "tx_bps": [], "rx_pps": [], "tx_pps": [] }, } prev = { 0: { "opackets": 0, "ipackets": 0, "obytes": 0, "ibytes": 0, "time": time.time(), }, 1: { "opackets": 0, "ipackets": 0, "obytes": 0, "ibytes": 0, "time": time.time(), }, 2: { "opackets": 0, "ipackets": 0, "obytes": 0, "ibytes": 0, "time": time.time(), }, 3: { "opackets": 0, "ipackets": 0, "obytes": 0, "ibytes": 0, "time": time.time(), }, } s_time = time.time() while c.is_traffic_active(): stats = c.get_stats(ports=ports) if not stats: break print("\nTRAFFIC RUNNING {:.2f} SEC".format(time.time() - s_time)) print("{:^4} | {:<10} | {:<10} | {:<10} | {:<10} |".format( "Port", "RX bps", "TX bps", "RX pps", "TX pps")) print("----------------------------------------------------------") for port in ports: opackets = stats[port]["opackets"] ipackets = stats[port]["ipackets"] obytes = stats[port]["obytes"] ibytes = stats[port]["ibytes"] time_diff = time.time() - prev[port]["time"] rx_bps = 8 * (ibytes - prev[port]["ibytes"]) / time_diff tx_bps = 8 * (obytes - prev[port]["obytes"]) / time_diff rx_pps = ipackets - prev[port]["ipackets"] / time_diff tx_pps = opackets - prev[port]["opackets"] / time_diff print("{:^4} | {:<10} | {:<10} | {:<10} | {:<10} |".format( port, to_readable(rx_bps, "bps"), to_readable(tx_bps, "bps"), to_readable(rx_pps, "pps"), to_readable(tx_pps, "pps"), )) results["duration"].append(time.time() - s_time) results[port]["rx_bps"].append(rx_bps) results[port]["tx_bps"].append(tx_bps) results[port]["rx_pps"].append(rx_pps) results[port]["tx_pps"].append(tx_pps) prev[port]["opackets"] = opackets prev[port]["ipackets"] = ipackets prev[port]["obytes"] = obytes prev[port]["ibytes"] = ibytes prev[port]["time"] = time.time() time.sleep(1) print("") return results
def main(): global PAYLOAD_SIZE parser = argparse.ArgumentParser( description= "On/Off traffic of a deterministic and stateless stream profile.") parser.add_argument( "--ip_src", type=str, default="192.168.17.1", help="Source IP address for all packets in the stream.", ) parser.add_argument( "--ip_dst", type=str, default="192.168.17.2", help="Destination IP address for all packets in the stream.", ) parser.add_argument( "--max_bit_rate", type=float, default=1, help="Maximal bit rate (with the unit Gbps) of the underlying network.", ) parser.add_argument("--on_time", type=int, default=2, help="ON time in seconds.") parser.add_argument( "--init_off_on_ratio", type=float, default=0.5, help="Initial ratio between OFF and ON time.", ) parser.add_argument( "--iteration", type=int, default=1, help="Number of iterations for the ON state of each PPS.", ) parser.add_argument( "--numa_node", type=int, default=0, help="The NUMA node of cores used for TX and RX.", ) parser.add_argument("--test", action="store_true", help="Just used for debug.") parser.add_argument( "--out", type=str, default="", help= "The name of the output file, stored in /home/malte/malte/latency if given", ) parser.add_argument( "--payload_size", type=int, default=PAYLOAD_SIZE, help="Payload size of the packets", ) args = parser.parse_args() PAYLOAD_SIZE = args.payload_size stream_params = create_stream_params( args.max_bit_rate, args.on_time, args.init_off_on_ratio, args.iteration, args.test, ) print("\n--- To be used stream parameters:") pprint.pp(stream_params) print() # Does not work on the blackbox # core_mask = get_core_mask(args.numa_node) # print(f"The core mask for RX and TX: {hex(core_mask)}") streams = create_streams(stream_params, args.ip_src, args.ip_dst) if args.test: pprint.pp(streams) pprint.pp([s.to_json() for s in streams]) RX_DELAY_S = sum([s["on_time"] for s in stream_params]) + 3 RX_DELAY_MS = 3 * 1000 # Time after last Tx to wait for the last packet at Rx side try: client = STLClient() client.connect() tx_port, rx_port = init_ports(client) client.add_streams(streams, ports=[tx_port]) # Start TX start_ts = time.time() client.clear_stats() # All cores in the core_mask is used by the tx_port and its adjacent # port, so it is the rx_port normally. # client.start(ports=[tx_port], core_mask=[core_mask], force=True) client.start(ports=[tx_port], force=True) print(f"The estimated RX delay: {RX_DELAY_MS / 1000} seconds.") client.wait_on_traffic(rx_delay_ms=RX_DELAY_MS) end_ts = time.time() test_dur = end_ts - start_ts print(f"Total test duration: {test_dur} seconds") # Check RX stats. # MARK: All latency results are in usec. # err_cntrs_results, latency_results = get_rx_stats( # client, tx_port, rx_port, stream_params # ) err_cntrs_results, latency_results, flow_results = get_rx_stats( client, tx_port, rx_port, stream_params) print("--- The latency results of all streams:") print(f"- Number of streams: {len(latency_results)}") for index, _ in enumerate(stream_params): print(f"- Stream: {index}") err_cntrs_results[index]["start_ts"] = start_ts err_cntrs_results[index]["end_ts"] = end_ts print(err_cntrs_results[index]) print(latency_results[index]) print(flow_results[index]) if args.out: savedir_latency = "/home/malte/malte/latency/" + args.out + "_latency.json" savedir_error = "/home/malte/malte/error/" + args.out + "_error.json" print("Results: ", savedir_latency, ", ", savedir_error) save_rx_stats(err_cntrs_results, savedir_error, stream_params) save_rx_stats(latency_results, savedir_latency, stream_params) except STLError as error: print(error) finally: client.disconnect()