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