9870293739bbe21f24b77a306936b145b0cbf087
[yardstick.git] / yardstick / network_services / traffic_profile / rfc2544.py
1 # Copyright (c) 2016-2017 Intel Corporation
2 #
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
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 import logging
16
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
21
22 from yardstick.network_services.traffic_profile import trex_traffic_profile
23
24
25 LOGGING = logging.getLogger(__name__)
26 SRC_PORT = 'sport'
27 DST_PORT = 'dport'
28
29
30 class PortPgIDMap(object):
31     """Port and pg_id mapping class
32
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
36     description.
37
38     Example of port <-> pg_id map:
39         self._port_pg_id_map = {
40             0: [1, 2, 3, 4],
41             1: [5, 6, 7, 8]
42         }
43     """
44
45     def __init__(self):
46         self._pg_id = 0
47         self._last_port = None
48         self._port_pg_id_map = {}
49
50     def add_port(self, port):
51         self._last_port = port
52         self._port_pg_id_map[port] = []
53
54     def get_pg_ids(self, port):
55         return self._port_pg_id_map.get(port, [])
56
57     def increase_pg_id(self, port=None):
58         port = self._last_port if not port else port
59         if port is None:
60             return
61         pg_id_list = self._port_pg_id_map.get(port)
62         if not pg_id_list:
63             self.add_port(port)
64             pg_id_list = self._port_pg_id_map[port]
65         self._pg_id += 1
66         pg_id_list.append(self._pg_id)
67         return self._pg_id
68
69
70 class RFC2544Profile(trex_traffic_profile.TrexProfile):
71     """TRex RFC2544 traffic profile"""
72
73     TOLERANCE_LIMIT = 0.01
74
75     def __init__(self, traffic_generator):
76         super(RFC2544Profile, self).__init__(traffic_generator)
77         self.generator = None
78         self.rate = self.config.frame_rate
79         self.max_rate = self.config.frame_rate
80         self.min_rate = 0
81         self.drop_percent_max = 0
82
83     def register_generator(self, generator):
84         self.generator = generator
85
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
90
91         self.generator.client.stop()
92         self.generator.client.reset()
93         self.generator.client.remove_all_streams()
94
95     def execute_traffic(self, traffic_generator=None):
96         """Generate the stream and run traffic on the given ports
97
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
103         """
104         if traffic_generator is not None and self.generator is None:
105             self.generator = traffic_generator
106
107         port_pg_id = PortPgIDMap()
108         ports = []
109         for vld_id, intfs in sorted(self.generator.networks.items()):
110             profile_data = self.params.get(vld_id)
111             if not profile_data:
112                 continue
113             if (vld_id.startswith(self.DOWNLINK) and
114                     self.generator.rfc2544_helper.correlated_traffic):
115                 continue
116             for intf in intfs:
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.config.enable_latency)
123                 self.generator.client.add_streams(profile, ports=[port_num])
124
125         self.generator.client.start(ports=ports,
126                                     duration=self.config.duration,
127                                     force=True)
128         return ports, port_pg_id
129
130     def _create_profile(self, profile_data, rate, port_pg_id, enable_latency):
131         """Create a STL profile (list of streams) for a port"""
132         streams = []
133         for packet_name in profile_data:
134             imix = (profile_data[packet_name].
135                     get('outer_l2', {}).get('framesize'))
136             imix_data = self._create_imix_data(imix)
137             self._create_vm(profile_data[packet_name])
138             _streams = self._create_streams(imix_data, rate, port_pg_id,
139                                             enable_latency)
140             streams.extend(_streams)
141         return trex_stl_streams.STLProfile(streams)
142
143     def _create_imix_data(self, imix):
144         """Generate the IMIX distribution for a STL profile
145
146         The input information is the framesize dictionary in a test case
147         traffic profile definition. E.g.:
148           downlink_0:
149             ipv4:
150               id: 2
151                 outer_l2:
152                   framesize:
153                     64B: 10
154                     128B: 20
155                     ...
156
157         This function normalizes the sum of framesize weights to 100 and
158         returns a dictionary of frame sizes in bytes and weight in percentage.
159         E.g.:
160           imix_count = {64: 25, 128: 75}
161
162         :param imix: (dict) IMIX size and weight
163         """
164         imix_count = {}
165         if not imix:
166             return imix_count
167
168         imix_count = {size.upper().replace('B', ''): int(weight)
169                       for size, weight in imix.items()}
170         imix_sum = sum(imix_count.values())
171         if imix_sum <= 0:
172             imix_count = {64: 100}
173             imix_sum = 100
174
175         weight_normalize = float(imix_sum) / 100
176         return {size: float(weight) / weight_normalize
177                 for size, weight in imix_count.items()}
178
179     def _create_vm(self, packet_definition):
180         """Create the STL Raw instructions"""
181         self.ether_packet = Pkt.Ether()
182         self.ip_packet = Pkt.IP()
183         self.ip6_packet = None
184         self.udp_packet = Pkt.UDP()
185         self.udp[DST_PORT] = 'UDP.dport'
186         self.udp[SRC_PORT] = 'UDP.sport'
187         self.qinq = False
188         self.vm_flow_vars = []
189         outer_l2 = packet_definition.get('outer_l2')
190         outer_l3v4 = packet_definition.get('outer_l3v4')
191         outer_l3v6 = packet_definition.get('outer_l3v6')
192         outer_l4 = packet_definition.get('outer_l4')
193         if outer_l2:
194             self._set_outer_l2_fields(outer_l2)
195         if outer_l3v4:
196             self._set_outer_l3v4_fields(outer_l3v4)
197         if outer_l3v6:
198             self._set_outer_l3v6_fields(outer_l3v6)
199         if outer_l4:
200             self._set_outer_l4_fields(outer_l4)
201         self.trex_vm = trex_stl_packet_builder_scapy.STLScVmRaw(
202             self.vm_flow_vars)
203
204     def _create_single_packet(self, size=64):
205         size -= 4
206         ether_packet = self.ether_packet
207         ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
208         udp_packet = self.udp_packet
209         if self.qinq:
210             qinq_packet = self.qinq_packet
211             base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
212         else:
213             base_pkt = ether_packet / ip_packet / udp_packet
214         pad = max(0, size - len(base_pkt)) * 'x'
215         return trex_stl_packet_builder_scapy.STLPktBuilder(
216             pkt=base_pkt / pad, vm=self.trex_vm)
217
218     def _create_streams(self, imix_data, rate, port_pg_id, enable_latency):
219         """Create a list of streams per packet size
220
221         The STL TX mode speed of the generated streams will depend on the frame
222         weight and the frame rate. Both the frame weight and the total frame
223         rate are normalized to 100. The STL TX mode speed, defined in
224         percentage, is the combitation of both percentages. E.g.:
225           frame weight = 100
226           rate = 90
227             --> STLTXmode percentage = 10 (%)
228
229           frame weight = 80
230           rate = 50
231             --> STLTXmode percentage = 40 (%)
232
233         :param imix_data: (dict) IMIX size and weight
234         :param rate: (float) normalized [0..100] total weight
235         :param pg_id: (PortPgIDMap) port / pg_id (list) map
236         """
237         streams = []
238         for size, weight in ((int(size), float(weight)) for (size, weight)
239                              in imix_data.items() if float(weight) > 0):
240             packet = self._create_single_packet(size)
241             pg_id = port_pg_id.increase_pg_id()
242             stl_flow = (trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) if
243                         enable_latency else None)
244             mode = trex_stl_streams.STLTXCont(percentage=weight * rate / 100)
245             streams.append(trex_stl_client.STLStream(
246                 packet=packet, flow_stats=stl_flow, mode=mode))
247         return streams
248
249     def get_drop_percentage(self, samples, tol_low, tol_high,
250                             correlated_traffic):
251         """Calculate the drop percentage and run the traffic"""
252         completed = False
253         out_pkt_end = sum(port['out_packets'] for port in samples[-1].values())
254         in_pkt_end = sum(port['in_packets'] for port in samples[-1].values())
255         out_pkt_ini = sum(port['out_packets'] for port in samples[0].values())
256         in_pkt_ini = sum(port['in_packets'] for port in samples[0].values())
257         time_diff = (list(samples[-1].values())[0]['timestamp'] -
258                      list(samples[0].values())[0]['timestamp']).total_seconds()
259         out_packets = out_pkt_end - out_pkt_ini
260         in_packets = in_pkt_end - in_pkt_ini
261         tx_rate_fps = float(out_packets) / time_diff
262         rx_rate_fps = float(in_packets) / time_diff
263         drop_percent = 100.0
264
265         # https://tools.ietf.org/html/rfc2544#section-26.3
266         if out_packets:
267             drop_percent = round(
268                 (float(abs(out_packets - in_packets)) / out_packets) * 100, 5)
269
270         tol_high = max(tol_high, self.TOLERANCE_LIMIT)
271         tol_low = min(tol_low, self.TOLERANCE_LIMIT)
272         if drop_percent > tol_high:
273             self.max_rate = self.rate
274         elif drop_percent < tol_low:
275             self.min_rate = self.rate
276         else:
277             completed = True
278
279         last_rate = self.rate
280         self.rate = round(float(self.max_rate + self.min_rate) / 2.0, 5)
281
282         throughput = rx_rate_fps * 2 if correlated_traffic else rx_rate_fps
283
284         if drop_percent > self.drop_percent_max:
285             self.drop_percent_max = drop_percent
286
287         latency = {port_num: value['latency']
288                    for port_num, value in samples[-1].items()}
289
290         output = {
291             'TxThroughput': tx_rate_fps,
292             'RxThroughput': rx_rate_fps,
293             'CurrentDropPercentage': drop_percent,
294             'Throughput': throughput,
295             'DropPercentage': self.drop_percent_max,
296             'Rate': last_rate,
297             'Latency': latency
298         }
299         return completed, output