Perf: Bump T-Rex to 2.88
[csit.git] / GPL / tools / trex / trex_astf_profile.py
index ed0b8fc..3f244ec 100644 (file)
@@ -1,11 +1,21 @@
 #!/usr/bin/python3
 
-# Copyright (c) 2020 Cisco and/or its affiliates.
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
+# Copyright (c) 2021 Cisco and/or its affiliates.
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+#
+# Licensed under the Apache License 2.0 or
+# GNU General Public License v2.0 or later;  you may not use this file
+# except in compliance with one of these Licenses. You
+# may obtain a copy of the Licenses at:
 #
 #     http://www.apache.org/licenses/LICENSE-2.0
+#     https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
+#
+# Note: If this file is linked with Scapy, which is GPLv2+, your use of it
+# must be under GPLv2+.  If at any point in the future it is no longer linked
+# with Scapy (or other GPLv2+ licensed software), you are free to choose
+# Apache 2.
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,7 +25,7 @@
 
 """This module gets T-Rex advanced stateful (astf) traffic profile together
 with other parameters, reads the profile and sends the traffic. At the end, it
-measures the packet loss and latency.
+parses for various counters.
 """
 
 import argparse
@@ -24,7 +34,7 @@ import sys
 import time
 
 sys.path.insert(
-    0, u"/opt/trex-core-2.73/scripts/automation/trex_control_plane/interactive/"
+    0, u"/opt/trex-core-2.88/scripts/automation/trex_control_plane/interactive/"
 )
 from trex.astf.api import *
 
@@ -60,8 +70,16 @@ def fmt_latency(lat_min, lat_avg, lat_max, hdrh):
 
 
 def simple_burst(
-        profile_file, duration, framesize, mult, warmup_time, port_0, port_1,
-        latency, async_start=False, traffic_directions=2):
+        profile_file,
+        duration,
+        framesize,
+        multiplier,
+        port_0,
+        port_1,
+        latency,
+        async_start=False,
+        traffic_directions=2,
+    ):
     """Send traffic and measure packet loss and latency.
 
     Procedure:
@@ -70,20 +88,36 @@ def simple_burst(
      - resets the ports,
      - removes all existing streams,
      - adds streams from the traffic profile to the ports,
-     - if the warm-up time is more than 0, sends the warm-up traffic, reads the
-       statistics,
      - clears the statistics from the client,
      - starts the traffic,
      - waits for the defined time (or runs forever if async mode is defined),
-     - stops the traffic,
+     - explicitly stops the traffic,
      - reads and displays the statistics and
      - disconnects from the client.
 
+    Duration details:
+    Contrary to stateless mode, ASTF profiles typically limit the number
+    of flows/transactions that can happen.
+    The caller is expected to set the duration parameter accordingly to
+    this limit and multiplier, including any overheads.
+    See *_traffic_duration output fields for TRex's measurement
+    of the real traffic duration (should be without any inactivity overheads).
+    If traffic has not ended by the final time, the traffic
+    is stopped explicitly, counters reflect the state just after the stop.
+
+    TODO: Support tests which focus only on some transaction phases,
+    e.g. TCP tests ignoring init and teardown separated by delays.
+    Currently, approximated time measures the whole traffic duration.
+
     :param profile_file: A python module with T-rex traffic profile.
-    :param duration: Duration of traffic run in seconds (-1=infinite).
+    :param duration: Expected duration for all transactions to finish,
+        assuming only tolerable duration stretching happens.
+        This includes later start of later transactions
+        (according to TPS multiplier) and expected duration of each transaction.
+        Critically, this also includes any delay TRex shows when starting
+        traffic (but not the similar delay during stopping).
     :param framesize: Frame size.
-    :param mult: Multiplier of profile CPS.
-    :param warmup_time: Traffic warm-up time in seconds, 0 = disable.
+    :param multiplier: Multiplier of profile CPS.
     :param port_0: Port 0 on the traffic generator.
     :param port_1: Port 1 on the traffic generator.
     :param latency: With latency stats.
@@ -92,8 +126,7 @@ def simple_burst(
     :type profile_file: str
     :type duration: float
     :type framesize: int or str
-    :type mult: int
-    :type warmup_time: float
+    :type multiplier: int
     :type port_0: int
     :type port_1: int
     :type latency: bool
@@ -101,7 +134,7 @@ def simple_burst(
     :type traffic_directions: int
     """
     client = None
-    total_rcvd = 0
+    total_received = 0
     total_sent = 0
     lost_a = 0
     lost_b = 0
@@ -111,7 +144,6 @@ def simple_burst(
     lat_b_hist = u""
     l7_data = u""
     stats = dict()
-    stats_sampling = 1.0
     approximated_duration = 0
 
     # Read the profile.
@@ -139,37 +171,6 @@ def simple_burst(
         if traffic_directions > 1:
             ports.append(port_1)
 
-        # Warm-up phase.
-        if warmup_time > 0:
-            # Clear the stats before injecting.
-            client.clear_stats()
-            # Choose CPS and start traffic.
-            client.start(mult=mult, duration=warmup_time)
-            time_start = time.monotonic()
-
-            # Read the stats after the warmup duration (no sampling needed).
-            time.sleep(warmup_time)
-            stats[time.monotonic()-time_start] = client.get_stats()
-
-            if client.get_warnings():
-                for warning in client.get_warnings():
-                    print(warning)
-
-            client.reset()
-
-            print(u"##### Warmup Statistics #####")
-            print(json.dumps(stats, indent=4, separators=(u",", u": ")))
-
-            # TODO: check stats format
-            stats = stats[sorted(stats.keys())[-1]]
-            lost_a = stats[port_0][u"opackets"] - stats[port_1][u"ipackets"]
-            if traffic_directions > 1:
-                lost_b = stats[port_1][u"opackets"] - stats[port_0][u"ipackets"]
-
-            print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
-            if traffic_directions > 1:
-                print(f"packets lost from {port_1} --> {port_0}: {lost_b} pkts")
-
         # Clear the stats before injecting.
         lost_a = 0
         lost_b = 0
@@ -177,8 +178,13 @@ def simple_burst(
 
         # Choose CPS and start traffic.
         client.start(
-            mult=mult, duration=duration, nc=True,
-            latency_pps=mult if latency else 0, client_mask=2**len(ports)-1
+            mult=multiplier,
+            # Increase the input duration slightly,
+            # to ensure it does not end before sleep&stop below happens.
+            duration=duration + 0.1 if duration > 0 else duration,
+            nc=True,
+            latency_pps=int(multiplier) if latency else 0,
+            client_mask=2**len(ports)-1,
         )
         time_start = time.monotonic()
 
@@ -190,25 +196,27 @@ def simple_burst(
                 xsnap1 = client.ports[port_1].get_xstats().reference_stats
                 print(f"Xstats snapshot 1: {xsnap1!r}")
         else:
-            # Do not block until done.
-            while client.is_traffic_active(ports=ports):
-                time.sleep(
-                    stats_sampling if stats_sampling < duration else duration
-                )
-                # Sample the stats.
-                stats[time.monotonic()-time_start] = client.get_stats(
-                    ports=ports
-                )
-            else:
-                # Read the stats after the test
-                stats[time.monotonic()-time_start] = client.get_stats(
-                    ports=ports
-                )
+            time.sleep(duration)
+
+            # Do not block yet, the existing transactions may take long time
+            # to finish. We need an action that is almost reset(),
+            # but without clearing stats.
+            client.stop(block=False)
+            client.stop_latency()
+            client.remove_rx_queue(client.get_all_ports())
+            # Now we can wait for the real traffic stop.
+            client.stop(block=True)
+
+            # Read the stats after the traffic stopped (or time up).
+            stats[time.monotonic() - time_start] = client.get_stats(
+                ports=ports
+            )
 
             if client.get_warnings():
                 for warning in client.get_warnings():
                     print(warning)
 
+            # Now finish the complete reset.
             client.reset()
 
             print(u"##### Statistics #####")
@@ -241,61 +249,124 @@ def simple_burst(
             if traffic_directions > 1:
                 total_sent = \
                     stats[port_0][u"opackets"] + stats[port_1][u"opackets"]
-                total_rcvd = \
+                total_received = \
                     stats[port_0][u"ipackets"] + stats[port_1][u"ipackets"]
+                client_sent = stats[port_0][u"opackets"]
+                client_received = stats[port_0][u"ipackets"]
                 client_stats = stats[u"traffic"][u"client"]
                 server_stats = stats[u"traffic"][u"server"]
+                # Some zero counters are not sent
                 # Active and established flows UDP/TCP
                 # Client
                 c_act_flows = client_stats[u"m_active_flows"]
                 c_est_flows = client_stats[u"m_est_flows"]
-                l7_data = f"client_active_flows={c_act_flows}, "
-                l7_data += f"client_established_flows={c_est_flows}, "
+                c_traffic_duration = client_stats.get(u"m_traffic_duration", 0)
+                l7_data = f"client_active_flows={c_act_flows}; "
+                l7_data += f"client_established_flows={c_est_flows}; "
+                l7_data += f"client_traffic_duration={c_traffic_duration}; "
+                # Possible errors
+                # Too many packets in NIC rx queue
+                c_err_rx_throttled = client_stats.get(u"err_rx_throttled", 0)
+                l7_data += f"client_err_rx_throttled={c_err_rx_throttled}; "
+                # Number of client side flows that were not opened
+                # due to flow-table overflow
+                c_err_nf_throttled = client_stats.get(u"err_c_nf_throttled", 0)
+                l7_data += f"client_err_nf_throttled={c_err_nf_throttled}; "
+                # Too many flows
+                c_err_flow_overflow = client_stats.get(u"err_flow_overflow", 0)
+                l7_data += f"client_err_flow_overflow={c_err_flow_overflow}; "
                 # Server
                 s_act_flows = server_stats[u"m_active_flows"]
                 s_est_flows = server_stats[u"m_est_flows"]
-                l7_data += f"server_active_flows={s_act_flows}, "
-                l7_data += f"server_established_flows={s_est_flows}, "
-                # Some zero counters are not sent
+                s_traffic_duration = server_stats.get(u"m_traffic_duration", 0)
+                l7_data += f"server_active_flows={s_act_flows}; "
+                l7_data += f"server_established_flows={s_est_flows}; "
+                l7_data += f"server_traffic_duration={s_traffic_duration}; "
+                # Possible errors
+                # Too many packets in NIC rx queue
+                s_err_rx_throttled = server_stats.get(u"err_rx_throttled", 0)
+                l7_data += f"client_err_rx_throttled={s_err_rx_throttled}; "
                 if u"udp" in profile_file:
                     # Client
                     # Established connections
                     c_udp_connects = client_stats.get(u"udps_connects", 0)
-                    l7_data += f"client_udp_connects={c_udp_connects}, "
+                    l7_data += f"client_udp_connects={c_udp_connects}; "
                     # Closed connections
                     c_udp_closed = client_stats.get(u"udps_closed", 0)
-                    l7_data += f"client_udp_closed={c_udp_closed}, "
+                    l7_data += f"client_udp_closed={c_udp_closed}; "
+                    # Sent bytes
+                    c_udp_sndbyte = client_stats.get(u"udps_sndbyte", 0)
+                    l7_data += f"client_udp_tx_bytes={c_udp_sndbyte}; "
+                    # Sent packets
+                    c_udp_sndpkt = client_stats.get(u"udps_sndpkt", 0)
+                    l7_data += f"client_udp_tx_packets={c_udp_sndpkt}; "
+                    # Received bytes
+                    c_udp_rcvbyte = client_stats.get(u"udps_rcvbyte", 0)
+                    l7_data += f"client_udp_rx_bytes={c_udp_rcvbyte}; "
+                    # Received packets
+                    c_udp_rcvpkt = client_stats.get(u"udps_rcvpkt", 0)
+                    l7_data += f"client_udp_rx_packets={c_udp_rcvpkt}; "
+                    # Keep alive drops
+                    c_udp_keepdrops = client_stats.get(u"udps_keepdrops", 0)
+                    l7_data += f"client_udp_keep_drops={c_udp_keepdrops}; "
+                    # Client without flow
+                    c_err_cwf = client_stats.get(u"err_cwf", 0)
+                    l7_data += f"client_err_cwf={c_err_cwf}; "
                     # Server
                     # Accepted connections
                     s_udp_accepts = server_stats.get(u"udps_accepts", 0)
-                    l7_data += f"server_udp_accepts={s_udp_accepts}, "
+                    l7_data += f"server_udp_accepts={s_udp_accepts}; "
                     # Closed connections
                     s_udp_closed = server_stats.get(u"udps_closed", 0)
-                    l7_data += f"server_udp_closed={s_udp_closed}, "
+                    l7_data += f"server_udp_closed={s_udp_closed}; "
+                    # Sent bytes
+                    s_udp_sndbyte = server_stats.get(u"udps_sndbyte", 0)
+                    l7_data += f"server_udp_tx_bytes={s_udp_sndbyte}; "
+                    # Sent packets
+                    s_udp_sndpkt = server_stats.get(u"udps_sndpkt", 0)
+                    l7_data += f"server_udp_tx_packets={s_udp_sndpkt}; "
+                    # Received bytes
+                    s_udp_rcvbyte = server_stats.get(u"udps_rcvbyte", 0)
+                    l7_data += f"server_udp_rx_bytes={s_udp_rcvbyte}; "
+                    # Received packets
+                    s_udp_rcvpkt = server_stats.get(u"udps_rcvpkt", 0)
+                    l7_data += f"server_udp_rx_packets={s_udp_rcvpkt}; "
                 elif u"tcp" in profile_file:
                     # Client
-                    # Initiated connections
-                    c_tcp_connatt = client_stats.get(u"tcps_connattempt", 0)
-                    l7_data += f"client_tcp_connect_inits={c_tcp_connatt}, "
+                    # Connection attempts
+                    c_tcp_connattempt = client_stats.get(u"tcps_connattempt", 0)
+                    l7_data += f"client_tcp_connattempt={c_tcp_connattempt}; "
                     # Established connections
                     c_tcp_connects = client_stats.get(u"tcps_connects", 0)
-                    l7_data += f"client_tcp_connects={c_tcp_connects}, "
+                    l7_data += f"client_tcp_connects={c_tcp_connects}; "
                     # Closed connections
                     c_tcp_closed = client_stats.get(u"tcps_closed", 0)
-                    l7_data += f"client_tcp_closed={c_tcp_closed}, "
+                    l7_data += f"client_tcp_closed={c_tcp_closed}; "
+                    # Send bytes
+                    c_tcp_sndbyte = client_stats.get(u"tcps_sndbyte", 0)
+                    l7_data += f"client_tcp_tx_bytes={c_tcp_sndbyte}; "
+                    # Received bytes
+                    c_tcp_rcvbyte = client_stats.get(u"tcps_rcvbyte", 0)
+                    l7_data += f"client_tcp_rx_bytes={c_tcp_rcvbyte}; "
                     # Server
                     # Accepted connections
                     s_tcp_accepts = server_stats.get(u"tcps_accepts", 0)
-                    l7_data += f"server_tcp_accepts={s_tcp_accepts}, "
+                    l7_data += f"server_tcp_accepts={s_tcp_accepts}; "
                     # Established connections
                     s_tcp_connects = server_stats.get(u"tcps_connects", 0)
-                    l7_data += f"server_tcp_connects={s_tcp_connects}, "
+                    l7_data += f"server_tcp_connects={s_tcp_connects}; "
                     # Closed connections
                     s_tcp_closed = server_stats.get(u"tcps_closed", 0)
-                    l7_data += f"server_tcp_closed={s_tcp_closed}, "
+                    l7_data += f"server_tcp_closed={s_tcp_closed}; "
+                    # Sent bytes
+                    s_tcp_sndbyte = server_stats.get(u"tcps_sndbyte", 0)
+                    l7_data += f"server_tcp_tx_bytes={s_tcp_sndbyte}; "
+                    # Received bytes
+                    s_tcp_rcvbyte = server_stats.get(u"tcps_rcvbyte", 0)
+                    l7_data += f"server_tcp_rx_bytes={s_tcp_rcvbyte}; "
             else:
                 total_sent = stats[port_0][u"opackets"]
-                total_rcvd = stats[port_1][u"ipackets"]
+                total_received = stats[port_1][u"ipackets"]
 
             print(f"packets lost from {port_0} --> {port_1}: {lost_a} pkts")
             if traffic_directions > 1:
@@ -313,13 +384,17 @@ def simple_burst(
                 client.clear_profile()
                 client.disconnect()
                 print(
-                    f"cps={mult!r}, total_received={total_rcvd}, "
-                    f"total_sent={total_sent}, frame_loss={lost_a + lost_b}, "
-                    f"approximated_duration={approximated_duration}, "
-                    f"latency_stream_0(usec)={lat_a}, "
-                    f"latency_stream_1(usec)={lat_b}, "
-                    f"latency_hist_stream_0={lat_a_hist}, "
-                    f"latency_hist_stream_1={lat_b_hist}, "
+                    f"multiplier={multiplier!r}; "
+                    f"total_received={total_received}; "
+                    f"total_sent={total_sent}; "
+                    f"frame_loss={lost_a + lost_b}; "
+                    f"approximated_duration={approximated_duration}; "
+                    f"latency_stream_0(usec)={lat_a}; "
+                    f"latency_stream_1(usec)={lat_b}; "
+                    f"latency_hist_stream_0={lat_a_hist}; "
+                    f"latency_hist_stream_1={lat_b_hist}; "
+                    f"client_sent={client_sent}; "
+                    f"client_received={client_received}; "
                     f"{l7_data}"
                 )
 
@@ -337,20 +412,16 @@ def main():
     )
     parser.add_argument(
         u"-d", u"--duration", required=True, type=float,
-        help=u"Duration of traffic run."
+        help=u"Duration of the whole traffic run, including overheads."
     )
     parser.add_argument(
         u"-s", u"--frame_size", required=True,
         help=u"Size of a Frame without padding and IPG."
     )
     parser.add_argument(
-        u"-m", u"--mult", required=True, type=int,
+        u"-m", u"--multiplier", required=True, type=float,
         help=u"Multiplier of profile CPS."
     )
-    parser.add_argument(
-        u"-w", u"--warmup_time", type=float, default=5.0,
-        help=u"Traffic warm-up time in seconds, 0 = disable."
-    )
     parser.add_argument(
         u"--port_0", required=True, type=int,
         help=u"Port 0 on the traffic generator."
@@ -380,10 +451,15 @@ def main():
         framesize = args.frame_size
 
     simple_burst(
-        profile_file=args.profile, duration=args.duration, framesize=framesize,
-        mult=args.mult, warmup_time=args.warmup_time, port_0=args.port_0,
-        port_1=args.port_1, latency=args.latency, async_start=args.async_start,
-        traffic_directions=args.traffic_directions
+        profile_file=args.profile,
+        duration=args.duration,
+        framesize=framesize,
+        multiplier=args.multiplier,
+        port_0=args.port_0,
+        port_1=args.port_1,
+        latency=args.latency,
+        async_start=args.async_start,
+        traffic_directions=args.traffic_directions,
     )