http: add worker counters 67/43667/4
authorMatus Fabian <[email protected]>
Sun, 7 Sep 2025 20:25:40 +0000 (16:25 -0400)
committerFlorin Coras <[email protected]>
Mon, 15 Sep 2025 20:44:10 +0000 (20:44 +0000)
Type: improvement

Change-Id: I38c12f53568d41f7eb417d6c08d0c8fef5767597
Signed-off-by: Matus Fabian <[email protected]>
extras/hs-test/http1_test.go
extras/hs-test/http2_test.go
extras/hs-test/infra/suite_http1.go
extras/hs-test/infra/suite_http2.go
src/plugins/http/http.c
src/plugins/http/http1.c
src/plugins/http/http2/http2.c
src/plugins/http/http_private.h

index dbbd5d5..8361772 100644 (file)
@@ -283,6 +283,17 @@ func HttpCliTest(s *VethsSuite) {
        s.Containers.ServerVpp.VppInstance.Vppctl(cliServerCmd + " listener del")
        o = s.Containers.ServerVpp.VppInstance.Vppctl("show session verbose proto http")
        s.AssertNotContains(o, "LISTEN")
+
+       o = s.Containers.ClientVpp.VppInstance.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "1 connections established")
+       s.AssertContains(o, "1 requests sent")
+       s.AssertContains(o, "1 responses received")
+       o = s.Containers.ServerVpp.VppInstance.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "1 connections accepted")
+       s.AssertContains(o, "1 requests received")
+       s.AssertContains(o, "1 responses sent")
 }
 
 func HttpCliTlsTest(s *VethsSuite) {
@@ -432,6 +443,9 @@ func HttpClientInvalidHeaderNameTest(s *Http1Suite) {
        }
        s.AssertEqual(true, tcpSessionCleanupDone, "TCP session not cleanup")
        s.AssertEqual(true, httpCleanupDone, "HTTP not cleanup")
+       o = vpp.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "1 connections protocol error")
 }
 
 func HttpClientErrRespTest(s *Http1Suite) {
@@ -1905,6 +1919,9 @@ func HttpConnTimeoutTest(s *Http1Suite) {
        reply = make([]byte, 1024)
        _, err = conn.Read(reply)
        s.AssertMatchError(err, io.EOF, "connection not closed by server")
+       o := vpp.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "1 connections timeout")
 }
 
 func HttpIgnoreH2UpgradeTest(s *Http1Suite) {
index eaf18a9..e98f8e9 100644 (file)
@@ -178,6 +178,7 @@ func Http2CliTlsTest(s *VethsSuite) {
        s.Log(o)
        s.AssertContains(o, "<html>", "<html> not found in the result!")
        s.AssertContains(o, "</html>", "</html> not found in the result!")
+       s.Containers.ClientVpp.VppInstance.Vppctl("clear http stats")
 
        /* second request to test postponed ho-cleanup */
        o = s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" +
@@ -185,6 +186,21 @@ func Http2CliTlsTest(s *VethsSuite) {
        s.Log(o)
        s.AssertContains(o, "<html>", "<html> not found in the result!")
        s.AssertContains(o, "</html>", "</html> not found in the result!")
+
+       o = s.Containers.ClientVpp.VppInstance.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "1 connections established")
+       s.AssertContains(o, "1 requests sent")
+       s.AssertContains(o, "1 responses received")
+       s.AssertContains(o, "1 application streams opened")
+       s.AssertContains(o, "1 application streams closed")
+       o = s.Containers.ServerVpp.VppInstance.Vppctl("show http stats")
+       s.Log(o)
+       s.AssertContains(o, "2 connections accepted")
+       s.AssertContains(o, "2 requests received")
+       s.AssertContains(o, "2 responses sent")
+       s.AssertContains(o, "2 application streams opened")
+       s.AssertContains(o, "2 application streams closed")
 }
 
 func Http2ClientGetTest(s *Http2Suite) {
index 248a413..ead9c89 100644 (file)
@@ -83,6 +83,12 @@ func (s *Http1Suite) SetupTest() {
 
 func (s *Http1Suite) TeardownTest() {
        defer s.HstSuite.TeardownTest()
+       vpp := s.Containers.Vpp.VppInstance
+       if CurrentSpecReport().Failed() {
+               s.Log(vpp.Vppctl("show session verbose 2"))
+               s.Log(vpp.Vppctl("show error"))
+               s.Log(vpp.Vppctl("show http stats"))
+       }
 }
 
 // Creates container and config.
index 69b6bfd..770f631 100644 (file)
@@ -103,6 +103,7 @@ func (s *Http2Suite) TeardownTest() {
        if CurrentSpecReport().Failed() {
                s.Log(vpp.Vppctl("show session verbose 2"))
                s.Log(vpp.Vppctl("show error"))
+               s.Log(vpp.Vppctl("show http stats"))
        }
 }
 
index 7bfe5ea..f7071bf 100644 (file)
@@ -22,7 +22,8 @@
 #include <http/http_private.h>
 #include <http/http_timer.h>
 
-static http_main_t http_main;
+http_main_t http_main;
+
 static http_engine_vft_t *http_vfts;
 
 const http_buffer_type_t msg_to_buf_type[] = {
@@ -114,12 +115,6 @@ format_http_time_now (u8 *s, va_list *args)
   return format (s, "%U", format_clib_timebase_time, now);
 }
 
-static inline http_worker_t *
-http_worker_get (clib_thread_index_t thread_index)
-{
-  return &http_main.wrk[thread_index];
-}
-
 static inline u32
 http_conn_alloc_w_thread (clib_thread_index_t thread_index)
 {
@@ -427,6 +422,7 @@ http_conn_timeout_cb (void *hc_handlep)
   if (PREDICT_FALSE (hc->version != HTTP_VERSION_NA))
     http_vfts[hc->version].transport_close_callback (hc);
   http_disconnect_transport (hc);
+  http_stats_connections_timeout_inc (hs_handle >> 24);
 }
 
 /*************************/
@@ -830,6 +826,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en)
   vec_foreach (wrk, hm->wrk)
     {
       clib_spinlock_init (&wrk->pending_stream_connects_lock);
+      clib_memset (&wrk->stats, 0, sizeof (wrk->stats));
     }
   vec_validate (hm->rx_bufs, num_threads - 1);
   vec_validate (hm->tx_bufs, num_threads - 1);
@@ -1483,6 +1480,78 @@ http_transport_init (vlib_main_t *vm)
 
 VLIB_INIT_FUNCTION (http_transport_init);
 
+static clib_error_t *
+show_http_stats_fn (vlib_main_t *vm, unformat_input_t *input,
+                   vlib_cli_command_t *cmd)
+{
+  vlib_thread_main_t *vtm = vlib_get_thread_main ();
+  http_main_t *hm = &http_main;
+  http_worker_t *wrk;
+  u32 num_threads, i;
+
+  if (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    return clib_error_return (0, "unknown input `%U'", format_unformat_error,
+                             input);
+
+  if (!hm->is_init)
+    return clib_error_return (0, "http transport disabled");
+
+  num_threads = 1 /* main thread */ + vtm->n_threads;
+
+  for (i = 0; i < num_threads; i++)
+    {
+      wrk = http_worker_get (i);
+      vlib_cli_output (vm, "Thread %u:\n", i);
+
+#define _(name, str)                                                          \
+  if (wrk->stats.name)                                                        \
+    vlib_cli_output (vm, " %lu %s", wrk->stats.name, str);
+      foreach_http_wrk_stat
+#undef _
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (show_http_stats_command, static) = {
+  .path = "show http stats",
+  .short_help = "show http stats",
+  .function = show_http_stats_fn,
+};
+
+static clib_error_t *
+clear_http_stats_fn (vlib_main_t *vm, unformat_input_t *input,
+                    vlib_cli_command_t *cmd)
+{
+  vlib_thread_main_t *vtm = vlib_get_thread_main ();
+  http_main_t *hm = &http_main;
+  http_worker_t *wrk;
+  u32 num_threads, i;
+
+  if (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    return clib_error_return (0, "unknown input `%U'", format_unformat_error,
+                             input);
+
+  if (!hm->is_init)
+    return clib_error_return (0, "http transport disabled");
+
+  num_threads = 1 /* main thread */ + vtm->n_threads;
+
+  for (i = 0; i < num_threads; i++)
+    {
+      wrk = http_worker_get (i);
+      clib_memset (&wrk->stats, 0, sizeof (wrk->stats));
+    }
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (clear_http_stats_command, static) = {
+  .path = "clear http stats",
+  .short_help = "clear http stats",
+  .function = clear_http_stats_fn,
+};
+
 static uword
 unformat_http_version_cfg (unformat_input_t *input, va_list *va)
 {
index 7d781e4..5547d30 100644 (file)
@@ -871,6 +871,7 @@ http1_req_state_wait_transport_reply (http_conn_t *hc, http_req_t *req,
     }
 
   HTTP_DBG (3, "%v", rx_buf);
+  http_stats_responses_received_inc (hc->c_thread_index);
 
   if (vec_len (rx_buf) < 8)
     {
@@ -939,6 +940,7 @@ error:
   session_transport_closing_notify (&req->connection);
   session_transport_closed_notify (&req->connection);
   http_disconnect_transport (hc);
+  http_stats_proto_errors_inc (hc->c_thread_index);
   return HTTP_SM_ERROR;
 }
 
@@ -961,6 +963,7 @@ http1_req_state_wait_transport_method (http_conn_t *hc, http_req_t *req,
     return HTTP_SM_STOP;
 
   HTTP_DBG (3, "%v", rx_buf);
+  http_stats_requests_received_inc (hc->c_thread_index);
 
   if (vec_len (rx_buf) < 8)
     {
@@ -1045,6 +1048,7 @@ error:
   http_io_ts_after_read (hc, 1);
   http1_send_error (hc, ec, 0);
   session_transport_closing_notify (&req->connection);
+  http_stats_proto_errors_inc (hc->c_thread_index);
   http_disconnect_transport (hc);
 
   return HTTP_SM_ERROR;
@@ -1083,6 +1087,7 @@ http1_req_state_transport_io_more_data (http_conn_t *hc, http_req_t *req,
       clib_warning ("http protocol error: received more data than expected");
       session_transport_closing_notify (&req->connection);
       http_disconnect_transport (hc);
+      http_stats_proto_errors_inc (hc->c_thread_index);
       http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD);
       return HTTP_SM_ERROR;
     }
@@ -1186,6 +1191,7 @@ http1_req_state_udp_tunnel_rx (http_conn_t *hc, http_req_t *req,
              session_transport_closing_notify (&req->connection);
              session_transport_closed_notify (&req->connection);
              http_disconnect_transport (hc);
+             http_stats_proto_errors_inc (hc->c_thread_index);
              return HTTP_SM_STOP;
            }
          else
@@ -1360,12 +1366,14 @@ http1_req_state_wait_app_reply (http_conn_t *hc, http_req_t *req,
   http_req_state_change (req, next_state);
 
   http_io_ts_after_write (hc, 0);
+  http_stats_responses_sent_inc (hc->c_thread_index);
   return sm_result;
 
 error:
   http1_send_error (hc, sc, sp);
   session_transport_closing_notify (&req->connection);
   http_disconnect_transport (hc);
+  http_stats_proto_errors_inc (hc->c_thread_index);
   return HTTP_SM_STOP;
 }
 
@@ -1528,6 +1536,7 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req,
   http_req_state_change (req, next_state);
 
   http_io_ts_after_write (hc, 0);
+  http_stats_requests_sent_inc (hc->c_thread_index);
   goto done;
 
 error:
@@ -1535,6 +1544,7 @@ error:
   session_transport_closing_notify (&req->connection);
   session_transport_closed_notify (&req->connection);
   http_disconnect_transport (hc);
+  http_stats_proto_errors_inc (hc->c_thread_index);
 
 done:
   return sm_result;
@@ -1944,6 +1954,7 @@ http1_app_reset_callback (http_conn_t *hc, u32 req_index,
   req = http1_req_get (req_index, thread_index);
   session_transport_closed_notify (&req->connection);
   http_disconnect_transport (hc);
+  http_stats_connections_reset_by_app_inc (hc->c_thread_index);
 }
 
 static int
@@ -1955,6 +1966,7 @@ http1_transport_connected_callback (http_conn_t *hc)
 
   req = http1_conn_alloc_req (hc);
   http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD);
+  http_stats_connections_established_inc (hc->c_thread_index);
   return http_conn_established (hc, req, hc->hc_pa_app_api_ctx, 0);
 }
 
@@ -1969,6 +1981,7 @@ http1_transport_rx_callback (http_conn_t *hc)
       /* first request - create request ctx and notify app about new conn */
       req = http1_conn_alloc_req (hc);
       http_conn_accept_request (hc, req);
+      http_stats_connections_accepted_inc (hc->c_thread_index);
       http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD);
       hc->flags &= ~HTTP_CONN_F_NO_APP_SESSION;
     }
@@ -2026,6 +2039,7 @@ http1_transport_reset_callback (http_conn_t *hc)
     return;
   http_req_t *req = http1_conn_get_req (hc);
   session_transport_reset_notify (&req->connection);
+  http_stats_connections_reset_by_peer_inc (hc->c_thread_index);
 }
 
 static void
index 8c30fb8..adc99b1 100644 (file)
@@ -271,6 +271,7 @@ http2_conn_alloc_req (http_conn_t *hc, u8 is_parent)
       req->flags |= HTTP2_REQ_F_IS_PARENT;
       h2c->parent_req_index = req_index;
     }
+  http_stats_app_streams_opened_inc (hc->c_thread_index);
   return req;
 }
 
@@ -311,6 +312,7 @@ http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req,
     memset (req, 0xba, sizeof (*req));
   pool_put (wrk->req_pool, req);
   h2c->req_num--;
+  http_stats_app_streams_closed_inc (thread_index);
 }
 
 static inline void
@@ -472,6 +474,7 @@ http2_connection_error (http_conn_t *hc, http2_error_t error,
   if (clib_llist_elt_is_linked (h2c, sched_list))
     clib_llist_remove (wrk->conn_pool, sched_list, h2c);
   http_shutdown_transport (hc);
+  http_stats_proto_errors_inc (hc->c_thread_index);
 }
 
 always_inline void
@@ -1067,6 +1070,7 @@ http2_sched_dispatch_resp_headers (http2_req_t *req, http_conn_t *hc,
   n_written = http_io_ts_write_segs (hc, segs, 2, 0);
   ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len));
   http_io_ts_after_write (hc, 0);
+  http_stats_responses_sent_inc (hc->c_thread_index);
 }
 
 static void
@@ -1227,6 +1231,7 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc,
   n_written = http_io_ts_write_segs (hc, segs, 2, 0);
   ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len));
   http_io_ts_after_write (hc, 0);
+  http_stats_requests_sent_inc (hc->c_thread_index);
 }
 
 static void
@@ -1330,6 +1335,8 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req,
   http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
   http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_METHOD;
 
+  http_stats_responses_received_inc (hc->c_thread_index);
+
   h2c = http2_conn_ctx_get_w_thread (hc);
 
   vec_reset_length (req->base.headers);
@@ -1436,6 +1443,8 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req,
   http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_REPLY;
   http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index);
 
+  http_stats_requests_received_inc (hc->c_thread_index);
+
   h2c = http2_conn_ctx_get_w_thread (hc);
 
   *error =
@@ -2424,6 +2433,7 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh)
            &h2c->decoder_dynamic_table,
            http2_default_conn_settings.header_table_size);
          http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD);
+         http_stats_connections_established_inc (hc->c_thread_index);
          if (http_conn_established (hc, &req->base, hc->hc_pa_app_api_ctx, 0))
            return HTTP2_ERROR_INTERNAL_ERROR;
        }
@@ -2558,6 +2568,7 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh)
     }
   else
     {
+      http_stats_connections_reset_by_peer_inc (hc->c_thread_index);
       if (fh->length > HTTP2_GOAWAY_MIN_SIZE)
        clib_warning ("additional debug data: %U", format_http_bytes,
                      rx_buf + HTTP2_GOAWAY_MIN_SIZE,
@@ -2897,6 +2908,13 @@ http2_app_reset_callback (http_conn_t *hc, u32 req_index,
                                                 HTTP2_ERROR_INTERNAL_ERROR,
                           0);
   session_transport_delete_notify (&req->base.connection);
+  if (req->flags & HTTP2_REQ_F_IS_PARENT)
+    {
+      HTTP_DBG (1, "client app closed parent, closing connection");
+      ASSERT (!(hc->flags & HTTP_CONN_F_IS_SERVER));
+      http_disconnect_transport (hc);
+      http_stats_connections_reset_by_app_inc (thread_index);
+    }
   h2c = http2_conn_ctx_get_w_thread (hc);
   http2_conn_free_req (h2c, req, hc->c_thread_index);
 }
@@ -2941,12 +2959,15 @@ http2_transport_rx_callback (http_conn_t *hc)
        {
          HTTP_DBG (1, "to_deq %u is less than conn preface size", to_deq);
          http_disconnect_transport (hc);
+         http_stats_proto_errors_inc (hc->c_thread_index);
          return;
        }
+      http_stats_connections_accepted_inc (hc->c_thread_index);
       if (http2_expect_preface (hc, h2c))
        {
          HTTP_DBG (1, "conn preface verification failed");
          http_disconnect_transport (hc);
+         http_stats_proto_errors_inc (hc->c_thread_index);
          return;
        }
       http2_send_server_preface (hc);
index 1c90671..0ca599a 100644 (file)
 static const http_token_t http2_conn_preface = { http_token_lit (
   "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") };
 
+#define foreach_http_wrk_stat                                                 \
+  _ (proto_errors, "connections protocol error")                              \
+  _ (connections_timeout, "connections timeout")                              \
+  _ (connections_accepted, "connections accepted")                            \
+  _ (connections_established, "connections established")                      \
+  _ (connections_reset_by_peer, "connections reset by peer")                  \
+  _ (connections_reset_by_app, "connections reset by app")                    \
+  _ (app_streams_opened, "application streams opened")                        \
+  _ (app_streams_closed, "application streams closed")                        \
+  _ (requests_received, "requests received")                                  \
+  _ (requests_sent, "requests sent")                                          \
+  _ (responses_received, "responses received")                                \
+  _ (responses_sent, "responses sent")
+
+typedef struct
+{
+#define _(name, str) u64 name;
+  foreach_http_wrk_stat
+#undef _
+} http_wrk_stats_t;
+
 typedef union
 {
   struct
@@ -220,6 +241,7 @@ typedef struct http_worker_
   clib_spinlock_t pending_stream_connects_lock;
   http_pending_connect_stream_t *pending_connect_streams;
   http_pending_connect_stream_t *burst_connect_streams;
+  http_wrk_stats_t stats;
 } http_worker_t;
 
 typedef struct http_main_
@@ -923,4 +945,22 @@ http_conn_established (http_conn_t *hc, http_req_t *req,
   return 0;
 }
 
+extern http_main_t http_main;
+
+static_always_inline http_worker_t *
+http_worker_get (clib_thread_index_t thread_index)
+{
+  return &http_main.wrk[thread_index];
+}
+
+#define _(name, str)                                                          \
+  static_always_inline void http_stats_##name##_inc (                         \
+    clib_thread_index_t thread_index)                                         \
+  {                                                                           \
+    http_worker_t *wrk = http_worker_get (thread_index);                      \
+    wrk->stats.name++;                                                        \
+  }
+foreach_http_wrk_stat
+#undef _
+
 #endif /* SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_ */