hsa: measure rtt in echo client 89/43289/7
authorSemir Sionek <[email protected]>
Wed, 25 Jun 2025 12:16:41 +0000 (12:16 +0000)
committerFlorin Coras <[email protected]>
Wed, 16 Jul 2025 19:30:18 +0000 (19:30 +0000)
For TCP, rtt is extracted from transport info. For UDP & echo-bytes
option, we're measuring the time between sending & receiving the first
packet.
When using multiple clients, min/avg/max rtt values are displayed.

Type: improvement
Change-Id: I617527ca55726571638996dbcff05c2292f0ad18
Signed-off-by: Semir Sionek <[email protected]>
extras/hs-test/echo_test.go
src/plugins/hs_apps/echo_client.c
src/plugins/hs_apps/echo_client.h

index 53f8354..24ebb6d 100644 (file)
@@ -8,7 +8,7 @@ import (
 )
 
 func init() {
-       RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest)
+       RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest)
        RegisterSoloVethTests(TcpWithLossTest)
        RegisterVeth6Tests(TcpWithLoss6Test)
 }
@@ -57,6 +57,34 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) {
        }
 }
 
+func EchoBuiltinRoundtripTest(s *VethsSuite) {
+       regex := regexp.MustCompile(`(\.\d+)ms roundtrip`)
+       serverVpp := s.Containers.ServerVpp.VppInstance
+
+       serverVpp.Vppctl("test echo server " +
+               " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1)
+
+       clientVpp := s.Containers.ClientVpp.VppInstance
+
+       o := clientVpp.Vppctl("test echo client bytes 8m" +
+               " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1)
+       s.Log(o)
+       s.AssertContains(o, "Test started")
+       s.AssertContains(o, "Test finished")
+       if regex.MatchString(o) {
+               matches := regex.FindStringSubmatch(o)
+               if len(matches) != 0 {
+                       seconds, _ := strconv.ParseFloat(matches[1], 32)
+                       // Make sure that we are within ms range
+                       s.AssertEqualWithinThreshold(seconds, 0.5, 0.5)
+               } else {
+                       s.AssertEmpty("invalid echo test client output")
+               }
+       } else {
+               s.AssertEmpty("invalid echo test client output")
+       }
+}
+
 func EchoBuiltinEchobytesTest(s *VethsSuite) {
        serverVpp := s.Containers.ServerVpp.VppInstance
 
index 2d70120..4bc464b 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include <hs_apps/echo_client.h>
+#include <vnet/tcp/tcp_types.h>
 
 static ec_main_t ec_main;
 
@@ -76,6 +77,20 @@ ec_session_get (ec_worker_t *wrk, u32 ec_index)
   return pool_elt_at_index (wrk->sessions, ec_index);
 }
 
+static void
+update_rtt_stats (f64 session_rtt)
+{
+  ec_main_t *ecm = &ec_main;
+  clib_spinlock_lock (&ecm->rtt_stats.w_lock);
+  ecm->rtt_stats.sum_rtt += session_rtt;
+  ecm->rtt_stats.n_sum++;
+  if (session_rtt < ecm->rtt_stats.min_rtt)
+    ecm->rtt_stats.min_rtt = session_rtt;
+  if (session_rtt > ecm->rtt_stats.max_rtt)
+    ecm->rtt_stats.max_rtt = session_rtt;
+  clib_spinlock_unlock (&ecm->rtt_stats.w_lock);
+}
+
 static void
 send_data_chunk (ec_main_t *ecm, ec_session_t *es)
 {
@@ -221,6 +236,12 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es)
 
   if (n_read > 0)
     {
+      if (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes &&
+         (es->rtt_stat & EC_UDP_RTT_RX_FLAG) == 0)
+       {
+         es->rtt_stat |= EC_UDP_RTT_RX_FLAG;
+         update_rtt_stats (vlib_time_now (vlib_get_main ()) - es->send_rtt);
+       }
       if (ecm->cfg.verbose)
        {
           ELOG_TYPE_DECLARE (e) =
@@ -331,6 +352,12 @@ ec_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
 
       if (es->bytes_to_send > 0)
        {
+         if (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes &&
+             (es->rtt_stat & EC_UDP_RTT_TX_FLAG) == 0)
+           {
+             es->send_rtt = time_now;
+             es->rtt_stat |= EC_UDP_RTT_TX_FLAG;
+           }
          send_data_chunk (ecm, es);
          if (ecm->throughput)
            es->time_to_send += ecm->pacing_window_len;
@@ -350,6 +377,19 @@ ec_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
 
          if (s)
            {
+             if (ecm->transport_proto == TRANSPORT_PROTO_TCP)
+               {
+                 transport_connection_t *tc;
+                 tcp_connection_t *tcpc;
+                 tc = transport_get_connection (
+                   TRANSPORT_PROTO_TCP, s->connection_index, s->thread_index);
+                 if (PREDICT_TRUE (tc != NULL))
+                   {
+                     tcpc = tcp_get_connection_from_transport (tc);
+                     update_rtt_stats (tcpc->srtt * TCP_TICK);
+                   }
+               }
+
              vnet_disconnect_args_t _a, *a = &_a;
              a->handle = session_handle (s);
              a->app_index = ecm->app_index;
@@ -420,6 +460,10 @@ ec_reset_runtime_config (ec_main_t *ecm)
   ecm->throughput = 0;
   ecm->pacing_window_len = 1;
   ecm->max_chunk_bytes = 128 << 10;
+  clib_memset (&ecm->rtt_stats, 0, sizeof (ec_rttstat_t));
+  ecm->rtt_stats.min_rtt = CLIB_F64_MAX;
+  if (ecm->rtt_stats.w_lock == NULL)
+    clib_spinlock_init (&ecm->rtt_stats.w_lock);
   vec_free (ecm->connect_uri);
 }
 
@@ -530,6 +574,7 @@ ec_cleanup (ec_main_t *ecm)
     ecm->pacing_window_len = 1;
   if (ecm->barrier_acq_needed)
     vlib_worker_thread_barrier_sync (ecm->vlib_main);
+  clib_spinlock_free (&ecm->rtt_stats.w_lock);
 }
 
 static int
@@ -1392,6 +1437,21 @@ parse_config:
   /*
    * Done. Compute stats
    */
+  if (ecm->transport_proto == TRANSPORT_PROTO_TCP ||
+      (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes))
+    {
+      /* display rtt stats in milliseconds */
+      if (ecm->rtt_stats.n_sum == 1)
+       ec_cli ("%.05fms roundtrip", ecm->rtt_stats.min_rtt * 1000);
+      else if (ecm->rtt_stats.n_sum > 1)
+       ec_cli ("%.05fms/%.05fms/%.05fms min/avg/max roundtrip",
+               ecm->rtt_stats.min_rtt * 1000,
+               ecm->rtt_stats.sum_rtt / ecm->rtt_stats.n_sum * 1000,
+               ecm->rtt_stats.max_rtt * 1000);
+      else
+       ec_cli ("error measuring roundtrip time");
+    }
+
   delta = ecm->test_end_time - ecm->test_start_time;
   if (delta == 0.0)
     {
index d0eed6b..01b807a 100644 (file)
 #include <vnet/session/session.h>
 #include <vnet/session/application_interface.h>
 
+typedef struct ec_rttstat_
+{
+  f64 min_rtt;
+  f64 max_rtt;
+  f64 sum_rtt;
+  u32 n_sum;
+  clib_spinlock_t w_lock;
+} ec_rttstat_t;
+
 typedef struct ec_session_
 {
   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
@@ -38,6 +47,8 @@ typedef struct ec_session_
   f64 time_to_send;
   u64 bytes_paced_target;
   u64 bytes_paced_current;
+  f64 send_rtt;
+  u8 rtt_stat;
 } ec_session_t;
 
 typedef struct ec_worker_
@@ -67,6 +78,7 @@ typedef struct
   f64 test_end_time;
   u32 prev_conns;
   u32 repeats;
+  ec_rttstat_t rtt_stats;
 
   f64
     pacing_window_len; /**< Time between data chunk sends when limiting tput */
@@ -141,6 +153,12 @@ typedef enum ec_cli_signal_
   EC_CLI_TEST_DONE
 } ec_cli_signal_t;
 
+typedef enum ec_rtt_stat_
+{
+  EC_UDP_RTT_TX_FLAG = 1,
+  EC_UDP_RTT_RX_FLAG = 2
+} ec_rtt_stat;
+
 void ec_program_connects (void);
 
 #endif /* __included_echo_client_h__ */