CSIT-102: Add latency measurement to performance testing
[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.03/scripts/automation/"+\
52                    "trex_control_plane/stl/")
53 from trex_stl_lib.api import *
54
55
56 def generate_payload(length):
57     """Generate payload.
58
59     :param length: Length of payload.
60     :type length: int
61     :return: Payload filled with chars.
62     :rtype string
63     """
64
65     word = ''
66     alphabet_size = len(string.letters)
67     for i in range(length):
68         word += string.letters[(i % alphabet_size)]
69
70     return word
71
72
73 def get_start_end_ipv6(start_ip, end_ip):
74     """Get start and end host from IPv6 as integer.
75
76     :param start_ip: Start IPv6.
77     :param end_ip: End IPv6.
78     :type start_ip: string
79     :type end_ip: string
80     :return: Start host, end host.
81     :rtype int
82     """
83
84     try:
85         ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
86         ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
87
88         hi1, lo1 = struct.unpack('!QQ', ip1)
89         hi2, lo2 = struct.unpack('!QQ', ip2)
90
91         if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
92             print "IPv6: start_ip is greater then end_ip"
93             sys.exit(2)
94
95         max_p1 = abs(int(lo1) - int(lo2)) + 1
96         base_p1 = lo1
97     except AddressValueError as ex_error:
98         print ex_error
99         sys.exit(2)
100
101     return base_p1, max_p1
102
103
104 def create_packets(traffic_options, frame_size=64):
105     """Create two IP packets to be used in stream.
106
107     :param traffic_options: Parameters for packets.
108     :param frame_size: Size of L2 frame.
109     :type traffic_options: list
110     :type frame_size: int
111     :return: Packet instances.
112     :rtype: Tuple of STLPktBuilder
113     """
114
115     if frame_size < 64:
116         print_error("Packet min. size is 64B")
117         sys.exit(1)
118
119     fsize_no_fcs = frame_size - 4 # no FCS
120
121     p1_src_start_ip = traffic_options['p1_src_start_ip']
122     p1_src_end_ip = traffic_options['p1_src_end_ip']
123     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
124     p2_src_start_ip = traffic_options['p2_src_start_ip']
125     p2_src_end_ip = traffic_options['p2_src_end_ip']
126     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
127
128     base_pkt_a = Ether()/IP(src=p1_src_start_ip, dst=p1_dst_start_ip, proto=61)
129     base_pkt_b = Ether()/IP(src=p2_src_start_ip, dst=p2_dst_start_ip, proto=61)
130
131     # The following code applies raw instructions to packet (IP src increment).
132     # It splits the generated traffic by "ip_src" variable to cores and fix
133     # IPv4 header checksum.
134     vm1 = STLScVmRaw([STLVmFlowVar(name="src",
135                                    min_value=p1_src_start_ip,
136                                    max_value=p1_src_end_ip,
137                                    size=4, op="inc"),
138                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
139                       STLVmFixIpv4(offset="IP"),
140                      ], split_by_field="src")
141     # The following code applies raw instructions to packet (IP src increment).
142     # It splits the generated traffic by "ip_src" variable to cores and fix
143     # IPv4 header checksum.
144     vm2 = STLScVmRaw([STLVmFlowVar(name="src",
145                                    min_value=p2_src_start_ip,
146                                    max_value=p2_src_end_ip,
147                                    size=4, op="inc"),
148                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
149                       STLVmFixIpv4(offset="IP"),
150                      ], split_by_field="src")
151
152     pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
153         max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
154     pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
155         max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
156     lat_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
157         max(0, fsize_no_fcs-len(base_pkt_a))))
158     lat_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
159         max(0, fsize_no_fcs-len(base_pkt_b))))
160
161     return(pkt_a, pkt_b, lat_a, lat_b)
162
163
164 def create_packets_v6(traffic_options, frame_size=78):
165     """Create two IPv6 packets to be used in stream.
166
167     :param traffic_options: Parameters for packets.
168     :param frame_size: Size of L2 frame.
169     :type traffic_options: List
170     :type frame_size: int
171     :return: Packet instances.
172     :rtype: Tuple of STLPktBuilder
173     """
174
175     if frame_size < 78:
176         print "Packet min. size is 78B"
177         sys.exit(2)
178
179     fsize_no_fcs = frame_size - 4 # no FCS
180
181     p1_src_start_ip = traffic_options['p1_src_start_ip']
182     p1_src_end_ip = traffic_options['p1_src_end_ip']
183     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
184     p2_src_start_ip = traffic_options['p2_src_start_ip']
185     p2_src_end_ip = traffic_options['p2_src_end_ip']
186     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
187
188     base_p1, max_p1 = get_start_end_ipv6(p1_src_start_ip, p1_src_end_ip)
189     base_p2, max_p2 = get_start_end_ipv6(p2_src_start_ip, p2_src_end_ip)
190
191     base_pkt_a = Ether()/IPv6(src=p1_src_start_ip, dst=p1_dst_start_ip)
192     base_pkt_b = Ether()/IPv6(src=p2_src_start_ip, dst=p2_dst_start_ip)
193
194     # The following code applies raw instructions to packet (IP src increment).
195     # It splits the generated traffic by "ip_src" variable to cores
196     vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
197                                    min_value=base_p1,
198                                    max_value=max_p1+base_p1,
199                                    size=8, op="inc"),
200                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
201                                      offset_fixup=8)
202                      ]
203                      , split_by_field="ipv6_src")
204
205     # The following code applies raw instructions to packet (IP src increment).
206     # It splits the generated traffic by "ip_src" variable to cores
207     vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
208                                    min_value=base_p2,
209                                    max_value=max_p2+base_p2,
210                                    size=8, op="inc"),
211                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
212                                      offset_fixup=8)
213                      ]
214                      , split_by_field="ipv6_src")
215
216     pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
217         max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
218     pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
219         max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
220     lat_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
221         max(0, fsize_no_fcs-len(base_pkt_a))))
222     lat_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
223         max(0, fsize_no_fcs-len(base_pkt_b))))
224
225     return(pkt_a, pkt_b, lat_a, lat_b)
226
227
228 def simple_burst(pkt_a, pkt_b, pkt_lat_a, pkt_lat_b, duration, rate,
229                  warmup_time, async_start, latency):
230     """Run the traffic with specific parameters.
231
232     :param pkt_a: Base packet for stream 1.
233     :param pkt_b: Base packet for stream 2.
234     :param pkt_lat_a: Base packet for latency stream 1.
235     :param pkt_lat_b: Base packet for latency stream 2.
236     :param duration: Duration of traffic run in seconds (-1=infinite).
237     :param rate: Rate of traffic run [percentage, pps, bps].
238     :param warmup_time: Warm up duration.
239     :param async_start: Start the traffic and exit.
240     :param latency: With latency stats.
241     :type pkt_a: STLPktBuilder
242     :type pkt_b: STLPktBuilder
243     :type pkt_lat_a: STLPktBuilder
244     :type pkt_lat_b: STLPktBuilder
245     :type duration: int
246     :type rate: string
247     :type warmup_time: int
248     :type async_start: bool
249     :type latency: bool
250     :return: nothing
251     """
252
253     # create client
254     client = STLClient()
255
256     total_rcvd = 0
257     total_sent = 0
258     lost_a = 0
259     lost_b = 0
260     lat_a = 'NA'
261     lat_b = 'NA'
262
263     try:
264         # turn this off if too many logs
265         #client.set_verbose("high")
266
267         # connect to server
268         client.connect()
269
270         # prepare our ports (my machine has 0 <--> 1 with static route)
271         client.reset(ports=[0, 1])
272
273         # create two traffic streams without latency stats
274         stream1 = STLStream(packet=pkt_a,
275                             mode=STLTXCont(pps=1000))
276         # second traffic stream with a phase of 10ns (inter stream gap)
277         stream2 = STLStream(packet=pkt_b,
278                             isg=10.0,
279                             mode=STLTXCont(pps=1000))
280         client.add_streams(stream1, ports=[0])
281         client.add_streams(stream2, ports=[1])
282
283         if latency:
284             # create two traffic streams with latency stats
285             lat_stream1 = STLStream(packet=pkt_lat_a,
286                                     flow_stats=STLFlowLatencyStats(pg_id=0),
287                                     mode=STLTXCont(pps=1000))
288             # second traffic stream with a phase of 10ns (inter stream gap)
289             lat_stream2 = STLStream(packet=pkt_lat_b,
290                                     isg=10.0,
291                                     flow_stats=STLFlowLatencyStats(pg_id=1),
292                                     mode=STLTXCont(pps=1000))
293             client.add_streams(lat_stream1, ports=[0])
294             client.add_streams(lat_stream2, ports=[1])
295
296         #warmup phase
297         if warmup_time > 0:
298             # clear the stats before injecting
299             client.clear_stats()
300
301             # choose rate and start traffic
302             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
303
304             # block until done
305             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
306
307             if client.get_warnings():
308                 for warning in client.get_warnings():
309                     print_error(warning)
310
311             # read the stats after the test
312             stats = client.get_stats()
313
314             print "#####warmup statistics#####"
315             print json.dumps(stats, indent=4,
316                              separators=(',', ': '), sort_keys=True)
317             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
318             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
319
320             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
321             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
322
323         # clear the stats before injecting
324         client.clear_stats()
325         lost_a = 0
326         lost_b = 0
327
328         # choose rate and start traffic
329         client.start(ports=[0, 1], mult=rate, duration=duration)
330
331         if not async_start:
332             # block until done
333             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
334
335             if client.get_warnings():
336                 for warning in client.get_warnings():
337                     print_error(warning)
338
339             # read the stats after the test
340             stats = client.get_stats()
341
342             print "#####statistics#####"
343             print json.dumps(stats, indent=4,
344                              separators=(',', ': '), sort_keys=True)
345             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
346             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
347
348             if latency:
349                 lat_a = "/".join((\
350                     str(stats["latency"][0]["latency"]["total_min"]),\
351                     str(stats["latency"][0]["latency"]["average"]),\
352                     str(stats["latency"][0]["latency"]["total_max"])))
353                 lat_b = "/".join((\
354                     str(stats["latency"][1]["latency"]["total_min"]),\
355                     str(stats["latency"][1]["latency"]["average"]),\
356                     str(stats["latency"][1]["latency"]["total_max"])))
357
358             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
359             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
360
361             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
362             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
363
364     except STLError as ex_error:
365         print_error(str(ex_error))
366         sys.exit(1)
367
368     finally:
369         if async_start:
370             client.disconnect(stop_traffic=False, release_ports=True)
371         else:
372             client.disconnect()
373             print "rate={0}, totalReceived={1}, totalSent={2}, "\
374                 "frameLoss={3}, latencyStream0(usec)={4}, "\
375                 "latencyStream1(usec)={5}".format(rate, total_rcvd,\
376                 total_sent, lost_a+lost_b, lat_a, lat_b)
377
378
379 def print_error(msg):
380     """Print error message on stderr.
381
382     :param msg: Error message to print.
383     :type msg: string
384     :return: nothing
385     """
386
387     sys.stderr.write(msg+'\n')
388
389
390 def parse_args():
391     """Parse arguments from cmd line.
392
393     :return: Parsed arguments.
394     :rtype ArgumentParser
395     """
396
397     parser = argparse.ArgumentParser()
398     parser.add_argument("-d", "--duration", required=True, type=int,
399                         help="Duration of traffic run")
400     parser.add_argument("-s", "--frame_size", required=True, type=int,
401                         help="Size of a Frame without padding and IPG")
402     parser.add_argument("-r", "--rate", required=True,
403                         help="Traffic rate with included units (%, pps)")
404     parser.add_argument("-6", "--use_IPv6", action="store_true",
405                         default=False,
406                         help="Use IPv6 traffic profile instead of IPv4")
407     parser.add_argument("--async", action="store_true",
408                         default=False,
409                         help="Non-blocking call of the script")
410     parser.add_argument("--latency", action="store_true",
411                         default=False,
412                         help="Add latency stream")
413     parser.add_argument("-w", "--warmup_time", type=int,
414                         default=5,
415                         help="Traffic warmup time in seconds, 0 = disable")
416 #    parser.add_argument("--p1_src_mac",
417 #                        help="Port 1 source MAC address")
418 #    parser.add_argument("--p1_dst_mac",
419 #                        help="Port 1 destination MAC address")
420     parser.add_argument("--p1_src_start_ip", required=True,
421                         help="Port 1 source start IP address")
422     parser.add_argument("--p1_src_end_ip", required=True,
423                         help="Port 1 source end IP address")
424     parser.add_argument("--p1_dst_start_ip", required=True,
425                         help="Port 1 destination start IP address")
426 #    parser.add_argument("--p1_dst_end_ip",
427 #                        help="Port 1 destination end IP address")
428 #    parser.add_argument("--p2_src_mac",
429 #                        help="Port 2 source MAC address")
430 #    parser.add_argument("--p2_dst_mac",
431 #                        help="Port 2 destination MAC address")
432     parser.add_argument("--p2_src_start_ip", required=True,
433                         help="Port 2 source start IP address")
434     parser.add_argument("--p2_src_end_ip", required=True,
435                         help="Port 2 source end IP address")
436     parser.add_argument("--p2_dst_start_ip", required=True,
437                         help="Port 2 destination start IP address")
438 #    parser.add_argument("--p2_dst_end_ip",
439 #                        help="Port 2 destination end IP address")
440
441     return parser.parse_args()
442
443
444 def main():
445     """Main function."""
446
447     args = parse_args()
448
449     _duration = args.duration
450     _frame_size = args.frame_size
451     _rate = args.rate
452     _use_ipv6 = args.use_IPv6
453     _async_call = args.async
454     _latency = args.latency
455     _warmup_time = args.warmup_time
456
457     _traffic_options = {}
458     for attr in [a for a in dir(args) if a.startswith('p')]:
459         if getattr(args, attr) is not None:
460             _traffic_options[attr] = getattr(args, attr)
461
462     if _use_ipv6:
463         # WARNING: Trex limitation to IPv4 only. IPv6 is not yet supported.
464         print_error('IPv6 latency is not supported yet. Running without lat.')
465         _latency = False
466
467         pkt_a, pkt_b, lat_a, lat_b = create_packets_v6(_traffic_options,
468                                                        frame_size=_frame_size)
469     else:
470         pkt_a, pkt_b, lat_a, lat_b = create_packets(_traffic_options,
471                                                     frame_size=_frame_size)
472
473     simple_burst(pkt_a, pkt_b, lat_a, lat_b, _duration, _rate, _warmup_time,
474                  _async_call, _latency)
475
476 if __name__ == "__main__":
477     sys.exit(main())