From f9b1778e1914e8e6159aa75a59d9a08ce8557dc7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Sun, 7 Sep 2025 16:25:40 -0400 Subject: [PATCH] http: add worker counters Type: improvement Change-Id: I38c12f53568d41f7eb417d6c08d0c8fef5767597 Signed-off-by: Matus Fabian --- extras/hs-test/http1_test.go | 17 ++++++++ extras/hs-test/http2_test.go | 16 +++++++ extras/hs-test/infra/suite_http1.go | 6 +++ extras/hs-test/infra/suite_http2.go | 1 + src/plugins/http/http.c | 83 +++++++++++++++++++++++++++++++++---- src/plugins/http/http1.c | 14 +++++++ src/plugins/http/http2/http2.c | 21 ++++++++++ src/plugins/http/http_private.h | 40 ++++++++++++++++++ 8 files changed, 191 insertions(+), 7 deletions(-) diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index dbbd5d54b8f..836177239fa 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -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) { diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index eaf18a91a1a..e98f8e9a822 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -178,6 +178,7 @@ func Http2CliTlsTest(s *VethsSuite) { s.Log(o) s.AssertContains(o, "", " not found in the result!") s.AssertContains(o, "", " 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, "", " not found in the result!") s.AssertContains(o, "", " 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) { diff --git a/extras/hs-test/infra/suite_http1.go b/extras/hs-test/infra/suite_http1.go index 248a413bce4..ead9c89b401 100644 --- a/extras/hs-test/infra/suite_http1.go +++ b/extras/hs-test/infra/suite_http1.go @@ -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. diff --git a/extras/hs-test/infra/suite_http2.go b/extras/hs-test/infra/suite_http2.go index 69b6bfdf565..770f6313d08 100644 --- a/extras/hs-test/infra/suite_http2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -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")) } } diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 7bfe5ea32bd..f7071bf0e1a 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -22,7 +22,8 @@ #include #include -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) { diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 7d781e4d0d2..5547d309bf6 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -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 diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 8c30fb8830a..adc99b1a4eb 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -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); diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 1c906715b55..0ca599a4a9d 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -17,6 +17,27 @@ 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_ */ -- 2.16.6