1 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # 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, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
14 """Driver module for TRex traffic generator."""
22 from itertools import count
23 from nfvbench.log import LOG
24 from nfvbench.traffic_server import TRexTrafficServer
25 from nfvbench.utils import cast_integer
26 from nfvbench.utils import timeout
27 from nfvbench.utils import TimeoutError
28 from traffic_base import AbstractTrafficGenerator
29 from traffic_base import TrafficGeneratorException
30 import traffic_utils as utils
31 from traffic_utils import IMIX_AVG_L2_FRAME_SIZE
32 from traffic_utils import IMIX_L2_SIZES
33 from traffic_utils import IMIX_RATIOS
35 # pylint: disable=import-error
36 from trex.common.services.trex_service_arp import ServiceARP
37 from trex.stl.api import bind_layers
38 from trex.stl.api import CTRexVmInsFixHwCs
39 from trex.stl.api import Dot1Q
40 from trex.stl.api import Ether
41 from trex.stl.api import FlagsField
42 from trex.stl.api import IP
43 from trex.stl.api import Packet
44 from trex.stl.api import STLClient
45 from trex.stl.api import STLError
46 from trex.stl.api import STLFlowLatencyStats
47 from trex.stl.api import STLFlowStats
48 from trex.stl.api import STLPktBuilder
49 from trex.stl.api import STLScVmRaw
50 from trex.stl.api import STLStream
51 from trex.stl.api import STLTXCont
52 from trex.stl.api import STLVmFixChecksumHw
53 from trex.stl.api import STLVmFlowVar
54 from trex.stl.api import STLVmFlowVarRepeatableRandom
55 from trex.stl.api import STLVmWrFlowVar
56 from trex.stl.api import ThreeBytesField
57 from trex.stl.api import UDP
58 from trex.stl.api import XByteField
61 # pylint: enable=import-error
66 _VXLAN_FLAGS = ['R' * 27] + ['I'] + ['R' * 5]
68 fields_desc = [FlagsField("flags", 0x08000000, 32, _VXLAN_FLAGS),
69 ThreeBytesField("vni", 0),
70 XByteField("reserved", 0x00)]
74 return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
76 class TRex(AbstractTrafficGenerator):
77 """TRex traffic generator driver."""
80 CHAIN_PG_ID_MASK = 0x007F
81 PORT_PG_ID_MASK = 0x0080
82 LATENCY_PG_ID_MASK = 0x0100
84 def __init__(self, traffic_client):
86 AbstractTrafficGenerator.__init__(self, traffic_client)
90 self.chain_count = self.generator_config.service_chain_count
92 self.capture_id = None
95 def get_version(self):
96 """Get the Trex version."""
97 return self.client.get_server_version() if self.client else ''
99 def get_pg_id(self, port, chain_id):
100 """Calculate the packet group IDs to use for a given port/stream type/chain_id.
103 chain_id: identifies to which chain the pg_id is associated (0 to 255)
104 return: pg_id, lat_pg_id
106 We use a bit mask to set up the 3 fields:
107 0x007F: chain ID (8 bits for a max of 128 chains)
111 pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
112 return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
114 def extract_stats(self, in_stats):
115 """Extract stats from dict returned by Trex API.
117 :param in_stats: dict as returned by TRex api
119 utils.nan_replace(in_stats)
120 # LOG.debug(in_stats)
123 # port_handles should have only 2 elements: [0, 1]
124 # so (1 - ph) will be the index for the far end port
125 for ph in self.port_handle:
127 far_end_stats = in_stats[1 - ph]
130 'total_pkts': cast_integer(stats['opackets']),
131 'total_pkt_bytes': cast_integer(stats['obytes']),
132 'pkt_rate': cast_integer(stats['tx_pps']),
133 'pkt_bit_rate': cast_integer(stats['tx_bps'])
136 'total_pkts': cast_integer(stats['ipackets']),
137 'total_pkt_bytes': cast_integer(stats['ibytes']),
138 'pkt_rate': cast_integer(stats['rx_pps']),
139 'pkt_bit_rate': cast_integer(stats['rx_bps']),
140 # how many pkts were dropped in RX direction
141 # need to take the tx counter on the far end port
142 'dropped_pkts': cast_integer(
143 far_end_stats['opackets'] - stats['ipackets'])
146 self.__combine_latencies(in_stats, result[ph]['rx'], ph)
148 total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
149 result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
150 result["flow_stats"] = in_stats["flow_stats"]
151 result["latency"] = in_stats["latency"]
154 def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
155 """Extract the aggregated stats for a given chain.
157 trex_stats: stats as returned by get_stats()
158 if_stats: a list of 2 interface stats to update (port 0 and 1)
159 latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
160 latencies[p] is the latency for packets sent on port p
161 if there are no latency streams, the Latency instances are not modified
162 chain_idx: chain index of the interface stats
164 The packet counts include normal and latency streams.
166 Trex returns flows stats as follows:
168 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
169 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
170 'rx_bytes': {0: nan, 1: nan, 'total': nan},
171 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
172 'rx_pps': {0: 0, 1: 0, 'total': 0},
173 'tx_bps': {0: 0, 1: 0, 'total': 0},
174 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
175 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
176 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
177 'tx_pps': {0: 0, 1: 0, 'total': 0}},
178 1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
179 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
180 'rx_bytes': {0: nan, 1: nan, 'total': nan},
181 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
182 'rx_pps': {0: 0, 1: 0, 'total': 0},
183 'tx_bps': {0: 0, 1: 0, 'total': 0},
184 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
185 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
186 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
187 'tx_pps': {0: 0, 1: 0, 'total': 0}},
188 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
189 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
190 'rx_bytes': {0: nan, 1: nan, 'total': nan},
191 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
192 'rx_pps': {0: 0, 1: 0, 'total': 0},
193 'tx_bps': {0: 0, 1: 0, 'total': 0},
194 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
195 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
196 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
197 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
199 the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
201 packet counters for a given stream sent on port p are reported as:
202 - tx_pkts[p] on port p
203 - rx_pkts[1-p] on the far end port
205 This is a tricky/critical counter transposition operation because
206 the results are grouped by port (not by stream):
207 tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
208 rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
209 tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
210 rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
212 or using a more generic formula:
213 tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
214 rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
216 the second formula is equivalent to
217 rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
219 If there are latency streams, those same counters need to be added in the same way
221 def get_latency(lval):
223 return int(round(lval))
229 for port in range(2):
230 pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
231 for pid in [pg_id, lat_pg_id]:
233 pg_stats = trex_stats['flow_stats'][pid]
234 if_stats[port].tx += pg_stats['tx_pkts'][port]
235 if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
239 lat = trex_stats['latency'][lat_pg_id]['latency']
240 # dropped_pkts += lat['err_cntrs']['dropped']
241 latencies[port].max_usec = get_latency(lat['total_max'])
242 if math.isnan(lat['total_min']):
243 latencies[port].min_usec = 0
244 latencies[port].avg_usec = 0
246 latencies[port].min_usec = get_latency(lat['total_min'])
247 latencies[port].avg_usec = get_latency(lat['average'])
251 def __combine_latencies(self, in_stats, results, port_handle):
252 """Traverse TRex result dictionary and combines chosen latency stats."""
255 total_min = float("inf")
256 for chain_id in range(self.chain_count):
258 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
259 lat = in_stats['latency'][lat_pg_id]['latency']
260 # dropped_pkts += lat['err_cntrs']['dropped']
261 total_max = max(lat['total_max'], total_max)
262 total_min = min(lat['total_min'], total_min)
263 average += lat['average']
266 if total_min == float("inf"):
268 results['min_delay_usec'] = total_min
269 results['max_delay_usec'] = total_max
270 results['avg_delay_usec'] = int(average / self.chain_count)
272 def _bind_vxlan(self):
273 bind_layers(UDP, VXLAN, dport=4789)
274 bind_layers(VXLAN, Ether)
276 def _create_pkt(self, stream_cfg, l2frame_size):
277 """Create a packet of given size.
279 l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
281 # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
282 frame_size = int(l2frame_size) - 4
284 if stream_cfg['vxlan'] is True:
287 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
288 if stream_cfg['vtep_vlan'] is not None:
289 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
290 pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
291 pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
292 pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
293 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
296 pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
298 if stream_cfg['vlan_tag'] is not None:
299 pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
302 if stream_cfg['udp_src_port']:
303 udp_args['sport'] = int(stream_cfg['udp_src_port'])
304 if stream_cfg['udp_dst_port']:
305 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
306 pkt_base /= IP() / UDP(**udp_args)
308 if stream_cfg['ip_addrs_step'] == 'random':
309 src_fv = STLVmFlowVarRepeatableRandom(
311 min_value=stream_cfg['ip_src_addr'],
312 max_value=stream_cfg['ip_src_addr_max'],
314 seed=random.randint(0, 32767),
315 limit=stream_cfg['ip_src_count'])
316 dst_fv = STLVmFlowVarRepeatableRandom(
318 min_value=stream_cfg['ip_dst_addr'],
319 max_value=stream_cfg['ip_dst_addr_max'],
321 seed=random.randint(0, 32767),
322 limit=stream_cfg['ip_dst_count'])
324 src_fv = STLVmFlowVar(
326 min_value=stream_cfg['ip_src_addr'],
327 max_value=stream_cfg['ip_src_addr'],
330 step=stream_cfg['ip_addrs_step'])
331 dst_fv = STLVmFlowVar(
333 min_value=stream_cfg['ip_dst_addr'],
334 max_value=stream_cfg['ip_dst_addr_max'],
337 step=stream_cfg['ip_addrs_step'])
341 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
343 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
344 STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level),
345 l4_offset="UDP:{}".format(encap_level),
346 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
348 pad = max(0, frame_size - len(pkt_base)) * 'x'
350 return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param))
352 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
354 """Create a list of streams corresponding to a given chain and stream config.
356 port: port where the streams originate (0 or 1)
357 chain_id: the chain to which the streams are associated to
358 stream_cfg: stream configuration
359 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
360 latency: if True also create a latency stream
361 e2e: True if performing "end to end" connectivity check
364 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
365 if l2frame == 'IMIX':
366 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
367 pkt = self._create_pkt(stream_cfg, l2_frame_size)
369 streams.append(STLStream(packet=pkt,
370 mode=STLTXCont(pps=ratio)))
372 if stream_cfg['vxlan'] is True:
373 streams.append(STLStream(packet=pkt,
374 flow_stats=STLFlowStats(pg_id=pg_id,
376 mode=STLTXCont(pps=ratio)))
378 streams.append(STLStream(packet=pkt,
379 flow_stats=STLFlowStats(pg_id=pg_id),
380 mode=STLTXCont(pps=ratio)))
383 # for IMIX, the latency packets have the average IMIX packet size
384 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
387 l2frame_size = int(l2frame)
388 pkt = self._create_pkt(stream_cfg, l2frame_size)
390 streams.append(STLStream(packet=pkt,
393 if stream_cfg['vxlan'] is True:
394 streams.append(STLStream(packet=pkt,
395 flow_stats=STLFlowStats(pg_id=pg_id,
399 streams.append(STLStream(packet=pkt,
400 flow_stats=STLFlowStats(pg_id=pg_id),
402 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
403 # without vlan, the min l2 frame size is 64
405 # This only applies to the latency stream
406 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
407 pkt = self._create_pkt(stream_cfg, 68)
410 # TRex limitation: VXLAN skip is not supported for latency stream
411 streams.append(STLStream(packet=pkt,
412 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
413 mode=STLTXCont(pps=self.LATENCY_PPS)))
417 def __connect(self, client):
420 def __connect_after_start(self):
421 # after start, Trex may take a bit of time to initialize
422 # so we need to retry a few times
423 for it in xrange(self.config.generic_retry_count):
426 self.client.connect()
428 except Exception as ex:
429 if it == (self.config.generic_retry_count - 1):
431 LOG.info("Retrying connection to TRex (%s)...", ex.message)
434 """Connect to the TRex server."""
435 server_ip = self.generator_config.ip
436 LOG.info("Connecting to TRex (%s)...", server_ip)
438 # Connect to TRex server
439 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
440 async_port=self.generator_config.zmq_pub_port)
442 self.__connect(self.client)
443 if server_ip == '127.0.0.1':
444 config_updated = self.__check_config()
445 if config_updated or self.config.restart:
447 except (TimeoutError, STLError) as e:
448 if server_ip == '127.0.0.1':
449 self.__start_local_server()
451 raise TrafficGeneratorException(e.message)
453 ports = list(self.generator_config.ports)
454 self.port_handle = ports
456 self.client.reset(ports)
457 # Read HW information from each port
458 # this returns an array of dict (1 per port)
460 Example of output for Intel XL710
461 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
462 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
463 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
464 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
465 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
466 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
467 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
468 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
469 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
470 'layer_mode': 'Ethernet', u'numa': 0}, ...]
472 self.port_info = self.client.get_port_info(ports)
473 LOG.info('Connected to TRex')
474 for id, port in enumerate(self.port_info):
475 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
476 id, port['description'], port['speed'], port['src_mac'],
477 port['pci_addr'], port['driver'])
478 # Make sure the 2 ports have the same speed
479 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
480 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
481 (self.port_info[0]['speed'],
482 self.port_info[1]['speed']))
484 def __start_local_server(self):
486 LOG.info("Starting TRex ...")
487 self.__start_server()
488 self.__connect_after_start()
489 except (TimeoutError, STLError) as e:
490 LOG.error('Cannot connect to TRex')
491 LOG.error(traceback.format_exc())
492 logpath = '/tmp/trex.log'
493 if os.path.isfile(logpath):
494 # Wait for TRex to finish writing error message
496 for _ in xrange(self.config.generic_retry_count):
497 size = os.path.getsize(logpath)
498 if size == last_size:
499 # probably not writing anymore
503 with open(logpath, 'r') as f:
507 raise TrafficGeneratorException(message)
509 def __start_server(self):
510 server = TRexTrafficServer()
511 server.run_server(self.generator_config)
513 def __check_config(self):
514 server = TRexTrafficServer()
515 return server.check_config_updated(self.generator_config)
518 LOG.info("Restarting TRex ...")
520 # Wait for server stopped
521 for _ in xrange(self.config.generic_retry_count):
523 if not self.client.is_connected():
524 LOG.info("TRex is stopped...")
526 self.__start_local_server()
528 def __stop_server(self):
529 if self.generator_config.ip == '127.0.0.1':
530 ports = self.client.get_acquired_ports()
531 LOG.info('Release ports %s and stopping TRex...', ports)
534 self.client.release(ports=ports)
535 self.client.server_shutdown()
536 except STLError as e:
537 LOG.warn('Unable to stop TRex. Error: %s', e)
539 LOG.info('Using remote TRex. Unable to stop TRex')
541 def resolve_arp(self):
542 """Resolve all configured remote IP addresses.
544 return: None if ARP failed to resolve for all IP addresses
545 else a dict of list of dest macs indexed by port#
546 the dest macs in the list are indexed by the chain id
548 self.client.set_service_mode(ports=self.port_handle)
549 LOG.info('Polling ARP until successful...')
551 for port, device in zip(self.port_handle, self.generator_config.devices):
552 # there should be 1 stream config per chain
553 stream_configs = device.get_stream_configs()
554 chain_count = len(stream_configs)
555 ctx = self.client.create_service_ctx(port=port)
556 # all dest macs on this port indexed by chain ID
557 dst_macs = [None] * chain_count
559 # the index in the list is the chain id
560 if self.config.vxlan:
563 src_ip=device.vtep_src_ip,
564 dst_ip=device.vtep_dst_ip,
565 vlan=device.vtep_vlan)
566 for cfg in stream_configs
571 src_ip=cfg['ip_src_tg_gw'],
572 dst_ip=cfg['mac_discovery_gw'],
573 # will be None if no vlan tagging
574 vlan=cfg['vlan_tag'])
575 for cfg in stream_configs
578 for attempt in range(self.config.generic_retry_count):
582 LOG.error(traceback.format_exc())
586 for chain_id, mac in enumerate(dst_macs):
588 arp_record = arps[chain_id].get_record()
589 if arp_record.dst_mac:
590 dst_macs[chain_id] = arp_record.dst_mac
592 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
595 arp_record.dst_ip, arp_record.dst_mac)
597 unresolved.append(arp_record.dst_ip)
598 if dst_macs_count == chain_count:
599 arp_dest_macs[port] = dst_macs
600 LOG.info('ARP resolved successfully for port %s', port)
604 LOG.info('Retrying ARP for: %s (retry %d/%d)',
605 unresolved, retry, self.config.generic_retry_count)
606 if retry < self.config.generic_retry_count:
607 time.sleep(self.config.generic_poll_sec)
609 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
615 self.client.set_service_mode(ports=self.port_handle, enabled=False)
616 if len(arp_dest_macs) == len(self.port_handle):
620 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
621 """Check if rate provided by user is above requirements. Applies only if latency is True."""
622 intf_speed = self.generator_config.intf_speed
628 r = utils.convert_rates(l2frame_size, rate, intf_speed)
629 total_rate += int(r['rate_pps'])
632 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
633 # rate must be enough for latency stream and at least 1 pps for base stream per chain
634 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
635 result = utils.convert_rates(l2frame_size,
636 {'rate_pps': required_rate},
638 result['result'] = total_rate >= required_rate
641 return {'result': True}
643 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
644 """Program all the streams in Trex server.
646 l2frame_size: L2 frame size or IMIX
647 rates: a list of 2 rates to run each direction
648 each rate is a dict like {'rate_pps': '10kpps'}
649 bidirectional: True if bidirectional
650 latency: True if latency measurement is needed
651 e2e: True if performing "end to end" connectivity check
653 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
655 raise TrafficGeneratorException(
656 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
657 .format(pps=r['rate_pps'],
659 load=r['rate_percent']))
660 # a dict of list of streams indexed by port#
661 # in case of fixed size, has self.chain_count * 2 * 2 streams
662 # (1 normal + 1 latency stream per direction per chain)
663 # for IMIX, has self.chain_count * 2 * 4 streams
664 # (3 normal + 1 latency stream per direction per chain)
666 for port in self.port_handle:
667 streamblock[port] = []
668 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
669 self.rates = [utils.to_rate_str(rate) for rate in rates]
670 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
671 streamblock[0].extend(self.generate_streams(self.port_handle[0],
677 if len(self.rates) > 1:
678 streamblock[1].extend(self.generate_streams(self.port_handle[1],
682 latency=bidirectional and latency,
685 for port in self.port_handle:
686 if self.config.vxlan:
687 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
689 self.client.set_port_attr(ports=port, vxlan_fs=None)
690 self.client.add_streams(streamblock[port], ports=port)
691 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
693 def clear_streamblock(self):
694 """Clear all streams from TRex."""
696 self.client.reset(self.port_handle)
697 LOG.info('Cleared all existing streams')
700 """Get stats from Trex."""
701 stats = self.client.get_stats()
702 return self.extract_stats(stats)
705 """Return the Trex local port MAC addresses.
707 return: a list of MAC addresses indexed by the port#
709 return [port['src_mac'] for port in self.port_info]
711 def get_port_speed_gbps(self):
712 """Return the Trex local port MAC addresses.
714 return: a list of speed in Gbps indexed by the port#
716 return [port['speed'] for port in self.port_info]
718 def clear_stats(self):
719 """Clear all stats in the traffic gneerator."""
721 self.client.clear_stats()
723 def start_traffic(self):
724 """Start generating traffic in all ports."""
725 for port, rate in zip(self.port_handle, self.rates):
726 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
728 def stop_traffic(self):
729 """Stop generating traffic."""
730 self.client.stop(ports=self.port_handle)
732 def start_capture(self):
733 """Capture all packets on both ports that are unicast to us."""
736 # Need to filter out unwanted packets so we do not end up counting
737 # src MACs of frames that are not unicast to us
738 src_mac_list = self.get_macs()
739 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
740 # ports must be set in service in order to enable capture
741 self.client.set_service_mode(ports=self.port_handle)
742 self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
743 bpf_filter=bpf_filter)
745 def fetch_capture_packets(self):
746 """Fetch capture packets in capture mode."""
748 self.packet_list = []
749 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
750 output=self.packet_list)
752 def stop_capture(self):
753 """Stop capturing packets."""
755 self.client.stop_capture(capture_id=self.capture_id['id'])
756 self.capture_id = None
757 self.client.set_service_mode(ports=self.port_handle, enabled=False)
760 """Cleanup Trex driver."""
763 self.client.reset(self.port_handle)
764 self.client.disconnect()
766 # TRex does not like a reset while in disconnected state