7b554ecb926699b9fad98b22f9d099eaa84c1a53
[vswitchperf.git] / tools / pkt_gen / trex / trex.py
1 # Copyright 2017 Martin Goldammer, OPNFV, Red Hat Inc.
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 """
16 Trex Traffic Generator Model
17 """
18 # pylint: disable=undefined-variable
19 import logging
20 import subprocess
21 import sys
22 from collections import OrderedDict
23 # pylint: disable=unused-import
24 import zmq
25 from conf import settings
26 from conf import merge_spec
27 from core.results.results_constants import ResultsConstants
28 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
29 # pylint: disable=wrong-import-position, import-error
30 sys.path.append(settings.getValue('PATHS')['trafficgen']['trex']['src']['path'])
31 from trex_stl_lib.api import *
32
33 _EMPTY_STATS = {
34     'global': {'bw_per_core': 0.0,
35                'cpu_util': 0.0,
36                'queue_full': 0.0,
37                'rx_bps': 0.0,
38                'rx_cpu_util': 0.0,
39                'rx_drop_bps': 0.0,
40                'rx_pps': 0.0,
41                'tx_bps': 0.0,
42                'tx_pps': 0.0,},
43     'latency': {},
44     'total': {'ibytes': 0.0,
45               'ierrors': 0.0,
46               'ipackets': 0.0,
47               'obytes': 0.0,
48               'oerrors': 0.0,
49               'opackets': 0.0,
50               'rx_bps': 0.0,
51               'rx_bps_L1': 0.0,
52               'rx_pps': 0.0,
53               'rx_util': 0.0,
54               'tx_bps': 0.0,
55               'tx_bps_L1': 0.0,
56               'tx_pps': 0.0,
57               'tx_util': 0.0,}}
58
59 class Trex(ITrafficGenerator):
60     """Trex Traffic generator wrapper."""
61     _logger = logging.getLogger(__name__)
62
63     def __init__(self):
64         """Trex class constructor."""
65         super().__init__()
66         self._logger.info("In trex __init__ method")
67         self._params = {}
68         self._trex_host_ip_addr = (
69             settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR'))
70         self._trex_base_dir = (
71             settings.getValue('TRAFFICGEN_TREX_BASE_DIR'))
72         self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER')
73         self._stlclient = None
74
75     def connect(self):
76         '''Connect to Trex traffic generator
77
78         Verify that Trex is on the system indicated by
79         the configuration file
80         '''
81         self._stlclient = STLClient()
82         self._logger.info("TREX:  In Trex connect method...")
83         if self._trex_host_ip_addr:
84             cmd_ping = "ping -c1 " + self._trex_host_ip_addr
85         else:
86             raise RuntimeError('TREX: Trex host not defined')
87
88         ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
89         output, error = ping.communicate()
90
91         if ping.returncode:
92             self._logger.error(error)
93             self._logger.error(output)
94             raise RuntimeError('TREX: Cannot ping Trex host at ' + \
95                                self._trex_host_ip_addr)
96
97         connect_trex = "ssh " + self._trex_user + \
98                           "@" + self._trex_host_ip_addr
99
100         cmd_find_trex = connect_trex + " ls " + \
101                           self._trex_base_dir + "t-rex-64"
102
103
104         find_trex = subprocess.Popen(cmd_find_trex,
105                                      shell=True,
106                                      stderr=subprocess.PIPE)
107         output, error = find_trex.communicate()
108
109         if find_trex.returncode:
110             self._logger.error(error)
111             self._logger.error(output)
112             raise RuntimeError(
113                 'TREX: Cannot locate Trex program at %s within %s' \
114                 % (self._trex_host_ip_addr, self._trex_base_dir))
115
116         self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
117                                     verbose_level=0)
118         self._stlclient.connect()
119         self._logger.info("TREX: Trex host successfully found...")
120
121     def disconnect(self):
122         """Disconnect from the traffic generator.
123
124         As with :func:`connect`, this function is optional.
125
126         Where implemented, this function should raise an exception on
127         failure.
128
129         :returns: None
130         """
131         self._logger.info("TREX: In trex disconnect method")
132         self._stlclient.disconnect(stop_traffic=True, release_ports=True)
133
134     @staticmethod
135     def create_packets(traffic, ports_info):
136         """Create base packet according to traffic specification.
137            If traffic haven't specified srcmac and dstmac fields
138            packet will be create with mac address of trex server.
139         """
140         mac_add = [li['hw_mac'] for li in ports_info]
141
142         if traffic and traffic['l2']['framesize'] > 0:
143             if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \
144                traffic['l2']['srcmac'] == '00:00:00:00:00:00':
145                 base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \
146                              IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'],
147                                 dst=traffic['l3']['dstip'])/ \
148                              UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
149                 base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \
150                              IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
151                                 dst=traffic['l3']['srcip'])/ \
152                              UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
153             else:
154                 base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \
155                              IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
156                                 dst=traffic['l3']['srcip'])/ \
157                              UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
158
159                 base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \
160                              IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
161                                 dst=traffic['l3']['srcip'])/ \
162                              UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
163
164         return (base_pkt_a, base_pkt_b)
165
166     @staticmethod
167     def create_streams(base_pkt_a, base_pkt_b, traffic):
168         """Add the base packet to the streams. Erase FCS and add payload
169            according to traffic specification
170         """
171         stream_1_lat = None
172         stream_2_lat = None
173         frame_size = int(traffic['l2']['framesize'])
174         fsize_no_fcs = frame_size - 4
175         payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x'
176         payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x'
177         pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a)
178         pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b)
179         stream_1 = STLStream(packet=pkt_a,
180                              name='stream_1',
181                              mode=STLTXCont(percentage=traffic['frame_rate']))
182         stream_2 = STLStream(packet=pkt_b,
183                              name='stream_2',
184                              mode=STLTXCont(percentage=traffic['frame_rate']))
185         lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS')
186         if lat_pps > 0:
187             stream_1_lat = STLStream(packet=pkt_a,
188                                      flow_stats=STLFlowLatencyStats(pg_id=0),
189                                      name='stream_1_lat',
190                                      mode=STLTXCont(pps=lat_pps))
191             stream_2_lat = STLStream(packet=pkt_b,
192                                      flow_stats=STLFlowLatencyStats(pg_id=1),
193                                      name='stream_2_lat',
194                                      mode=STLTXCont(pps=lat_pps))
195
196         return (stream_1, stream_2, stream_1_lat, stream_2_lat)
197
198     def generate_traffic(self, traffic, duration):
199         """The method that generate a stream
200         """
201         my_ports = [0, 1]
202         self._stlclient.reset(my_ports)
203         ports_info = self._stlclient.get_port_info(my_ports)
204         # for SR-IOV
205         if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'):
206             self._stlclient.set_port_attr(my_ports, promiscuous=True)
207
208         packet_1, packet_2 = Trex.create_packets(traffic, ports_info)
209         stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic)
210         self._stlclient.add_streams(stream_1, ports=[0])
211         self._stlclient.add_streams(stream_2, ports=[1])
212
213         if stream_1_lat is not None:
214             self._stlclient.add_streams(stream_1_lat, ports=[0])
215             self._stlclient.add_streams(stream_2_lat, ports=[1])
216
217         self._stlclient.clear_stats()
218         self._stlclient.start(ports=[0, 1], force=True, duration=duration)
219         self._stlclient.wait_on_traffic(ports=[0, 1])
220         stats = self._stlclient.get_stats(sync_now=True)
221         return stats
222
223     @staticmethod
224     def calculate_results(stats):
225         """Calculate results from Trex statistic
226         """
227         result = OrderedDict()
228         result[ResultsConstants.TX_RATE_FPS] = (
229             '{:.3f}'.format(
230                 float(stats["total"]["tx_pps"])))
231
232         result[ResultsConstants.THROUGHPUT_RX_FPS] = (
233             '{:.3f}'.format(
234                 float(stats["total"]["rx_pps"])))
235
236         result[ResultsConstants.TX_RATE_MBPS] = (
237             '{:.3f}'.format(
238                 float(stats["total"]["tx_bps"] / 1000000)))
239         result[ResultsConstants.THROUGHPUT_RX_MBPS] = (
240             '{:.3f}'.format(
241                 float(stats["total"]["rx_bps"] / 1000000)))
242
243         result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown'
244
245         result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown'
246         if stats["total"]["opackets"]:
247             result[ResultsConstants.FRAME_LOSS_PERCENT] = (
248                 '{:.3f}'.format(
249                     float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 /
250                           stats["total"]["opackets"])))
251         else:
252             result[ResultsConstants.FRAME_LOSS_PERCENT] = 100
253
254         if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']:
255             result[ResultsConstants.MIN_LATENCY_NS] = (
256                 '{:.3f}'.format(
257                     (float(min(stats["latency"][0]["latency"]["total_min"],
258                                stats["latency"][1]["latency"]["total_min"])))))
259
260             result[ResultsConstants.MAX_LATENCY_NS] = (
261                 '{:.3f}'.format(
262                     (float(max(stats["latency"][0]["latency"]["total_max"],
263                                stats["latency"][1]["latency"]["total_max"])))))
264
265             result[ResultsConstants.AVG_LATENCY_NS] = (
266                 '{:.3f}'.format(
267                     float((stats["latency"][0]["latency"]["average"]+
268                            stats["latency"][1]["latency"]["average"])/2)))
269
270         else:
271             result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
272             result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
273             result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
274         return result
275
276     def send_cont_traffic(self, traffic=None, duration=30):
277         """See ITrafficGenerator for description
278         """
279         self._logger.info("In Trex send_cont_traffic method")
280         self._params.clear()
281
282         self._params['traffic'] = self.traffic_defaults.copy()
283         if traffic:
284             self._params['traffic'] = merge_spec(
285                 self._params['traffic'], traffic)
286
287         stats = self.generate_traffic(traffic, duration)
288
289         return self.calculate_results(stats)
290
291     def start_cont_traffic(self, traffic=None, duration=30):
292         raise NotImplementedError(
293             'Trex start cont traffic not implemented')
294
295     def stop_cont_traffic(self):
296         """See ITrafficGenerator for description
297         """
298         raise NotImplementedError(
299             'Trex stop_cont_traffic method not implemented')
300
301     def send_rfc2544_throughput(self, traffic=None, duration=60,
302                                 lossrate=0.0, tests=10):
303         """See ITrafficGenerator for description
304         """
305         self._logger.info("In Trex send_rfc2544_throughput method")
306         self._params.clear()
307         threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD')
308         test_lossrate = 0
309         left = 0
310         iteration = 1
311         stats_ok = _EMPTY_STATS
312         self._params['traffic'] = self.traffic_defaults.copy()
313         if traffic:
314             self._params['traffic'] = merge_spec(
315                 self._params['traffic'], traffic)
316         new_params = copy.deepcopy(traffic)
317         stats = self.generate_traffic(traffic, duration)
318         right = traffic['frame_rate']
319         center = traffic['frame_rate']
320
321         # Loops until the preconfigured difference between frame rate
322         # of successful and unsuccessful iterations is reached
323         while (right - left) > threshold:
324             test_lossrate = ((stats["total"]["opackets"] - stats["total"]
325                               ["ipackets"]) * 100) / stats["total"]["opackets"]
326             self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s",
327                                iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'],
328                                "{:.3f}".format(test_lossrate))
329             if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']:
330                 stats_ok = copy.deepcopy(stats)
331                 break
332             elif test_lossrate > lossrate:
333                 right = center
334                 center = (left+right) / 2
335                 new_params = copy.deepcopy(traffic)
336                 new_params['frame_rate'] = center
337                 stats = self.generate_traffic(new_params, duration)
338             else:
339                 stats_ok = copy.deepcopy(stats)
340                 left = center
341                 center = (left+right) / 2
342                 new_params = copy.deepcopy(traffic)
343                 new_params['frame_rate'] = center
344                 stats = self.generate_traffic(new_params, duration)
345             iteration += 1
346         return self.calculate_results(stats_ok)
347
348     def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
349                                  lossrate=0.0):
350         raise NotImplementedError(
351             'Trex start rfc2544 throughput not implemented')
352
353     def wait_rfc2544_throughput(self):
354         raise NotImplementedError(
355             'Trex wait rfc2544 throughput not implemented')
356
357     def send_burst_traffic(self, traffic=None, numpkts=100, duration=5):
358         raise NotImplementedError(
359             'Trex send burst traffic not implemented')
360
361     def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
362                                lossrate=0.0):
363         raise NotImplementedError(
364             'Trex send rfc2544 back2back not implemented')
365
366     def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
367                                 lossrate=0.0):
368         raise NotImplementedError(
369             'Trex start rfc2544 back2back not implemented')
370
371     def wait_rfc2544_back2back(self):
372         raise NotImplementedError(
373             'Trex wait rfc2544 back2back not implemented')
374
375 if __name__ == "__main__":
376     pass