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.common import constants
23 from yardstick.network_services.traffic_profile import trex_traffic_profile
26 LOGGING = logging.getLogger(__name__)
31 class PortPgIDMap(object):
32 """Port and pg_id mapping class
34 "pg_id" is the identification STL library gives to each stream. In the
35 RFC2544Profile class, the traffic has a STLProfile per port, which contains
36 one or several streams, one per packet size defined in the IMIX test case
39 Example of port <-> pg_id map:
40 self._port_pg_id_map = {
48 self._last_port = None
49 self._port_pg_id_map = {}
51 def add_port(self, port):
52 self._last_port = port
53 self._port_pg_id_map[port] = []
55 def get_pg_ids(self, port):
56 return self._port_pg_id_map.get(port, [])
58 def increase_pg_id(self, port=None):
59 port = self._last_port if not port else port
62 pg_id_list = self._port_pg_id_map.get(port)
65 pg_id_list = self._port_pg_id_map[port]
67 pg_id_list.append(self._pg_id)
71 class RFC2544Profile(trex_traffic_profile.TrexProfile):
72 """TRex RFC2544 traffic profile"""
74 TOLERANCE_LIMIT = 0.01
76 def __init__(self, traffic_generator):
77 super(RFC2544Profile, self).__init__(traffic_generator)
79 self.rate = self.config.frame_rate
80 self.max_rate = self.config.frame_rate
82 self.drop_percent_max = 0
84 def register_generator(self, generator):
85 self.generator = generator
87 def stop_traffic(self, traffic_generator=None):
88 """"Stop traffic injection, reset counters and remove streams"""
89 if traffic_generator is not None and self.generator is None:
90 self.generator = traffic_generator
92 self.generator.client.stop()
93 self.generator.client.reset()
94 self.generator.client.remove_all_streams()
96 def execute_traffic(self, traffic_generator=None):
97 """Generate the stream and run traffic on the given ports
99 :param traffic_generator: (TrexTrafficGenRFC) traffic generator
100 :return ports: (list of int) indexes of ports
101 port_pg_id: (dict) port indexes and pg_id [1] map
102 [1] https://trex-tgn.cisco.com/trex/doc/cp_stl_docs/api/
103 profile_code.html#stlstream-modes
105 if traffic_generator is not None and self.generator is None:
106 self.generator = traffic_generator
108 port_pg_id = PortPgIDMap()
110 for vld_id, intfs in sorted(self.generator.networks.items()):
111 profile_data = self.params.get(vld_id)
114 if (vld_id.startswith(self.DOWNLINK) and
115 self.generator.rfc2544_helper.correlated_traffic):
118 port_num = int(self.generator.port_num(intf))
119 ports.append(port_num)
120 port_pg_id.add_port(port_num)
121 profile = self._create_profile(profile_data,
122 self.rate, port_pg_id,
123 self.config.enable_latency)
124 self.generator.client.add_streams(profile, ports=[port_num])
126 self.generator.client.start(ports=ports,
127 duration=self.config.duration,
129 return ports, port_pg_id
131 def _create_profile(self, profile_data, rate, port_pg_id, enable_latency):
132 """Create a STL profile (list of streams) for a port"""
134 for packet_name in profile_data:
135 imix = (profile_data[packet_name].
136 get('outer_l2', {}).get('framesize'))
137 imix_data = self._create_imix_data(imix)
138 self._create_vm(profile_data[packet_name])
139 _streams = self._create_streams(imix_data, rate, port_pg_id,
141 streams.extend(_streams)
142 return trex_stl_streams.STLProfile(streams)
144 def _create_imix_data(self, imix,
145 weight_mode=constants.DISTRIBUTION_IN_PACKETS):
146 """Generate the IMIX distribution for a STL profile
148 The input information is the framesize dictionary in a test case
149 traffic profile definition. E.g.:
159 This function normalizes the sum of framesize weights to 100 and
160 returns a dictionary of frame sizes in bytes and weight in percentage.
162 imix_count = {64: 25, 128: 75}
164 The weight mode is described in [1]. There are two ways to describe the
165 weight of the packets:
166 - Distribution in packets: the weight defines the percentage of
167 packets sent per packet size. IXIA uses this definition.
168 - Distribution in bytes: the weight defines the percentage of bytes
169 sent per packet size.
171 Packet size # packets D. in packets Bytes D. in bytes
173 576 4 33.33% 2304 56%
174 1500 1 8.33% 1500 37%
176 [1] https://en.wikipedia.org/wiki/Internet_Mix
178 :param imix: (dict) IMIX size and weight
184 imix_count = {size.upper().replace('B', ''): int(weight)
185 for size, weight in imix.items()}
186 imix_sum = sum(imix_count.values())
188 imix_count = {64: 100}
191 weight_normalize = float(imix_sum) / 100
192 imix_dip = {size: float(weight) / weight_normalize
193 for size, weight in imix_count.items()}
195 if weight_mode == constants.DISTRIBUTION_IN_BYTES:
198 byte_total = sum([int(size) * weight
199 for size, weight in imix_dip.items()])
200 return {size: (int(size) * weight * 100) / byte_total
201 for size, weight in imix_dip.items()}
203 def _create_vm(self, packet_definition):
204 """Create the STL Raw instructions"""
205 self.ether_packet = Pkt.Ether()
206 self.ip_packet = Pkt.IP()
207 self.ip6_packet = None
208 self.udp_packet = Pkt.UDP()
209 self.udp[DST_PORT] = 'UDP.dport'
210 self.udp[SRC_PORT] = 'UDP.sport'
212 self.vm_flow_vars = []
213 outer_l2 = packet_definition.get('outer_l2')
214 outer_l3v4 = packet_definition.get('outer_l3v4')
215 outer_l3v6 = packet_definition.get('outer_l3v6')
216 outer_l4 = packet_definition.get('outer_l4')
218 self._set_outer_l2_fields(outer_l2)
220 self._set_outer_l3v4_fields(outer_l3v4)
222 self._set_outer_l3v6_fields(outer_l3v6)
224 self._set_outer_l4_fields(outer_l4)
225 self.trex_vm = trex_stl_packet_builder_scapy.STLScVmRaw(
228 def _create_single_packet(self, size=64):
230 ether_packet = self.ether_packet
231 ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
232 udp_packet = self.udp_packet
234 qinq_packet = self.qinq_packet
235 base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
237 base_pkt = ether_packet / ip_packet / udp_packet
238 pad = max(0, size - len(base_pkt)) * 'x'
239 return trex_stl_packet_builder_scapy.STLPktBuilder(
240 pkt=base_pkt / pad, vm=self.trex_vm)
242 def _create_streams(self, imix_data, rate, port_pg_id, enable_latency):
243 """Create a list of streams per packet size
245 The STL TX mode speed of the generated streams will depend on the frame
246 weight and the frame rate. Both the frame weight and the total frame
247 rate are normalized to 100. The STL TX mode speed, defined in
248 percentage, is the combitation of both percentages. E.g.:
251 --> STLTXmode percentage = 10 (%)
255 --> STLTXmode percentage = 40 (%)
257 :param imix_data: (dict) IMIX size and weight
258 :param rate: (float) normalized [0..100] total weight
259 :param pg_id: (PortPgIDMap) port / pg_id (list) map
262 for size, weight in ((int(size), float(weight)) for (size, weight)
263 in imix_data.items() if float(weight) > 0):
264 packet = self._create_single_packet(size)
265 pg_id = port_pg_id.increase_pg_id()
266 stl_flow = (trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) if
267 enable_latency else None)
268 mode = trex_stl_streams.STLTXCont(percentage=weight * rate / 100)
269 streams.append(trex_stl_client.STLStream(
270 packet=packet, flow_stats=stl_flow, mode=mode))
273 def get_drop_percentage(self, samples, tol_low, tol_high,
275 """Calculate the drop percentage and run the traffic"""
277 out_pkt_end = sum(port['out_packets'] for port in samples[-1].values())
278 in_pkt_end = sum(port['in_packets'] for port in samples[-1].values())
279 out_pkt_ini = sum(port['out_packets'] for port in samples[0].values())
280 in_pkt_ini = sum(port['in_packets'] for port in samples[0].values())
281 time_diff = (list(samples[-1].values())[0]['timestamp'] -
282 list(samples[0].values())[0]['timestamp']).total_seconds()
283 out_packets = out_pkt_end - out_pkt_ini
284 in_packets = in_pkt_end - in_pkt_ini
285 tx_rate_fps = float(out_packets) / time_diff
286 rx_rate_fps = float(in_packets) / time_diff
289 # https://tools.ietf.org/html/rfc2544#section-26.3
291 drop_percent = round(
292 (float(abs(out_packets - in_packets)) / out_packets) * 100, 5)
294 tol_high = max(tol_high, self.TOLERANCE_LIMIT)
295 tol_low = min(tol_low, self.TOLERANCE_LIMIT)
296 if drop_percent > tol_high:
297 self.max_rate = self.rate
298 elif drop_percent < tol_low:
299 self.min_rate = self.rate
303 last_rate = self.rate
304 self.rate = round(float(self.max_rate + self.min_rate) / 2.0, 5)
306 throughput = rx_rate_fps * 2 if correlated_traffic else rx_rate_fps
308 if drop_percent > self.drop_percent_max:
309 self.drop_percent_max = drop_percent
311 latency = {port_num: value['latency']
312 for port_num, value in samples[-1].items()}
315 'TxThroughput': tx_rate_fps,
316 'RxThroughput': rx_rate_fps,
317 'CurrentDropPercentage': drop_percent,
318 'Throughput': throughput,
319 'DropPercentage': self.drop_percent_max,
323 return completed, output