This is an automated email from the ASF dual-hosted git repository.

maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 3c232f47f9 Add metrics for H2/H3 frames and a way to access them from 
plugins (#10627)
3c232f47f9 is described below

commit 3c232f47f9b0c0f0c32b644ecb715dd1a7b7ce25
Author: Masakazu Kitajo <mas...@apache.org>
AuthorDate: Mon Jan 29 15:53:33 2024 -0700

    Add metrics for H2/H3 frames and a way to access them from plugins (#10627)
---
 .../api/functions/TSHttpSsnInfoIntGet.en.rst       |  55 +++++++
 include/proxy/ProxySession.h                       |   3 +
 include/proxy/http2/HTTP2.h                        |  13 ++
 include/proxy/http2/Http2ClientSession.h           |   2 +
 include/proxy/http2/Http2CommonSession.h           |  20 +++
 include/proxy/http2/Http2ServerSession.h           |   2 +
 include/proxy/http3/Http3.h                        |  10 ++
 .../proxy/http3/{Http3.h => Http3FrameCounter.h}   |  49 ++++---
 include/proxy/http3/Http3Session.h                 |   5 +
 include/proxy/http3/Http3Types.h                   |  27 ++--
 include/ts/apidefs.h.in                            |   9 ++
 include/ts/ts.h                                    |  13 ++
 src/api/InkAPI.cc                                  |  25 ++++
 src/proxy/ProxySession.cc                          |  12 ++
 src/proxy/http2/HTTP2.cc                           |  25 ++++
 src/proxy/http2/Http2ClientSession.cc              |  16 ++
 src/proxy/http2/Http2CommonSession.cc              |  13 ++
 src/proxy/http2/Http2ServerSession.cc              |  16 ++
 src/proxy/http3/CMakeLists.txt                     |   1 +
 src/proxy/http3/Http3.cc                           |  30 +++-
 src/proxy/http3/Http3App.cc                        |   2 +
 src/proxy/http3/Http3DebugNames.cc                 |  14 +-
 src/proxy/http3/Http3FrameCounter.cc               |  59 ++++++++
 src/proxy/http3/Http3ProtocolEnforcer.cc           |   9 +-
 src/proxy/http3/Http3Session.cc                    |  18 +++
 src/proxy/http3/Http3Transaction.cc                |   1 +
 tests/gold_tests/h2/gold/log.gold                  |   2 +
 tests/gold_tests/pluginTest/tsapi/CMakeLists.txt   |   1 +
 .../pluginTest/tsapi/test_TSHttpSsnInfo.cc         | 163 +++++++++++++++++++++
 .../pluginTest/tsapi/test_TSHttpSsnInfo.test.py    | 103 +++++++++++++
 .../pluginTest/tsapi/test_TSHttpSsnInfo_curl0.gold |   1 +
 .../tsapi/test_TSHttpSsnInfo_nghttp0.gold          |  21 +++
 .../tsapi/test_TSHttpSsnInfo_plugin_log.gold       |   2 +
 33 files changed, 693 insertions(+), 49 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSHttpSsnInfoIntGet.en.rst 
b/doc/developer-guide/api/functions/TSHttpSsnInfoIntGet.en.rst
new file mode 100644
index 0000000000..c8028d906a
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSHttpSsnInfoIntGet.en.rst
@@ -0,0 +1,55 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed
+   with this work for additional information regarding copyright
+   ownership.  The ASF licenses this file to you 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
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied.  See the License for the specific language governing
+   permissions and limitations under the License.
+
+
+TSHttpSsnInfoIntGet
+===================
+
+Synopsis
+--------
+
+.. code-block:: cpp
+
+    #include <ts/ts.h>
+
+.. c:function:: TSReturnCode TSHttpSsnInfoIntGet(TSHttpSsn ssnp, 
TSHttpSsnInfoKey key, TSMgmtInt * value, uint64_t subkey = 0)
+
+Description
+-----------
+
+:c:func:`TSHttpSsnInfoIntGet` returns arbitrary integer-typed info about a 
session as defined in
+:c:type:`TSHttpSsnInfoKey`. The API will be part of a generic API umbrella 
that can support returning
+arbitrary info about a session using custom log tags.
+
+The :c:type:`TSHttpSsnInfoKey` currently supports the below integer-based info 
about a transaction
+
+.. c:enum:: TSHttpSsnInfoKey
+
+   .. c:enumerator:: TS_SSN_INFO_TRANSACTION_COUNT
+
+      The value indicate the number of transactions made on the session.
+
+   .. c:enumerator:: TS_SSN_INFO_RECEIVED_FRAME_COUNT
+
+      The value indicate the number of HTTP/2 or HTTP/3 frames received on the 
session.
+      A frame type must be specified by passing it to subkey.
+      You can use TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN and 
TS_SSN_INFO_RECEIVED_FRAME_COUNT_H3_UNKNOWN to get the value for
+      unknown frames.
+
+Return values
+-------------
+
+The API returns :c:data:`TS_SUCCESS`, if the requested info is supported, 
:c:data:`TS_ERROR` otherwise.
diff --git a/include/proxy/ProxySession.h b/include/proxy/ProxySession.h
index 9ad9f644d6..a9f3e4a289 100644
--- a/include/proxy/ProxySession.h
+++ b/include/proxy/ProxySession.h
@@ -111,6 +111,9 @@ public:
 
   virtual PoolableSession *get_server_session() const;
 
+  virtual bool is_protocol_framed() const;
+  virtual uint64_t get_received_frame_count(uint64_t type) const;
+
   // Replicate NetVConnection API
   virtual sockaddr const *get_remote_addr() const;
   virtual sockaddr const *get_local_addr();
diff --git a/include/proxy/http2/HTTP2.h b/include/proxy/http2/HTTP2.h
index e71f5577f6..fbaa579e7c 100644
--- a/include/proxy/http2/HTTP2.h
+++ b/include/proxy/http2/HTTP2.h
@@ -106,6 +106,17 @@ struct Http2StatsBlock {
   Metrics::Counter::AtomicType *insufficient_avg_window_update;
   Metrics::Counter::AtomicType *max_concurrent_streams_exceeded_in;
   Metrics::Counter::AtomicType *max_concurrent_streams_exceeded_out;
+  Metrics::Counter::AtomicType *data_frames_in;
+  Metrics::Counter::AtomicType *headers_frames_in;
+  Metrics::Counter::AtomicType *priority_frames_in;
+  Metrics::Counter::AtomicType *rst_stream_frames_in;
+  Metrics::Counter::AtomicType *settings_frames_in;
+  Metrics::Counter::AtomicType *push_promise_frames_in;
+  Metrics::Counter::AtomicType *ping_frames_in;
+  Metrics::Counter::AtomicType *goaway_frames_in;
+  Metrics::Counter::AtomicType *window_update_frames_in;
+  Metrics::Counter::AtomicType *continuation_frames_in;
+  Metrics::Counter::AtomicType *unknown_frames_in;
 };
 
 extern Http2StatsBlock http2_rsb;
@@ -166,6 +177,8 @@ enum Http2FrameType {
   HTTP2_FRAME_TYPE_MAX,
 };
 
+extern Metrics::Counter::AtomicType 
*http2_frame_metrics_in[HTTP2_FRAME_TYPE_MAX + 1];
+
 // [RFC 7540] 6.1. Data
 enum Http2FrameFlagsData {
   HTTP2_FLAGS_DATA_END_STREAM = 0x01,
diff --git a/include/proxy/http2/Http2ClientSession.h 
b/include/proxy/http2/Http2ClientSession.h
index 8a550c02f4..3e3cea196a 100644
--- a/include/proxy/http2/Http2ClientSession.h
+++ b/include/proxy/http2/Http2ClientSession.h
@@ -49,6 +49,8 @@ public:
   void destroy() override;
   void release(ProxyTransaction *trans) override;
   void free() override;
+  bool is_protocol_framed() const override;
+  uint64_t get_received_frame_count(uint64_t type) const override;
 
   ////////////////////
   // Accessors
diff --git a/include/proxy/http2/Http2CommonSession.h 
b/include/proxy/http2/Http2CommonSession.h
index 60e19be543..9f9b7e53b3 100644
--- a/include/proxy/http2/Http2CommonSession.h
+++ b/include/proxy/http2/Http2CommonSession.h
@@ -132,6 +132,11 @@ protected:
   int do_start_frame_read(Http2ErrorCode &ret_error);
   int do_complete_frame_read();
 
+  /**
+   * Count received frames
+   */
+  void _count_received_frames(uint32_t type);
+
   bool _should_do_something_else();
 
   ////////
@@ -169,6 +174,21 @@ protected:
   int64_t read_from_early_data   = 0;
   bool cur_frame_from_early_data = false;
 
+  // Counter for received frames
+  std::atomic<uint64_t> _frame_counts_in[HTTP2_FRAME_TYPE_MAX + 1] = {
+    0, // DATA
+    0, // HEADERS
+    0, // PRIORITY
+    0, // RST_STREAM
+    0, // SETTINGS
+    0, // PUSH_PROMISE
+    0, // PING
+    0, // GOAWAY
+    0, // WINDOW_UPDATE
+    0, // CONTINUATION
+    0  // UNKNOWN
+  };
+
 private:
   bool _interrupt_reading_frames = false;
 };
diff --git a/include/proxy/http2/Http2ServerSession.h 
b/include/proxy/http2/Http2ServerSession.h
index 472c594700..c575314278 100644
--- a/include/proxy/http2/Http2ServerSession.h
+++ b/include/proxy/http2/Http2ServerSession.h
@@ -52,6 +52,8 @@ public:
   void release(ProxyTransaction *trans) override;
   void free() override;
   ProxyTransaction *new_transaction() override;
+  bool is_protocol_framed() const override;
+  uint64_t get_received_frame_count(uint64_t type) const override;
 
   void add_session() override;
   void remove_session();
diff --git a/include/proxy/http3/Http3.h b/include/proxy/http3/Http3.h
index c1b1ff3205..f3777e5d38 100644
--- a/include/proxy/http3/Http3.h
+++ b/include/proxy/http3/Http3.h
@@ -25,6 +25,7 @@
 
 #include "tscore/ink_defs.h"
 #include "tsutil/Metrics.h"
+#include "Http3Types.h"
 
 using ts::Metrics;
 
@@ -44,6 +45,15 @@ struct Http3StatsBlock {
   // Example: Metrics::Counter::AtomicType *current_client_session_count;
   // Once created, e.g.
   // Metrics::Counter::increment(http3_rsb.current_client_session_count);
+  Metrics::Counter::AtomicType *data_frames_in;
+  Metrics::Counter::AtomicType *headers_frames_in;
+  Metrics::Counter::AtomicType *cancel_push_frames_in;
+  Metrics::Counter::AtomicType *settings_frames_in;
+  Metrics::Counter::AtomicType *push_promise_frames_in;
+  Metrics::Counter::AtomicType *goaway_frames_in;
+  Metrics::Counter::AtomicType *max_push_id;
+  Metrics::Counter::AtomicType *unknown_frames_in;
 };
 
 extern Http3StatsBlock http3_rsb; // Container for statistics.
+extern Metrics::Counter::AtomicType 
*http3_frame_metrics_in[static_cast<int>(Http3FrameType::UNKNOWN) + 1];
diff --git a/include/proxy/http3/Http3.h 
b/include/proxy/http3/Http3FrameCounter.h
similarity index 50%
copy from include/proxy/http3/Http3.h
copy to include/proxy/http3/Http3FrameCounter.h
index c1b1ff3205..b64b080dc4 100644
--- a/include/proxy/http3/Http3.h
+++ b/include/proxy/http3/Http3FrameCounter.h
@@ -23,27 +23,38 @@
 
 #pragma once
 
-#include "tscore/ink_defs.h"
-#include "tsutil/Metrics.h"
+#include "proxy/http3/Http3Types.h"
+#include "proxy/http3/Http3FrameHandler.h"
 
-using ts::Metrics;
-
-extern const uint32_t HTTP3_DEFAULT_HEADER_TABLE_SIZE;
-extern const uint32_t HTTP3_DEFAULT_MAX_FIELD_SECTION_SIZE;
-extern const uint32_t HTTP3_DEFAULT_QPACK_BLOCKED_STREAMS;
-extern const uint32_t HTTP3_DEFAULT_NUM_PLACEHOLDERS;
-
-class Http3
+class Http3FrameCounter : public Http3FrameHandler
 {
 public:
-  static void init();
-};
+  Http3FrameCounter(){};
 
-// Statistics
-struct Http3StatsBlock {
-  // Example: Metrics::Counter::AtomicType *current_client_session_count;
-  // Once created, e.g.
-  // Metrics::Counter::increment(http3_rsb.current_client_session_count);
-};
+  // Http3FrameHandler
+  std::vector<Http3FrameType> interests() override;
+  Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, int32_t 
frame_seq = -1,
+                              Http3StreamType s_type = 
Http3StreamType::UNKNOWN) override;
+
+  uint64_t get_count(uint64_t type) const;
 
-extern Http3StatsBlock http3_rsb; // Container for statistics.
+private:
+  // Counter for received frames
+  std::atomic<uint64_t> 
_frame_counts_in[static_cast<int>(Http3FrameType::UNKNOWN) + 1] = {
+    0, // DATA
+    0, // HEADERS
+    0, // X_RESERVED_1
+    0, // CANCEL_PUSH
+    0, // SETTINGS
+    0, // PUSH_PROMISE
+    0, // X_RESERVED_2
+    0, // GOAWAY
+    0, // X_RESERVED_3
+    0, // X_RESERVED_4
+    0, // UNDEFINED
+    0, // UNDEFINED
+    0, // UNDEFINED
+    0, // MAX_PUSH_ID
+    0  // UNKNOWN
+  };
+};
diff --git a/include/proxy/http3/Http3Session.h 
b/include/proxy/http3/Http3Session.h
index 5bbb4ed795..cfcb7e7f76 100644
--- a/include/proxy/http3/Http3Session.h
+++ b/include/proxy/http3/Http3Session.h
@@ -25,6 +25,7 @@
 
 #include "proxy/ProxySession.h"
 #include "proxy/http3/Http3Transaction.h"
+#include "proxy/http3/Http3FrameCounter.h"
 #include "proxy/http3/QPACK.h"
 
 class HQSession : public ProxySession
@@ -76,16 +77,20 @@ public:
   HTTPVersion get_version(HTTPHdr &hdr) const override;
   void increment_current_active_connections_stat() override;
   void decrement_current_active_connections_stat() override;
+  bool is_protocol_framed() const override;
+  uint64_t get_received_frame_count(uint64_t type) const override;
 
   // Implement ProxySession interface
   const char *get_protocol_string() const override;
 
   QPACK *local_qpack();
   QPACK *remote_qpack();
+  Http3FrameCounter *get_received_frame_counter();
 
 private:
   QPACK *_remote_qpack = nullptr; // QPACK for decoding
   QPACK *_local_qpack  = nullptr; // QPACK for encoding
+  Http3FrameCounter _received_frame_counter;
 };
 
 /**
diff --git a/include/proxy/http3/Http3Types.h b/include/proxy/http3/Http3Types.h
index 8986813d78..e5d54c6731 100644
--- a/include/proxy/http3/Http3Types.h
+++ b/include/proxy/http3/Http3Types.h
@@ -50,20 +50,19 @@ enum class Http3SettingsId : uint64_t {
 
 // Update Http3Frame::type(const uint8_t *) too when you modify this list
 enum class Http3FrameType : uint64_t {
-  DATA              = 0x00,
-  HEADERS           = 0x01,
-  PRIORITY          = 0x02,
-  CANCEL_PUSH       = 0x03,
-  SETTINGS          = 0x04,
-  PUSH_PROMISE      = 0x05,
-  X_RESERVED_1      = 0x06,
-  GOAWAY            = 0x07,
-  X_RESERVED_2      = 0x08,
-  X_RESERVED_3      = 0x09,
-  MAX_PUSH_ID       = 0x0D,
-  DUPLICATE_PUSH_ID = 0x0E,
-  X_MAX_DEFINED     = 0x0E,
-  UNKNOWN           = 0xFF,
+  DATA          = 0x00,
+  HEADERS       = 0x01,
+  X_RESERVED_1  = 0x02,
+  CANCEL_PUSH   = 0x03,
+  SETTINGS      = 0x04,
+  PUSH_PROMISE  = 0x05,
+  X_RESERVED_2  = 0x06,
+  GOAWAY        = 0x07,
+  X_RESERVED_3  = 0x08,
+  X_RESERVED_4  = 0x09,
+  MAX_PUSH_ID   = 0x0D,
+  X_MAX_DEFINED = 0x0D,
+  UNKNOWN       = 0x0E,
 };
 
 enum class Http3ErrorClass {
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index e2399b9768..9c9ae73823 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -647,6 +647,15 @@ char const TS_VERSION_STRING[] = "@TS_VERSION_STRING@";
     TS_TXN_INFO_LAST_ENTRY,
   };
 
+  enum TSHttpSsnInfoKey {
+    TS_SSN_INFO_NONE = -1,
+    TS_SSN_INFO_TRANSACTION_COUNT,
+    TS_SSN_INFO_RECEIVED_FRAME_COUNT,
+  };
+
+#define TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN 999
+#define TS_SSN_INFO_RECEIVED_FRAME_COUNT_H3_UNKNOWN 0x21
+
   enum TSVConnCloseFlags {
     TS_VC_CLOSE_ABORT  = -1,
     TS_VC_CLOSE_NORMAL = 1,
diff --git a/include/ts/ts.h b/include/ts/ts.h
index e8fc431113..d414bd39f8 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -3036,6 +3036,19 @@ namespace c
   */
   TSReturnCode TSHttpTxnInfoIntGet(TSHttpTxn txnp, TSHttpTxnInfoKey key, 
TSMgmtInt *value);
 
+  /* Get Arbitrary Ssn info such as total transaction count etc as defined in 
TSHttpSsnInfoKey */
+  /**
+     Return the particular ssn info requested.
+
+     @param ssnp the transaction pointer
+     @param key the requested ssn info.
+     @param TSMgmtInt a pointer to a integer where the return value is stored
+
+     @return @c TS_SUCCESS if the requested info is supported, TS_ERROR 
otherwise
+
+  */
+  TSReturnCode TSHttpSsnInfoIntGet(TSHttpSsn ssnp, TSHttpSsnInfoKey key, 
TSMgmtInt *value, uint64_t sub_key = 0);
+
   /****************************************************************************
    *  TSHttpTxnCacheLookupCountGet
    *  Return: TS_SUCCESS/TS_ERROR
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 47657c11f6..05ee4ce783 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -4933,6 +4933,31 @@ tsapi::c::TSHttpTxnInfoIntGet(TSHttpTxn txnp, 
TSHttpTxnInfoKey key, TSMgmtInt *v
   return TS_SUCCESS;
 }
 
+TSReturnCode
+tsapi::c::TSHttpSsnInfoIntGet(TSHttpSsn ssnp, TSHttpSsnInfoKey key, TSMgmtInt 
*value, uint64_t sub_key)
+{
+  sdk_assert(sdk_sanity_check_http_ssn(ssnp) == TS_SUCCESS);
+  sdk_assert(sdk_sanity_check_null_ptr((void *)value) == TS_SUCCESS);
+
+  ProxySession *ssn = reinterpret_cast<ProxySession *>(ssnp);
+
+  switch (key) {
+  case TS_SSN_INFO_TRANSACTION_COUNT:
+    *value = ssn->get_transact_count();
+    break;
+  case TS_SSN_INFO_RECEIVED_FRAME_COUNT:
+    if (!ssn->is_protocol_framed()) {
+      return TS_ERROR;
+    }
+    *value = ssn->get_received_frame_count(sub_key);
+    break;
+  default:
+    return TS_ERROR;
+  }
+
+  return TS_SUCCESS;
+}
+
 int
 tsapi::c::TSHttpTxnIsWebsocket(TSHttpTxn txnp)
 {
diff --git a/src/proxy/ProxySession.cc b/src/proxy/ProxySession.cc
index dea84625a4..c2911ddd4f 100644
--- a/src/proxy/ProxySession.cc
+++ b/src/proxy/ProxySession.cc
@@ -215,6 +215,18 @@ ProxySession::get_server_session() const
   return nullptr;
 }
 
+bool
+ProxySession::is_protocol_framed() const
+{
+  return false;
+}
+
+uint64_t
+ProxySession::get_received_frame_count(uint64_t type) const
+{
+  return 0;
+}
+
 void
 ProxySession::set_active_timeout(ink_hrtime timeout_in)
 {
diff --git a/src/proxy/http2/HTTP2.cc b/src/proxy/http2/HTTP2.cc
index 584d203a73..3bd504ee62 100644
--- a/src/proxy/http2/HTTP2.cc
+++ b/src/proxy/http2/HTTP2.cc
@@ -50,6 +50,8 @@ static VersionConverter hvc;
 // Statistics
 Http2StatsBlock http2_rsb;
 
+Metrics::Counter::AtomicType *http2_frame_metrics_in[HTTP2_FRAME_TYPE_MAX + 1];
+
 union byte_pointer {
   byte_pointer(void *p) : ptr(p) {}
   void *ptr;
@@ -600,6 +602,29 @@ Http2::init()
     
Metrics::Counter::createPtr("proxy.process.http2.max_concurrent_streams_exceeded_in");
   http2_rsb.max_concurrent_streams_exceeded_out =
     
Metrics::Counter::createPtr("proxy.process.http2.max_concurrent_streams_exceeded_out");
+  http2_rsb.data_frames_in          = 
Metrics::Counter::createPtr("proxy.process.http2.data_frames_in"),
+  http2_rsb.headers_frames_in       = 
Metrics::Counter::createPtr("proxy.process.http2.headers_frames_in"),
+  http2_rsb.priority_frames_in      = 
Metrics::Counter::createPtr("proxy.process.http2.priority_frames_in"),
+  http2_rsb.rst_stream_frames_in    = 
Metrics::Counter::createPtr("proxy.process.http2.rst_stream_frames_in"),
+  http2_rsb.settings_frames_in      = 
Metrics::Counter::createPtr("proxy.process.http2.settings_frames_in"),
+  http2_rsb.push_promise_frames_in  = 
Metrics::Counter::createPtr("proxy.process.http2.push_promise_frames_in"),
+  http2_rsb.ping_frames_in          = 
Metrics::Counter::createPtr("proxy.process.http2.ping_frames_in"),
+  http2_rsb.goaway_frames_in        = 
Metrics::Counter::createPtr("proxy.process.http2.goaway_frames_in"),
+  http2_rsb.window_update_frames_in = 
Metrics::Counter::createPtr("proxy.process.http2.window_update_frames_in"),
+  http2_rsb.continuation_frames_in  = 
Metrics::Counter::createPtr("proxy.process.http2.continuation_frames_in"),
+  http2_rsb.unknown_frames_in       = 
Metrics::Counter::createPtr("proxy.process.http2.unknown_frames_in"),
+
+  http2_frame_metrics_in[0]  = http2_rsb.data_frames_in;
+  http2_frame_metrics_in[1]  = http2_rsb.headers_frames_in;
+  http2_frame_metrics_in[2]  = http2_rsb.priority_frames_in;
+  http2_frame_metrics_in[3]  = http2_rsb.rst_stream_frames_in;
+  http2_frame_metrics_in[4]  = http2_rsb.settings_frames_in;
+  http2_frame_metrics_in[5]  = http2_rsb.push_promise_frames_in;
+  http2_frame_metrics_in[6]  = http2_rsb.ping_frames_in;
+  http2_frame_metrics_in[7]  = http2_rsb.goaway_frames_in;
+  http2_frame_metrics_in[8]  = http2_rsb.window_update_frames_in;
+  http2_frame_metrics_in[9]  = http2_rsb.continuation_frames_in;
+  http2_frame_metrics_in[10] = http2_rsb.unknown_frames_in;
 
   http2_init();
 }
diff --git a/src/proxy/http2/Http2ClientSession.cc 
b/src/proxy/http2/Http2ClientSession.cc
index f343a6b1c5..bf4732fb46 100644
--- a/src/proxy/http2/Http2ClientSession.cc
+++ b/src/proxy/http2/Http2ClientSession.cc
@@ -338,3 +338,19 @@ Http2ClientSession::get_version(HTTPHdr &hdr) const
 {
   return HTTP_2_0;
 }
+
+bool
+Http2ClientSession::is_protocol_framed() const
+{
+  return true;
+}
+
+uint64_t
+Http2ClientSession::get_received_frame_count(uint64_t type) const
+{
+  if (type == 999) { // TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN in 
apidefs.h.in
+    return this->_frame_counts_in[HTTP2_FRAME_TYPE_MAX];
+  } else {
+    return this->_frame_counts_in[type];
+  }
+}
diff --git a/src/proxy/http2/Http2CommonSession.cc 
b/src/proxy/http2/Http2CommonSession.cc
index cc4a357523..56c82e40ac 100644
--- a/src/proxy/http2/Http2CommonSession.cc
+++ b/src/proxy/http2/Http2CommonSession.cc
@@ -321,6 +321,7 @@ Http2CommonSession::do_complete_frame_read()
   ink_release_assert(this->_read_buffer_reader->read_avail() >= 
this->current_hdr.length);
 
   Http2Frame frame(this->current_hdr, this->_read_buffer_reader, 
this->cur_frame_from_early_data);
+  this->_count_received_frames(frame.header().type);
   connection_state.rcv_frame(&frame);
 
   // Check whether data is read from early data
@@ -467,3 +468,15 @@ Http2CommonSession::is_outbound() const
 {
   return false;
 }
+
+void
+Http2CommonSession::_count_received_frames(uint32_t type)
+{
+  if (type > HTTP2_FRAME_TYPE_MAX) {
+    type = HTTP2_FRAME_TYPE_MAX;
+  }
+  // Global counter
+  Metrics::Counter::increment(http2_frame_metrics_in[type]);
+  // Local counter
+  this->_frame_counts_in[type]++;
+}
diff --git a/src/proxy/http2/Http2ServerSession.cc 
b/src/proxy/http2/Http2ServerSession.cc
index 10896ac6aa..46b3767420 100644
--- a/src/proxy/http2/Http2ServerSession.cc
+++ b/src/proxy/http2/Http2ServerSession.cc
@@ -403,6 +403,22 @@ Http2ServerSession::get_remote_reader()
   return _read_buffer_reader;
 }
 
+bool
+Http2ServerSession::is_protocol_framed() const
+{
+  return true;
+}
+
+uint64_t
+Http2ServerSession::get_received_frame_count(uint64_t type) const
+{
+  if (type == 999) { // TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN in 
apidefs.h.in
+    return this->_frame_counts_in[HTTP2_FRAME_TYPE_MAX];
+  } else {
+    return this->_frame_counts_in[type];
+  }
+}
+
 std::function<PoolableSession *()> create_h2_server_session = []() -> 
PoolableSession * {
   return http2ServerSessionAllocator.alloc();
 };
diff --git a/src/proxy/http3/CMakeLists.txt b/src/proxy/http3/CMakeLists.txt
index 49e3d93d4b..37e00b918d 100644
--- a/src/proxy/http3/CMakeLists.txt
+++ b/src/proxy/http3/CMakeLists.txt
@@ -28,6 +28,7 @@ add_library(
   Http3DebugNames.cc
   Http3Frame.cc
   Http3FrameCollector.cc
+  Http3FrameCounter.cc
   Http3FrameDispatcher.cc
   Http3HeaderFramer.cc
   Http3DataFramer.cc
diff --git a/src/proxy/http3/Http3.cc b/src/proxy/http3/Http3.cc
index f46c1b933c..c9c8b4165d 100644
--- a/src/proxy/http3/Http3.cc
+++ b/src/proxy/http3/Http3.cc
@@ -22,6 +22,7 @@
  */
 
 #include "proxy/http3/Http3.h"
+#include "proxy/http3/Http3Types.h"
 
 // Default values of settings defined by specs (draft-17)
 const uint32_t HTTP3_DEFAULT_HEADER_TABLE_SIZE      = 0;
@@ -31,11 +32,34 @@ const uint32_t HTTP3_DEFAULT_NUM_PLACEHOLDERS       = 0;
 
 Http3StatsBlock http3_rsb;
 
+Metrics::Counter::AtomicType 
*http3_frame_metrics_in[static_cast<int>(Http3FrameType::UNKNOWN) + 1];
+
 void
 Http3::init()
 {
-  // Example (remove comments here when addding)
-  //
   // Setup statistics
-  // http3_rsb.current_client_session_count = 
Metrics::Gauge::createPtr("proxy.process.http3.current_client_connections");
+  http3_rsb.data_frames_in         = 
Metrics::Counter::createPtr("proxy.process.http3.data_frames_in");
+  http3_rsb.headers_frames_in      = 
Metrics::Counter::createPtr("proxy.process.http3.headers_frames_in");
+  http3_rsb.cancel_push_frames_in  = 
Metrics::Counter::createPtr("proxy.process.http3.cancel_push_frames_in");
+  http3_rsb.settings_frames_in     = 
Metrics::Counter::createPtr("proxy.process.http3.settings_frames_in");
+  http3_rsb.push_promise_frames_in = 
Metrics::Counter::createPtr("proxy.process.http3.push_promise_frames_in");
+  http3_rsb.goaway_frames_in       = 
Metrics::Counter::createPtr("proxy.process.http3.goaway_frames_in");
+  http3_rsb.max_push_id            = 
Metrics::Counter::createPtr("proxy.process.http3.max_push_id_frames_in");
+  http3_rsb.unknown_frames_in      = 
Metrics::Counter::createPtr("proxy.process.http3.unknown_frames_in");
+
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::DATA)]         = 
http3_rsb.data_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::HEADERS)]      = 
http3_rsb.headers_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::X_RESERVED_1)] = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::CANCEL_PUSH)]  = 
http3_rsb.cancel_push_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::SETTINGS)]     = 
http3_rsb.settings_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::PUSH_PROMISE)] = 
http3_rsb.push_promise_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::X_RESERVED_2)] = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::GOAWAY)]       = 
http3_rsb.goaway_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::X_RESERVED_3)] = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::X_RESERVED_4)] = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[10]                                             = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[11]                                             = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[12]                                             = 
http3_rsb.unknown_frames_in;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::MAX_PUSH_ID)]  = 
http3_rsb.max_push_id;
+  http3_frame_metrics_in[static_cast<int>(Http3FrameType::UNKNOWN)]      = 
http3_rsb.unknown_frames_in;
 }
diff --git a/src/proxy/http3/Http3App.cc b/src/proxy/http3/Http3App.cc
index 5d0cf18b8a..c453950b6e 100644
--- a/src/proxy/http3/Http3App.cc
+++ b/src/proxy/http3/Http3App.cc
@@ -54,6 +54,8 @@ Http3App::Http3App(NetVConnection *client_vc, QUICConnection 
*qc, IpAllow::ACL &
 
   this->_qc->stream_manager()->set_default_application(this);
 
+  
this->_control_stream_dispatcher.add_handler(this->_ssn->get_received_frame_counter());
+
   this->_protocol_enforcer = new Http3ProtocolEnforcer();
   this->_control_stream_dispatcher.add_handler(this->_protocol_enforcer);
 
diff --git a/src/proxy/http3/Http3DebugNames.cc 
b/src/proxy/http3/Http3DebugNames.cc
index e3aebe5b84..4208f6c4a4 100644
--- a/src/proxy/http3/Http3DebugNames.cc
+++ b/src/proxy/http3/Http3DebugNames.cc
@@ -32,24 +32,22 @@ Http3DebugNames::frame_type(Http3FrameType type)
     return "DATA";
   case Http3FrameType::HEADERS:
     return "HEADERS";
-  case Http3FrameType::PRIORITY:
-    return "PRIORITY";
+  case Http3FrameType::X_RESERVED_1:
+    return "X_RESERVED_1";
   case Http3FrameType::CANCEL_PUSH:
     return "CANCEL_PUSH";
   case Http3FrameType::SETTINGS:
     return "SETTINGS";
   case Http3FrameType::PUSH_PROMISE:
     return "PUSH_PROMISE";
-  case Http3FrameType::GOAWAY:
-    return "GOAWAY";
-  case Http3FrameType::DUPLICATE_PUSH_ID:
-    return "DUPLICATE_PUSH_ID";
-  case Http3FrameType::X_RESERVED_1:
-    return "X_RESERVED_1";
   case Http3FrameType::X_RESERVED_2:
     return "X_RESERVED_2";
+  case Http3FrameType::GOAWAY:
+    return "GOAWAY";
   case Http3FrameType::X_RESERVED_3:
     return "X_RESERVED_3";
+  case Http3FrameType::X_RESERVED_4:
+    return "X_RESERVED_4";
   case Http3FrameType::UNKNOWN:
   default:
     return "UNKNOWN";
diff --git a/src/proxy/http3/Http3FrameCounter.cc 
b/src/proxy/http3/Http3FrameCounter.cc
new file mode 100644
index 0000000000..8fee6afd42
--- /dev/null
+++ b/src/proxy/http3/Http3FrameCounter.cc
@@ -0,0 +1,59 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @section license License
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include "tsutil/Metrics.h"
+#include "proxy/http3/Http3.h"
+#include "proxy/http3/Http3FrameCounter.h"
+
+std::vector<Http3FrameType>
+Http3FrameCounter::interests()
+{
+  return {Http3FrameType::DATA,         Http3FrameType::HEADERS,      
Http3FrameType::X_RESERVED_1, Http3FrameType::CANCEL_PUSH,
+          Http3FrameType::SETTINGS,     Http3FrameType::PUSH_PROMISE, 
Http3FrameType::X_RESERVED_2, Http3FrameType::GOAWAY,
+          Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4, 
Http3FrameType::MAX_PUSH_ID,  Http3FrameType::X_MAX_DEFINED,
+          Http3FrameType::UNKNOWN};
+}
+
+Http3ErrorUPtr
+Http3FrameCounter::handle_frame(std::shared_ptr<const Http3Frame> frame, 
int32_t frame_seq, Http3StreamType s_type)
+{
+  Http3ErrorUPtr error  = Http3ErrorUPtr(nullptr);
+  Http3FrameType f_type = frame->type();
+  if (f_type > Http3FrameType::X_MAX_DEFINED) {
+    f_type = Http3FrameType::X_MAX_DEFINED;
+  }
+  this->_frame_counts_in[static_cast<int>(f_type)]++;
+  
Metrics::Counter::increment(http3_frame_metrics_in[static_cast<int>(f_type)]);
+
+  return error;
+}
+
+uint64_t
+Http3FrameCounter::get_count(uint64_t type) const
+{
+  if (type == 0x21) { // TS_SSN_INFO_RECEIVED_FRAME_COUNT_H3_UNKNOWN in 
apidefs.h.in
+    return 
this->_frame_counts_in[static_cast<int>(Http3FrameType::X_MAX_DEFINED)];
+  } else {
+    return this->_frame_counts_in[type];
+  }
+}
diff --git a/src/proxy/http3/Http3ProtocolEnforcer.cc 
b/src/proxy/http3/Http3ProtocolEnforcer.cc
index 23ad112d79..ee2b26addf 100644
--- a/src/proxy/http3/Http3ProtocolEnforcer.cc
+++ b/src/proxy/http3/Http3ProtocolEnforcer.cc
@@ -27,11 +27,10 @@
 std::vector<Http3FrameType>
 Http3ProtocolEnforcer::interests()
 {
-  return {Http3FrameType::DATA,          Http3FrameType::HEADERS,     
Http3FrameType::PRIORITY,
-          Http3FrameType::CANCEL_PUSH,   Http3FrameType::SETTINGS,    
Http3FrameType::PUSH_PROMISE,
-          Http3FrameType::X_RESERVED_1,  Http3FrameType::GOAWAY,      
Http3FrameType::X_RESERVED_2,
-          Http3FrameType::X_RESERVED_3,  Http3FrameType::MAX_PUSH_ID, 
Http3FrameType::DUPLICATE_PUSH_ID,
-          Http3FrameType::X_MAX_DEFINED, Http3FrameType::UNKNOWN};
+  return {Http3FrameType::DATA,         Http3FrameType::HEADERS,      
Http3FrameType::X_RESERVED_1, Http3FrameType::CANCEL_PUSH,
+          Http3FrameType::SETTINGS,     Http3FrameType::PUSH_PROMISE, 
Http3FrameType::X_RESERVED_2, Http3FrameType::GOAWAY,
+          Http3FrameType::X_RESERVED_3, Http3FrameType::X_RESERVED_4, 
Http3FrameType::MAX_PUSH_ID,  Http3FrameType::X_MAX_DEFINED,
+          Http3FrameType::UNKNOWN};
 }
 
 Http3ErrorUPtr
diff --git a/src/proxy/http3/Http3Session.cc b/src/proxy/http3/Http3Session.cc
index 61a3ee4dce..eb31215572 100644
--- a/src/proxy/http3/Http3Session.cc
+++ b/src/proxy/http3/Http3Session.cc
@@ -25,6 +25,7 @@
 #include "iocore/net/QUICSupport.h"
 
 #include "proxy/http3/Http3.h"
+#include "proxy/http3/Http3Types.h"
 
 //
 // HQSession
@@ -220,6 +221,23 @@ Http3Session::remote_qpack()
   return this->_remote_qpack;
 }
 
+Http3FrameCounter *
+Http3Session::get_received_frame_counter()
+{
+  return &this->_received_frame_counter;
+}
+
+bool
+Http3Session::is_protocol_framed() const
+{
+  return true;
+}
+uint64_t
+Http3Session::get_received_frame_count(uint64_t type) const
+{
+  return this->_received_frame_counter.get_count(type);
+}
+
 //
 // Http09Session
 //
diff --git a/src/proxy/http3/Http3Transaction.cc 
b/src/proxy/http3/Http3Transaction.cc
index 2e544c41f9..1b2388fcc4 100644
--- a/src/proxy/http3/Http3Transaction.cc
+++ b/src/proxy/http3/Http3Transaction.cc
@@ -413,6 +413,7 @@ Http3Transaction::Http3Transaction(Http3Session *session, 
QUICStreamVCAdapter::I
   this->_header_handler = new Http3HeaderVIOAdaptor(&this->_read_vio, 
http_type, session->remote_qpack(), stream_id);
   this->_data_handler   = new Http3StreamDataVIOAdaptor(&this->_read_vio);
 
+  this->_frame_dispatcher.add_handler(session->get_received_frame_counter());
   this->_frame_dispatcher.add_handler(this->_header_handler);
   this->_frame_dispatcher.add_handler(this->_data_handler);
 
diff --git a/tests/gold_tests/h2/gold/log.gold 
b/tests/gold_tests/h2/gold/log.gold
new file mode 100644
index 0000000000..23b348f90c
--- /dev/null
+++ b/tests/gold_tests/h2/gold/log.gold
@@ -0,0 +1,2 @@
+Global: event=TS_EVENT_HTTP_SSN_CLOSE
+``
diff --git a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt 
b/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
index b0ee883557..a03fa94037 100644
--- a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
+++ b/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
@@ -17,3 +17,4 @@
 
 add_autest_plugin(test_tsapi test_tsapi.cc)
 add_autest_plugin(test_TSHttpTxnServerAddrSet test_TSHttpTxnServerAddrSet.cc)
+add_autest_plugin(test_TSHttpSsnInfo test_TSHttpSsnInfo.cc)
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.cc 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.cc
new file mode 100644
index 0000000000..129f1f17b6
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.cc
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <cstdlib>
+
+#include <ts/ts.h>
+
+namespace
+{
+#define PINAME "test_TSHttpSsnInfo"
+char PIName[] = PINAME;
+
+DbgCtl dbg_ctl{PIName};
+
+// NOTE:  It's important to flush this after writing so that a gold test using 
this plugin can examine the log before TS
+// terminates.
+//
+std::fstream logFile;
+
+void
+handle_ssn_close(TSHttpSsn ssn)
+{
+  if (TSHttpSsnClientProtocolStackContains(ssn, "h2")) {
+    TSMgmtInt count[11];
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[0], 0);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[1], 1);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[2], 2);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[3], 3);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[4], 4);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[5], 5);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[6], 6);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[7], 7);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[8], 8);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[9], 9);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[10], 
TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN);
+
+    logFile << "H2 Frames Received:"
+            << "D" << count[0] << ","
+            << "H" << count[1] << ","
+            << "PR" << count[2] << ","
+            << "RS" << count[3] << ","
+            << "S" << count[4] << ","
+            << "PP" << count[5] << ","
+            << "P" << count[6] << ","
+            << "G" << count[7] << ","
+            << "WU" << count[8] << ","
+            << "C" << count[9] << ","
+            << "U" << count[10] << std::endl;
+  } else {
+    TSMgmtInt count[15];
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[0], 0);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[1], 1);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[2], 2);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[3], 3);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[4], 4);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[5], 5);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[6], 6);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[7], 7);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[8], 8);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[9], 9);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[10], 10);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[11], 11);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[12], 12);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[13], 13);
+    TSHttpSsnInfoIntGet(ssn, TS_SSN_INFO_RECEIVED_FRAME_COUNT, &count[14], 
TS_SSN_INFO_RECEIVED_FRAME_COUNT_H2_UNKNOWN);
+
+    logFile << "H3 Frames Received:"
+            << "D" << count[0] << ","
+            << "H" << count[1] << ","
+            << "Ra" << count[2] << ","
+            << "CP" << count[3] << ","
+            << "S" << count[4] << ","
+            << "PP" << count[5] << ","
+            << "Rb" << count[6] << ","
+            << "G" << count[7] << ","
+            << "Rc" << count[8] << ","
+            << "Rd" << count[9] << ","
+            << "UND" << count[10] << ","
+            << "UND" << count[11] << ","
+            << "UND" << count[12] << ","
+            << "MPI" << count[13] << ","
+            << "U" << count[14] << std::endl;
+  }
+
+  TSHttpSsnReenable(ssn, TS_EVENT_HTTP_CONTINUE);
+}
+
+int
+globalContFunc(TSCont, TSEvent event, void *eventData)
+{
+  logFile << "Global: event=" << TSHttpEventNameLookup(event) << std::endl;
+
+  Dbg(dbg_ctl, "Global: event=%s(%d) eventData=%p", 
TSHttpEventNameLookup(event), event, eventData);
+
+  switch (event) {
+  case TS_EVENT_HTTP_SSN_CLOSE:
+    handle_ssn_close(static_cast<TSHttpSsn>(eventData));
+    break;
+  default:
+    break;
+  } // end switch
+
+  return 0;
+}
+
+TSCont gCont;
+
+} // end anonymous namespace
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PIName;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "d...@trafficserver.apache.org";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError(PINAME ": Plugin registration failed");
+
+    return;
+  }
+
+  const char *fileSpec = std::getenv("OUTPUT_FILE");
+
+  if (nullptr == fileSpec) {
+    TSError(PINAME ": Environment variable OUTPUT_FILE not found.");
+
+    return;
+  }
+
+  // Disable output buffering for logFile, so that explicit flushing is not 
necessary.
+  logFile.rdbuf()->pubsetbuf(nullptr, 0);
+
+  logFile.open(fileSpec, std::ios::out);
+  if (!logFile.is_open()) {
+    TSError(PINAME ": could not open log file \"%s\"", fileSpec);
+
+    return;
+  }
+
+  // Mutex to protect the logFile object.
+  TSMutex mtx = TSMutexCreate();
+  gCont       = TSContCreate(globalContFunc, mtx);
+  TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, gCont);
+}
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py
new file mode 100644
index 0000000000..421e2aa720
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py
@@ -0,0 +1,103 @@
+'''
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you 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
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+
+Test.Summary = '''
+Test TS API to get H2 Session info and H2 metrics
+'''
+
+Test.SkipUnless(Condition.HasProgram("nghttp", "Nghttp need to be installed on 
system for this test to work"),)
+Test.ContinueOnFail = True
+
+# ----
+# Setup Origin Server
+# ----
+httpbin = Test.MakeHttpBinServer("httpbin")
+
+# 128ytes
+post_body = "0123456789abcdef" * 8
+post_body_file = open(os.path.join(Test.RunDirectory, "post_body"), "w")
+post_body_file.write(post_body)
+post_body_file.close()
+
+# ----
+# Setup ATS
+# ----
+if Condition.HasATSFeature('TS_USE_QUIC') and 
Condition.HasCurlFeature('http3'):
+    ts = Test.MakeATSProcess("ts", enable_tls=True, enable_quic=True)
+else:
+    ts = Test.MakeATSProcess("ts", enable_tls=True)
+
+# add ssl materials like key, certificates for the server
+ts.addDefaultSSLFiles()
+
+ts.Disk.remap_config.AddLines(['map /httpbin/ 
http://127.0.0.1:{0}/'.format(httpbin.Variables.Port)])
+
+ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem 
ssl_key_name=server.key')
+
+Test.PrepareTestPlugin(
+    os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', 
'.libs', 'test_TSHttpSsnInfo.so'), ts)
+
+ts.Disk.records_config.update(
+    {
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'http2|http3|quic|test_TSHttpSsnInfo',
+        'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+        'proxy.config.ssl.server.private_key.path': 
'{0}'.format(ts.Variables.SSLDir)
+    })
+
+# http2_info.so will output test logging to this file.
+log_path = os.path.join(ts.Variables.LOGDIR, 
"test_TSHttpSsnInfo_plugin_log.txt")
+Test.Env["OUTPUT_FILE"] = log_path
+
+# ----
+# Test Cases
+# ----
+
+# H2 SETTINGS, PRIORITY, HEADERS, CONTINUATION, DATA, GOAWAY
+tr = Test.AddTestRun()
+tr.TimeOut = 10
+tr.Processes.Default.Command = f"nghttp -vn --continuation 
'https://127.0.0.1:{ts.Variables.ssl_port}/httpbin/post' -d 'post_body'"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.StartBefore(httpbin, 
ready=When.PortOpen(httpbin.Variables.Port))
+tr.Processes.Default.StartBefore(Test.Processes.ts)
+tr.Processes.Default.Streams.stdout = "test_TSHttpSsnInfo_nghttp0.gold"
+tr.StillRunningAfter = httpbin
+tr.StillRunningAfter = ts
+
+# H3
+if Condition.HasATSFeature('TS_USE_QUIC') and 
Condition.HasCurlFeature('http3'):
+    tr = Test.AddTestRun()
+    tr.TimeOut = 10
+    tr.Processes.Default.Command = f"curl -k --http3 
'https://127.0.0.1:{ts.Variables.ssl_port}/httpbin/post' -d 'post_body'"
+    tr.Processes.Default.ReturnCode = 0
+    tr.Processes.Default.Streams.stdout = "test_TSHttpSsnInfo_curl0.gold"
+    tr.StillRunningAfter = httpbin
+    tr.StillRunningAfter = ts
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = "echo check log"
+tr.Processes.Default.ReturnCode = 0
+f = tr.Disk.File(log_path)
+f.Content = "test_TSHttpSsnInfo_plugin_log.gold"
+f.Content += Testers.ContainsExpression(
+    "H2 Frames Received:D1,H1,PR5,RS0,S2,PP0,P0,G1,WU0,C1,U0", "Expected 
numbers of frames should be received")
+# We cannot test this on H3 now because the test plugin does not work on H3 
sessions
+# f.Content += Testers.ContainsExpression("H3 Frames 
Received:D1,H1,Ra0,CP0,S1,PP0,Rb0,G0,Rc0,Rd0,UND0,UND0,UND0,MPI0,U0",
+#                                        "Expected numbers of frames should be 
received")
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_curl0.gold 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_curl0.gold
new file mode 100644
index 0000000000..cf637de588
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_curl0.gold
@@ -0,0 +1 @@
+``
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_nghttp0.gold 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_nghttp0.gold
new file mode 100644
index 0000000000..8fc661cb94
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_nghttp0.gold
@@ -0,0 +1,21 @@
+``
+[``] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
+          (dep_stream_id=0, weight=201, exclusive=0)
+[``] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
+          (dep_stream_id=0, weight=101, exclusive=0)
+[``] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
+          (dep_stream_id=0, weight=1, exclusive=0)
+[``] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
+          (dep_stream_id=7, weight=1, exclusive=0)
+[``] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
+          (dep_stream_id=3, weight=1, exclusive=0)
+[``] send HEADERS frame <length=``, flags=0x24, stream_id=13>
+          ; END_HEADERS | PRIORITY
+``
+[``] send DATA frame <length=``, flags=0x01, stream_id=13>
+``; END_STREAM
+``
+[``] recv (stream_id=13) :status: 431
+``
+``; END_STREAM
+``
diff --git 
a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_plugin_log.gold 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_plugin_log.gold
new file mode 100644
index 0000000000..23b348f90c
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo_plugin_log.gold
@@ -0,0 +1,2 @@
+Global: event=TS_EVENT_HTTP_SSN_CLOSE
+``


Reply via email to