1 # Copyright (c) 2016-2017 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.
17 from trex_stl_lib import api as Pkt
18 from trex_stl_lib import trex_stl_client
19 from trex_stl_lib import trex_stl_packet_builder_scapy
20 from trex_stl_lib import trex_stl_streams
22 from yardstick.network_services.traffic_profile import trex_traffic_profile
25 LOGGING = logging.getLogger(__name__)
30 class PortPgIDMap(object):
31 """Port and pg_id mapping class
33 "pg_id" is the identification STL library gives to each stream. In the
34 RFC2544Profile class, the traffic has a STLProfile per port, which contains
35 one or several streams, one per packet size defined in the IMIX test case
38 Example of port <-> pg_id map:
39 self._port_pg_id_map = {
47 self._last_port = None
48 self._port_pg_id_map = {}
50 def add_port(self, port):
51 self._last_port = port
52 self._port_pg_id_map[port] = []
54 def get_pg_ids(self, port):
55 return self._port_pg_id_map.get(port)
57 def increase_pg_id(self, port=None):
58 port = self._last_port if not port else port
61 pg_id_list = self._port_pg_id_map.get(port)
64 pg_id_list = self._port_pg_id_map[port]
66 pg_id_list.append(self._pg_id)
70 class RFC2544Profile(trex_traffic_profile.TrexProfile):
71 """TRex RFC2544 traffic profile"""
73 TOLERANCE_LIMIT = 0.05
75 def __init__(self, traffic_generator):
76 super(RFC2544Profile, self).__init__(traffic_generator)
78 self.rate = self.config.frame_rate
79 self.max_rate = self.config.frame_rate
81 self.drop_percent_max = 0
83 def register_generator(self, generator):
84 self.generator = generator
86 def stop_traffic(self, traffic_generator=None):
87 """"Stop traffic injection, reset counters and remove streams"""
88 if traffic_generator is not None and self.generator is None:
89 self.generator = traffic_generator
91 self.generator.client.stop()
92 self.generator.client.reset()
93 self.generator.client.remove_all_streams()
95 def execute_traffic(self, traffic_generator=None):
96 """Generate the stream and run traffic on the given ports
98 :param traffic_generator: (TrexTrafficGenRFC) traffic generator
99 :return ports: (list of int) indexes of ports
100 port_pg_id: (dict) port indexes and pg_id [1] map
101 [1] https://trex-tgn.cisco.com/trex/doc/cp_stl_docs/api/
102 profile_code.html#stlstream-modes
104 if traffic_generator is not None and self.generator is None:
105 self.generator = traffic_generator
107 port_pg_id = PortPgIDMap()
109 for vld_id, intfs in sorted(self.generator.networks.items()):
110 profile_data = self.params.get(vld_id)
113 if (vld_id.startswith(self.DOWNLINK) and
114 self.generator.rfc2544_helper.correlated_traffic):
117 port_num = int(self.generator.port_num(intf))
118 ports.append(port_num)
119 port_pg_id.add_port(port_num)
120 profile = self._create_profile(profile_data,
121 self.rate, port_pg_id)
122 self.generator.client.add_streams(profile, ports=[port_num])
124 self.generator.client.start(ports=ports,
125 duration=self.config.duration,
127 return ports, port_pg_id
129 def _create_profile(self, profile_data, rate, port_pg_id):
130 """Create a STL profile (list of streams) for a port"""
132 for packet_name in profile_data:
133 imix = (profile_data[packet_name].
134 get('outer_l2', {}).get('framesize'))
135 imix_data = self._create_imix_data(imix)
136 self._create_vm(profile_data[packet_name])
137 _streams = self._create_streams(imix_data, rate, port_pg_id)
138 streams.extend(_streams)
139 return trex_stl_streams.STLProfile(streams)
141 def _create_imix_data(self, imix):
142 """Generate the IMIX distribution for a STL profile
144 The input information is the framesize dictionary in a test case
145 traffic profile definition. E.g.:
155 This function normalizes the sum of framesize weights to 100 and
156 returns a dictionary of frame sizes in bytes and weight in percentage.
158 imix_count = {64: 25, 128: 75}
160 :param imix: (dict) IMIX size and weight
166 imix_count = {size.upper().replace('B', ''): int(weight)
167 for size, weight in imix.items()}
168 imix_sum = sum(imix_count.values())
170 imix_count = {64: 100}
173 weight_normalize = float(imix_sum) / 100
174 return {size: float(weight) / weight_normalize
175 for size, weight in imix_count.items()}
177 def _create_vm(self, packet_definition):
178 """Create the STL Raw instructions"""
179 self.ether_packet = Pkt.Ether()
180 self.ip_packet = Pkt.IP()
181 self.ip6_packet = None
182 self.udp_packet = Pkt.UDP()
183 self.udp[DST_PORT] = 'UDP.dport'
184 self.udp[SRC_PORT] = 'UDP.sport'
186 self.vm_flow_vars = []
187 outer_l2 = packet_definition.get('outer_l2')
188 outer_l3v4 = packet_definition.get('outer_l3v4')
189 outer_l3v6 = packet_definition.get('outer_l3v6')
190 outer_l4 = packet_definition.get('outer_l4')
192 self._set_outer_l2_fields(outer_l2)
194 self._set_outer_l3v4_fields(outer_l3v4)
196 self._set_outer_l3v6_fields(outer_l3v6)
198 self._set_outer_l4_fields(outer_l4)
199 self.trex_vm = trex_stl_packet_builder_scapy.STLScVmRaw(
202 def _create_single_packet(self, size=64):
204 ether_packet = self.ether_packet
205 ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
206 udp_packet = self.udp_packet
208 qinq_packet = self.qinq_packet
209 base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
211 base_pkt = ether_packet / ip_packet / udp_packet
212 pad = max(0, size - len(base_pkt)) * 'x'
213 return trex_stl_packet_builder_scapy.STLPktBuilder(
214 pkt=base_pkt / pad, vm=self.trex_vm)
216 def _create_streams(self, imix_data, rate, port_pg_id):
217 """Create a list of streams per packet size
219 The STL TX mode speed of the generated streams will depend on the frame
220 weight and the frame rate. Both the frame weight and the total frame
221 rate are normalized to 100. The STL TX mode speed, defined in
222 percentage, is the combitation of both percentages. E.g.:
225 --> STLTXmode percentage = 10 (%)
229 --> STLTXmode percentage = 40 (%)
231 :param imix_data: (dict) IMIX size and weight
232 :param rate: (float) normalized [0..100] total weight
233 :param pg_id: (PortPgIDMap) port / pg_id (list) map
236 for size, weight in ((int(size), float(weight)) for (size, weight)
237 in imix_data.items() if float(weight) > 0):
238 packet = self._create_single_packet(size)
239 pg_id = port_pg_id.increase_pg_id()
240 stl_flow = trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id)
241 mode = trex_stl_streams.STLTXCont(percentage=weight * rate / 100)
242 streams.append(trex_stl_client.STLStream(
243 packet=packet, flow_stats=stl_flow, mode=mode))
246 def get_drop_percentage(self, samples, tol_low, tol_high,
248 """Calculate the drop percentage and run the traffic"""
251 for sample in samples:
253 port['tx_throughput_fps'] for port in sample.values())
255 port['rx_throughput_fps'] for port in sample.values())
256 tx_rate_fps = round(float(tx_rate_fps) / len(samples), 2)
257 rx_rate_fps = round(float(rx_rate_fps) / len(samples), 2)
259 # TODO(esm): RFC2544 doesn't tolerate packet loss, why do we?
260 out_packets = sum(port['out_packets'] for port in samples[-1].values())
261 in_packets = sum(port['in_packets'] for port in samples[-1].values())
264 # https://tools.ietf.org/html/rfc2544#section-26.3
266 drop_percent = round(
267 (float(abs(out_packets - in_packets)) / out_packets) * 100, 5)
269 tol_high = tol_high if tol_high > self.TOLERANCE_LIMIT else tol_high
270 tol_low = tol_low if tol_low > self.TOLERANCE_LIMIT else tol_low
271 if drop_percent > tol_high:
272 self.max_rate = self.rate
273 elif drop_percent < tol_low:
274 self.min_rate = self.rate
276 # NOTE(ralonsoh): the test should finish here
278 last_rate = self.rate
279 self.rate = round(float(self.max_rate + self.min_rate) / 2.0, 5)
281 throughput = rx_rate_fps * 2 if correlated_traffic else rx_rate_fps
283 if drop_percent > self.drop_percent_max:
284 self.drop_percent_max = drop_percent
286 latency = {port_num: value['latency']
287 for port_num, value in samples[-1].items()}
290 'TxThroughput': tx_rate_fps,
291 'RxThroughput': rx_rate_fps,
292 'CurrentDropPercentage': drop_percent,
293 'Throughput': throughput,
294 'DropPercentage': self.drop_percent_max,