9ee34f6c22d395d2aa4cfe1abc0ce7373a662cb5
[csit.git] / resources / tools / t-rex / t-rex-stateless.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """This script uses T-REX stateless API to drive t-rex instance.
17
18 Requirements:
19 - T-REX: https://github.com/cisco-system-traffic-generator/trex-core
20  - compiled and running T-REX process (eg. ./t-rex-64 -i -c 4)
21  - trex_stl_lib.api library
22 - Script must be executed on a node with T-REX instance
23 - 2 interfaces must be configured in configuretion file /etc/trex_cfg.yaml
24
25 ##################### Example of /etc/trex_cfg.yaml ##########################
26 - port_limit      : 2 # numbers of ports to use
27   version         : 2
28   interfaces      : ["84:00.0","84:00.1"] # PCI address of interfaces
29   port_info       :  # set eth mac addr
30           - dest_mac        :   [0x90,0xe2,0xba,0x1f,0x97,0xd5]  # port 0
31             src_mac         :   [0x90,0xe2,0xba,0x1f,0x97,0xd4]
32           - dest_mac        :   [0x90,0xe2,0xba,0x1f,0x97,0xd4]  # port 1
33             src_mac         :   [0x90,0xe2,0xba,0x1f,0x97,0xd5]
34 ##############################################################################
35
36 Functionality:
37 1. Configure traffic on running T-REX instance
38 2. Clear statistics on all ports
39 3. Ctart traffic with specified duration
40 4. Print statistics to stdout
41
42 """
43
44 import argparse
45 import json
46 import socket
47 import string
48 import struct
49 import sys
50
51 sys.path.insert(0, "/opt/trex-core-2.06/scripts/automation/"+\
52                    "trex_control_plane/stl/")
53 from trex_stl_lib.api import *
54
55 stream_table = {'IMIX_v4_1': [{'size': 64, 'pps': 28, 'isg':0},
56                               {'size': 570, 'pps': 16, 'isg':0.1},
57                               {'size': 1518, 'pps': 4, 'isg':0.2}]
58                }
59
60 def generate_payload(length):
61     """Generate payload.
62
63     :param length: Length of payload.
64     :type length: int
65     :return: Payload filled with chars.
66     :rtype string
67     """
68
69     word = ''
70     alphabet_size = len(string.letters)
71     for i in range(length):
72         word += string.letters[(i % alphabet_size)]
73
74     return word
75
76
77 def get_start_end_ipv6(start_ip, end_ip):
78     """Get start and end host from IPv6 as integer.
79
80     :param start_ip: Start IPv6.
81     :param end_ip: End IPv6.
82     :type start_ip: string
83     :type end_ip: string
84     :return: Start host, end host.
85     :rtype int
86     """
87
88     try:
89         ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
90         ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
91
92         hi1, lo1 = struct.unpack('!QQ', ip1)
93         hi2, lo2 = struct.unpack('!QQ', ip2)
94
95         if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
96             print "IPv6: start_ip is greater then end_ip"
97             sys.exit(2)
98
99         max_p1 = abs(int(lo1) - int(lo2)) + 1
100         base_p1 = lo1
101     except AddressValueError as ex_error:
102         print ex_error
103         sys.exit(2)
104
105     return base_p1, max_p1
106
107 def create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size):
108     """Create STLStream streams
109
110     :param base_pkt_a: Base packet a for stream_a
111     :param base_pkt_b: Base packet b for stream_b
112     :param vm1: vm for stream_a
113     :param vm2: vm for stream_b
114     :param frame_size: frame size or name of traffic profile
115     :type base_pkt_a: Eth (scapy)
116     :type base_pkt_b: Eth (scapy)
117     :type vm1: STLScVmRaw
118     :type vm2: STLScVmRaw
119     :frame_size: int or string
120     :return: stream_a, stream_b, stream_a_latency, stream_b_latency
121     :rtype: STLStream, STLStream, STLStream, STLStream
122     """
123
124     if type(frame_size) is int:
125
126         fsize_no_fcs = frame_size - 4 # no FCS
127         pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
128             max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
129         pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
130             max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
131         pkt_lat_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
132             max(0, fsize_no_fcs-len(base_pkt_a))))
133         pkt_lat_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
134             max(0, fsize_no_fcs-len(base_pkt_b))))
135         lat_stream1 = STLStream(packet=pkt_lat_a,
136                                 flow_stats=STLFlowLatencyStats(pg_id=0),
137                                 mode=STLTXCont(pps=1000))
138         # second traffic stream with a phase of 10ns (inter stream gap)
139         lat_stream2 = STLStream(packet=pkt_lat_b,
140                                 isg=10.0,
141                                 flow_stats=STLFlowLatencyStats(pg_id=1),
142                                 mode=STLTXCont(pps=1000))
143
144         stream1 = STLStream(packet=pkt_a,
145                             mode=STLTXCont(pps=1000))
146         # second traffic stream with a phase of 10ns (inter stream gap)
147         stream2 = STLStream(packet=pkt_b,
148                             isg=10.0,
149                             mode=STLTXCont(pps=1000))
150     elif type(frame_size) is str:
151         lat_stream1 = []
152         lat_stream2 = []
153         stream1 = []
154         stream2 = []
155
156         for x in stream_table[frame_size]:
157             fsize_no_fcs = x['size'] - 4 # no FCS
158             pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
159                 max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
160             pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
161                 max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
162
163             stream1.append(STLStream(packet=pkt_a,
164                                isg=x['isg'],
165                                mode=STLTXCont(pps=x['pps'])))
166             stream2.append(STLStream(packet=pkt_b,
167                                isg=x['isg'],
168                                mode=STLTXCont(pps=x['pps'])))
169
170     else:
171         raise ValueError("Unknown frame_size type")
172
173     return (stream1, stream2, lat_stream1, lat_stream2)
174
175 def create_streams(traffic_options, frame_size=64):
176     """Create two IP packets to be used in stream.
177
178     :param traffic_options: Parameters for packets.
179     :param frame_size: Size of L2 frame.
180     :type traffic_options: list
181     :type frame_size: int
182     :return: Packet instances.
183     :rtype: Tuple of STLPktBuilder
184     """
185
186     if type(frame_size) is int and frame_size < 64:
187         print_error("Frame min. size is 64B")
188         sys.exit(1)
189
190     p1_src_start_ip = traffic_options['p1_src_start_ip']
191     p1_src_end_ip = traffic_options['p1_src_end_ip']
192     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
193     p2_src_start_ip = traffic_options['p2_src_start_ip']
194     p2_src_end_ip = traffic_options['p2_src_end_ip']
195     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
196
197     p1_dst_end_ip = traffic_options['p1_dst_end_ip']
198     p2_dst_end_ip = traffic_options['p2_dst_end_ip']
199
200     base_pkt_a = Ether()/IP(src=p1_src_start_ip, dst=p1_dst_start_ip, proto=61)
201     base_pkt_b = Ether()/IP(src=p2_src_start_ip, dst=p2_dst_start_ip, proto=61)
202
203     # The following code applies raw instructions to packet (IP src/dst
204     # increment). It splits the generated traffic by "ip_src"/"ip_dst" variable
205     # to cores and fix IPv4 header checksum.
206     if p1_dst_end_ip and p2_dst_end_ip:
207         vm1 = STLScVmRaw([STLVmFlowVar(name="dst",
208                                        min_value=p1_dst_start_ip,
209                                        max_value=p1_dst_end_ip,
210                                        size=4, op="inc"),
211                           STLVmWrFlowVar(fv_name="dst", pkt_offset="IP.dst"),
212                           STLVmFixIpv4(offset="IP"),
213                          ], split_by_field="dst")
214         vm2 = STLScVmRaw([STLVmFlowVar(name="dst",
215                                        min_value=p2_dst_start_ip,
216                                        max_value=p2_dst_end_ip,
217                                        size=4, op="inc"),
218                           STLVmWrFlowVar(fv_name="dst", pkt_offset="IP.dst"),
219                           STLVmFixIpv4(offset="IP"),
220                          ], split_by_field="dst")
221     else:
222         vm1 = STLScVmRaw([STLVmFlowVar(name="src",
223                                        min_value=p1_src_start_ip,
224                                        max_value=p1_src_end_ip,
225                                        size=4, op="inc"),
226                           STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
227                           STLVmFixIpv4(offset="IP"),
228                          ], split_by_field="src")
229         vm2 = STLScVmRaw([STLVmFlowVar(name="src",
230                                        min_value=p2_src_start_ip,
231                                        max_value=p2_src_end_ip,
232                                        size=4, op="inc"),
233                           STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
234                           STLVmFixIpv4(offset="IP"),
235                          ], split_by_field="src")
236
237     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
238
239
240 def create_streams_v6(traffic_options, frame_size=78):
241     """Create two IPv6 packets to be used in stream.
242
243     :param traffic_options: Parameters for packets.
244     :param frame_size: Size of L2 frame.
245     :type traffic_options: List
246     :type frame_size: int
247     :return: Packet instances.
248     :rtype: Tuple of STLPktBuilder
249     """
250
251     if type(frame_size) is int and frame_size < 78:
252         print_error("Frame min. size is 78B")
253         sys.exit(2)
254
255     p1_src_start_ip = traffic_options['p1_src_start_ip']
256     p1_src_end_ip = traffic_options['p1_src_end_ip']
257     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
258     p2_src_start_ip = traffic_options['p2_src_start_ip']
259     p2_src_end_ip = traffic_options['p2_src_end_ip']
260     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
261
262     base_p1, max_p1 = get_start_end_ipv6(p1_src_start_ip, p1_src_end_ip)
263     base_p2, max_p2 = get_start_end_ipv6(p2_src_start_ip, p2_src_end_ip)
264
265     base_pkt_a = Ether()/IPv6(src=p1_src_start_ip, dst=p1_dst_start_ip)
266     base_pkt_b = Ether()/IPv6(src=p2_src_start_ip, dst=p2_dst_start_ip)
267
268     # The following code applies raw instructions to packet (IP src increment).
269     # It splits the generated traffic by "ip_src" variable to cores
270     vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
271                                    min_value=base_p1,
272                                    max_value=max_p1+base_p1,
273                                    size=8, op="inc"),
274                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
275                                      offset_fixup=8)
276                      ]
277                      , split_by_field="ipv6_src")
278
279     # The following code applies raw instructions to packet (IP src increment).
280     # It splits the generated traffic by "ip_src" variable to cores
281     vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
282                                    min_value=base_p2,
283                                    max_value=max_p2+base_p2,
284                                    size=8, op="inc"),
285                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
286                                      offset_fixup=8)
287                      ]
288                      , split_by_field="ipv6_src")
289
290     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
291
292 def simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b, duration, rate,
293                  warmup_time, async_start, latency):
294     """Run the traffic with specific parameters.
295
296     :param pkt_a: Base packet for stream 1.
297     :param pkt_b: Base packet for stream 2.
298     :param pkt_lat_a: Base packet for latency stream 1.
299     :param pkt_lat_b: Base packet for latency stream 2.
300     :param duration: Duration of traffic run in seconds (-1=infinite).
301     :param rate: Rate of traffic run [percentage, pps, bps].
302     :param warmup_time: Warm up duration.
303     :param async_start: Start the traffic and exit.
304     :param latency: With latency stats.
305     :type pkt_a: STLPktBuilder
306     :type pkt_b: STLPktBuilder
307     :type pkt_lat_a: STLPktBuilder
308     :type pkt_lat_b: STLPktBuilder
309     :type duration: int
310     :type rate: string
311     :type warmup_time: int
312     :type async_start: bool
313     :type latency: bool
314     :return: nothing
315     """
316
317     # create client
318     client = STLClient()
319
320     total_rcvd = 0
321     total_sent = 0
322     lost_a = 0
323     lost_b = 0
324     lat_a = 'NA'
325     lat_b = 'NA'
326
327     try:
328         # turn this off if too many logs
329         #client.set_verbose("high")
330
331         # connect to server
332         client.connect()
333
334         # prepare our ports (my machine has 0 <--> 1 with static route)
335         client.reset(ports=[0, 1])
336
337         client.add_streams(stream_a, ports=[0])
338         client.add_streams(stream_b, ports=[1])
339
340         if latency:
341             client.add_streams(stream_lat_a, ports=[0])
342             client.add_streams(stream_lat_b, ports=[1])
343
344         #warmup phase
345         if warmup_time > 0:
346             # clear the stats before injecting
347             client.clear_stats()
348
349             # choose rate and start traffic
350             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
351
352             # block until done
353             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
354
355             if client.get_warnings():
356                 for warning in client.get_warnings():
357                     print_error(warning)
358
359             # read the stats after the test
360             stats = client.get_stats()
361
362             print "#####warmup statistics#####"
363             print json.dumps(stats, indent=4,
364                              separators=(',', ': '), sort_keys=True)
365             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
366             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
367
368             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
369             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
370
371         # clear the stats before injecting
372         client.clear_stats()
373         lost_a = 0
374         lost_b = 0
375
376         # choose rate and start traffic
377         client.start(ports=[0, 1], mult=rate, duration=duration)
378
379         if not async_start:
380             # block until done
381             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
382
383             if client.get_warnings():
384                 for warning in client.get_warnings():
385                     print_error(warning)
386
387             # read the stats after the test
388             stats = client.get_stats()
389
390             print "#####statistics#####"
391             print json.dumps(stats, indent=4,
392                              separators=(',', ': '), sort_keys=True)
393             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
394             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
395
396             if latency:
397                 lat_a = "/".join((\
398                     str(stats["latency"][0]["latency"]["total_min"]),\
399                     str(stats["latency"][0]["latency"]["average"]),\
400                     str(stats["latency"][0]["latency"]["total_max"])))
401                 lat_b = "/".join((\
402                     str(stats["latency"][1]["latency"]["total_min"]),\
403                     str(stats["latency"][1]["latency"]["average"]),\
404                     str(stats["latency"][1]["latency"]["total_max"])))
405
406             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
407             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
408
409             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
410             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
411
412     except STLError as ex_error:
413         print_error(str(ex_error))
414         sys.exit(1)
415
416     finally:
417         if async_start:
418             client.disconnect(stop_traffic=False, release_ports=True)
419         else:
420             client.disconnect()
421             print "rate={0}, totalReceived={1}, totalSent={2}, "\
422                 "frameLoss={3}, latencyStream0(usec)={4}, "\
423                 "latencyStream1(usec)={5}".format(rate, total_rcvd,\
424                 total_sent, lost_a+lost_b, lat_a, lat_b)
425
426
427 def print_error(msg):
428     """Print error message on stderr.
429
430     :param msg: Error message to print.
431     :type msg: string
432     :return: nothing
433     """
434
435     sys.stderr.write(msg+'\n')
436
437
438 def parse_args():
439     """Parse arguments from cmd line.
440
441     :return: Parsed arguments.
442     :rtype ArgumentParser
443     """
444
445     parser = argparse.ArgumentParser()
446     parser.add_argument("-d", "--duration", required=True, type=int,
447                         help="Duration of traffic run")
448     parser.add_argument("-s", "--frame_size", required=True,
449                         help="Size of a Frame without padding and IPG")
450     parser.add_argument("-r", "--rate", required=True,
451                         help="Traffic rate with included units (%, pps)")
452     parser.add_argument("-6", "--use_IPv6", action="store_true",
453                         default=False,
454                         help="Use IPv6 traffic profile instead of IPv4")
455     parser.add_argument("--async", action="store_true",
456                         default=False,
457                         help="Non-blocking call of the script")
458     parser.add_argument("--latency", action="store_true",
459                         default=False,
460                         help="Add latency stream")
461     parser.add_argument("-w", "--warmup_time", type=int,
462                         default=5,
463                         help="Traffic warmup time in seconds, 0 = disable")
464 #    parser.add_argument("--p1_src_mac",
465 #                        help="Port 1 source MAC address")
466 #    parser.add_argument("--p1_dst_mac",
467 #                        help="Port 1 destination MAC address")
468     parser.add_argument("--p1_src_start_ip", required=True,
469                         help="Port 1 source start IP address")
470     parser.add_argument("--p1_src_end_ip",
471                         default=False,
472                         help="Port 1 source end IP address")
473     parser.add_argument("--p1_dst_start_ip", required=True,
474                         help="Port 1 destination start IP address")
475     parser.add_argument("--p1_dst_end_ip",
476                         default=False,
477                         help="Port 1 destination end IP address")
478 #    parser.add_argument("--p2_src_mac",
479 #                        help="Port 2 source MAC address")
480 #    parser.add_argument("--p2_dst_mac",
481 #                        help="Port 2 destination MAC address")
482     parser.add_argument("--p2_src_start_ip", required=True,
483                         help="Port 2 source start IP address")
484     parser.add_argument("--p2_src_end_ip",
485                         default=False,
486                         help="Port 2 source end IP address")
487     parser.add_argument("--p2_dst_start_ip", required=True,
488                         help="Port 2 destination start IP address")
489     parser.add_argument("--p2_dst_end_ip",
490                         default=False,
491                         help="Port 2 destination end IP address")
492
493     return parser.parse_args()
494
495
496 def main():
497     """Main function."""
498
499     args = parse_args()
500
501     _duration = args.duration
502     _latency = args.latency
503     if args.frame_size.isdigit():
504         _frame_size = int(args.frame_size)
505     else:
506         _frame_size = args.frame_size
507         _latency = False
508     _rate = args.rate
509     _use_ipv6 = args.use_IPv6
510     _async_call = args.async
511     _warmup_time = args.warmup_time
512
513     _traffic_options = {}
514     for attr in [a for a in dir(args) if a.startswith('p')]:
515         if getattr(args, attr) is not None:
516             _traffic_options[attr] = getattr(args, attr)
517
518     if _use_ipv6:
519         # WARNING: Trex limitation to IPv4 only. IPv6 is not yet supported.
520         print_error('IPv6 latency is not supported yet. Running without lat.')
521         _latency = False
522
523         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams_v6(
524             _traffic_options, frame_size=_frame_size)
525     else:
526         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams(
527             _traffic_options, frame_size=_frame_size)
528
529     simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b,
530                  _duration, _rate, _warmup_time, _async_call, _latency)
531
532 if __name__ == "__main__":
533     sys.exit(main())