Introduce support for basic WebTransport stats.

PiperOrigin-RevId: 564426331
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 8b4383f..10603ec 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -347,6 +347,7 @@
     "quic/core/uber_quic_stream_id_manager.h",
     "quic/core/uber_received_packet_manager.h",
     "quic/core/web_transport_interface.h",
+    "quic/core/web_transport_stats.h",
     "quic/platform/api/quic_bug_tracker.h",
     "quic/platform/api/quic_client_stats.h",
     "quic/platform/api/quic_export.h",
@@ -659,6 +660,7 @@
     "quic/core/tls_server_handshaker.cc",
     "quic/core/uber_quic_stream_id_manager.cc",
     "quic/core/uber_received_packet_manager.cc",
+    "quic/core/web_transport_stats.cc",
     "quic/platform/api/quic_socket_address.cc",
     "spdy/core/array_output_buffer.cc",
     "spdy/core/hpack/hpack_constants.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 35dfa26..2cd0ddd 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -347,6 +347,7 @@
     "src/quiche/quic/core/uber_quic_stream_id_manager.h",
     "src/quiche/quic/core/uber_received_packet_manager.h",
     "src/quiche/quic/core/web_transport_interface.h",
+    "src/quiche/quic/core/web_transport_stats.h",
     "src/quiche/quic/platform/api/quic_bug_tracker.h",
     "src/quiche/quic/platform/api/quic_client_stats.h",
     "src/quiche/quic/platform/api/quic_export.h",
@@ -659,6 +660,7 @@
     "src/quiche/quic/core/tls_server_handshaker.cc",
     "src/quiche/quic/core/uber_quic_stream_id_manager.cc",
     "src/quiche/quic/core/uber_received_packet_manager.cc",
+    "src/quiche/quic/core/web_transport_stats.cc",
     "src/quiche/quic/platform/api/quic_socket_address.cc",
     "src/quiche/spdy/core/array_output_buffer.cc",
     "src/quiche/spdy/core/hpack/hpack_constants.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 22ece80..f41f490 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -346,6 +346,7 @@
     "quiche/quic/core/uber_quic_stream_id_manager.h",
     "quiche/quic/core/uber_received_packet_manager.h",
     "quiche/quic/core/web_transport_interface.h",
+    "quiche/quic/core/web_transport_stats.h",
     "quiche/quic/platform/api/quic_bug_tracker.h",
     "quiche/quic/platform/api/quic_client_stats.h",
     "quiche/quic/platform/api/quic_export.h",
@@ -658,6 +659,7 @@
     "quiche/quic/core/tls_server_handshaker.cc",
     "quiche/quic/core/uber_quic_stream_id_manager.cc",
     "quiche/quic/core/uber_received_packet_manager.cc",
+    "quiche/quic/core/web_transport_stats.cc",
     "quiche/quic/platform/api/quic_socket_address.cc",
     "quiche/spdy/core/array_output_buffer.cc",
     "quiche/spdy/core/hpack/hpack_constants.cc",
diff --git a/quiche/quic/core/http/web_transport_http3.h b/quiche/quic/core/http/web_transport_http3.h
index bb5444a..14b4a1c 100644
--- a/quiche/quic/core/http/web_transport_http3.h
+++ b/quiche/quic/core/http/web_transport_http3.h
@@ -17,6 +17,7 @@
 #include "quiche/quic/core/quic_stream.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/core/web_transport_stats.h"
 #include "quiche/common/platform/api/quiche_mem_slice.h"
 #include "quiche/common/quiche_callbacks.h"
 #include "quiche/web_transport/web_transport.h"
@@ -90,6 +91,13 @@
   QuicByteCount GetMaxDatagramSize() const override;
   void SetDatagramMaxTimeInQueue(absl::Duration max_time_in_queue) override;
 
+  webtransport::DatagramStats GetDatagramStats() override {
+    return WebTransportDatagramStatsForQuicSession(*session_);
+  }
+  webtransport::SessionStats GetSessionStats() override {
+    return WebTransportStatsForQuicSession(*session_);
+  }
+
   void NotifySessionDraining() override;
   void SetOnDraining(quiche::SingleUseCallback<void()> callback) override {
     drain_callback_ = std::move(callback);
diff --git a/quiche/quic/core/quic_datagram_queue.cc b/quiche/quic/core/quic_datagram_queue.cc
index 9796587..0b7b3f5 100644
--- a/quiche/quic/core/quic_datagram_queue.cc
+++ b/quiche/quic/core/quic_datagram_queue.cc
@@ -22,8 +22,7 @@
                                      std::unique_ptr<Observer> observer)
     : session_(session),
       clock_(session->connection()->clock()),
-      observer_(std::move(observer)),
-      force_flush_(false) {}
+      observer_(std::move(observer)) {}
 
 MessageStatus QuicDatagramQueue::SendOrQueueDatagram(
     quiche::QuicheMemSlice datagram) {
@@ -92,6 +91,7 @@
 void QuicDatagramQueue::RemoveExpiredDatagrams() {
   QuicTime now = clock_->ApproximateNow();
   while (!queue_.empty() && queue_.front().expiry <= now) {
+    ++expired_datagram_count_;
     queue_.pop_front();
     if (observer_) {
       observer_->OnDatagramProcessed(absl::nullopt);
diff --git a/quiche/quic/core/quic_datagram_queue.h b/quiche/quic/core/quic_datagram_queue.h
index 834ae54..ca79265 100644
--- a/quiche/quic/core/quic_datagram_queue.h
+++ b/quiche/quic/core/quic_datagram_queue.h
@@ -5,6 +5,7 @@
 #ifndef QUICHE_QUIC_CORE_QUIC_DATAGRAM_QUEUE_H_
 #define QUICHE_QUIC_CORE_QUIC_DATAGRAM_QUEUE_H_
 
+#include <cstdint>
 #include <memory>
 
 #include "absl/types/optional.h"
@@ -69,8 +70,8 @@
   void SetForceFlush(bool force_flush) { force_flush_ = force_flush; }
 
   size_t queue_size() { return queue_.size(); }
-
   bool empty() { return queue_.empty(); }
+  uint64_t expired_datagram_count() const { return expired_datagram_count_; }
 
  private:
   struct QUICHE_EXPORT Datagram {
@@ -87,7 +88,8 @@
   QuicTime::Delta max_time_in_queue_ = QuicTime::Delta::Zero();
   quiche::QuicheCircularDeque<Datagram> queue_;
   std::unique_ptr<Observer> observer_;
-  bool force_flush_;
+  uint64_t expired_datagram_count_ = 0;
+  bool force_flush_ = false;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_generic_session.h b/quiche/quic/core/quic_generic_session.h
index 5588320..773f170 100644
--- a/quiche/quic/core/quic_generic_session.h
+++ b/quiche/quic/core/quic_generic_session.h
@@ -23,6 +23,7 @@
 #include "quiche/quic/core/quic_stream.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/core/web_transport_stats.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/common/quiche_callbacks.h"
 #include "quiche/web_transport/web_transport.h"
@@ -95,6 +96,12 @@
   void SetDatagramMaxTimeInQueue(absl::Duration max_time_in_queue) override {
     datagram_queue()->SetMaxTimeInQueue(QuicTimeDelta(max_time_in_queue));
   }
+  webtransport::DatagramStats GetDatagramStats() override {
+    return WebTransportDatagramStatsForQuicSession(*this);
+  }
+  webtransport::SessionStats GetSessionStats() override {
+    return WebTransportStatsForQuicSession(*this);
+  }
   void NotifySessionDraining() override {}
   void SetOnDraining(quiche::SingleUseCallback<void()>) override {}
 
diff --git a/quiche/quic/core/quic_generic_session_test.cc b/quiche/quic/core/quic_generic_session_test.cc
index fced0ec..87c9176 100644
--- a/quiche/quic/core/quic_generic_session_test.cc
+++ b/quiche/quic/core/quic_generic_session_test.cc
@@ -7,16 +7,24 @@
 
 #include "quiche/quic/core/quic_generic_session.h"
 
+#include <cstddef>
+#include <cstring>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "quiche/quic/core/crypto/quic_compressed_certs_cache.h"
 #include "quiche/quic/core/crypto/quic_crypto_client_config.h"
 #include "quiche/quic/core/crypto/quic_crypto_server_config.h"
+#include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_constants.h"
 #include "quiche/quic/core/quic_datagram_queue.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/web_transport_interface.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/crypto_test_utils.h"
 #include "quiche/quic/test_tools/quic_session_peer.h"
@@ -321,5 +329,67 @@
   EXPECT_TRUE(client_->session()->CanOpenNextOutgoingUnidirectionalStream());
 }
 
+TEST_F(QuicGenericSessionTest, ExpireDatagrams) {
+  CreateDefaultEndpoints(kEchoServer);
+  WireUpEndpoints();
+  RunHandshake();
+
+  // Set the datagrams to expire very soon.
+  client_->session()->SetDatagramMaxTimeInQueue(
+      (0.2 * simulator::TestHarness::kRtt).ToAbsl());
+  for (int i = 0; i < 1000; i++) {
+    client_->session()->SendOrQueueDatagram(std::string(
+        client_->session()->GetGuaranteedLargestMessagePayload(), 'a'));
+  }
+
+  size_t received = 0;
+  EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
+      .WillRepeatedly(
+          [&received](absl::string_view /*datagram*/) { received++; });
+  ASSERT_TRUE(test_harness_.simulator().RunUntilOrTimeout(
+      [this]() { return client_->total_datagrams_processed() >= 1000; },
+      3 * simulator::TestHarness::kServerBandwidth.TransferTime(
+              1000 * kMaxOutgoingPacketSize)));
+  // Allow extra round-trips for the final flight of datagrams to arrive back.
+  test_harness_.simulator().RunFor(2 * simulator::TestHarness::kRtt);
+  EXPECT_LT(received, 500);
+  EXPECT_EQ(received + client_->session()->GetDatagramStats().expired_outgoing,
+            1000);
+}
+
+TEST_F(QuicGenericSessionTest, LoseDatagrams) {
+  CreateDefaultEndpoints(kEchoServer);
+  test_harness_.WireUpEndpointsWithLoss(/*lose_every_n=*/4);
+  RunHandshake();
+
+  // Set the datagrams to effectively never expire.
+  client_->session()->SetDatagramMaxTimeInQueue(
+      (10000 * simulator::TestHarness::kRtt).ToAbsl());
+  for (int i = 0; i < 1000; i++) {
+    client_->session()->SendOrQueueDatagram(std::string(
+        client_->session()->GetGuaranteedLargestMessagePayload(), 'a'));
+  }
+
+  size_t received = 0;
+  EXPECT_CALL(*client_->visitor(), OnDatagramReceived(_))
+      .WillRepeatedly(
+          [&received](absl::string_view /*datagram*/) { received++; });
+  ASSERT_TRUE(test_harness_.simulator().RunUntilOrTimeout(
+      [this]() { return client_->total_datagrams_processed() >= 1000; },
+      3 * simulator::TestHarness::kServerBandwidth.TransferTime(
+              1000 * kMaxOutgoingPacketSize)));
+  // Allow extra round-trips for the final flight of datagrams to arrive back.
+  test_harness_.simulator().RunFor(2 * simulator::TestHarness::kRtt);
+
+  QuicPacketCount client_lost =
+      client_->session()->GetDatagramStats().lost_outgoing;
+  QuicPacketCount server_lost =
+      server_->session()->GetDatagramStats().lost_outgoing;
+  EXPECT_LT(received, 800u);
+  EXPECT_GT(client_lost, 100u);
+  EXPECT_GT(server_lost, 100u);
+  EXPECT_EQ(received + client_lost + server_lost, 1000u);
+}
+
 }  // namespace
 }  // namespace quic::test
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index e140bb4..f86e722 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -2310,6 +2310,7 @@
 
 void QuicSession::OnFrameLost(const QuicFrame& frame) {
   if (frame.type == MESSAGE_FRAME) {
+    ++total_datagrams_lost_;
     OnMessageLost(frame.message_frame->message_id);
     return;
   }
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 79b7396..db9475a 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -656,6 +656,15 @@
     datagram_queue_.SetForceFlush(force_flush);
   }
 
+  // Returns the total number of expired datagrams dropped in the default
+  // datagram queue.
+  uint64_t expired_datagrams_in_default_queue() const {
+    return datagram_queue_.expired_datagram_count();
+  }
+
+  // Returns the total datagrams ever declared lost within the session.
+  uint64_t total_datagrams_lost() const { return total_datagrams_lost_; }
+
   // Find stream with |id|, returns nullptr if the stream does not exist or
   // closed. static streams and zombie streams are not considered active
   // streams.
@@ -1005,6 +1014,9 @@
   // The buffer used to queue the DATAGRAM frames.
   QuicDatagramQueue datagram_queue_;
 
+  // Total number of datagram frames declared lost within the session.
+  uint64_t total_datagrams_lost_ = 0;
+
   // TODO(fayang): switch to linked_hash_set when chromium supports it. The bool
   // is not used here.
   // List of streams with pending retransmissions.
diff --git a/quiche/quic/core/web_transport_stats.cc b/quiche/quic/core/web_transport_stats.cc
new file mode 100644
index 0000000..15797b2
--- /dev/null
+++ b/quiche/quic/core/web_transport_stats.cc
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/web_transport_stats.h"
+
+#include "absl/time/time.h"
+#include "quiche/quic/core/congestion_control/rtt_stats.h"
+#include "quiche/quic/core/quic_session.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace quic {
+
+webtransport::DatagramStats WebTransportDatagramStatsForQuicSession(
+    const QuicSession& session) {
+  webtransport::DatagramStats result;
+  result.expired_outgoing = session.expired_datagrams_in_default_queue();
+  result.lost_outgoing = session.total_datagrams_lost();
+  return result;
+}
+
+webtransport::SessionStats WebTransportStatsForQuicSession(
+    const QuicSession& session) {
+  const RttStats* rtt_stats =
+      session.connection()->sent_packet_manager().GetRttStats();
+  webtransport::SessionStats result;
+  result.min_rtt = rtt_stats->min_rtt().ToAbsl();
+  result.smoothed_rtt = rtt_stats->smoothed_rtt().ToAbsl();
+  result.rtt_variation = rtt_stats->mean_deviation().ToAbsl();
+  result.estimated_send_rate_bps = session.connection()
+                                       ->sent_packet_manager()
+                                       .BandwidthEstimate()
+                                       .ToBitsPerSecond();
+  result.datagram_stats = WebTransportDatagramStatsForQuicSession(session);
+  return result;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/web_transport_stats.h b/quiche/quic/core/web_transport_stats.h
new file mode 100644
index 0000000..5e39c28
--- /dev/null
+++ b/quiche/quic/core/web_transport_stats.h
@@ -0,0 +1,22 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_WEB_TRANSPORT_STATS_H_
+#define QUICHE_QUIC_CORE_WEB_TRANSPORT_STATS_H_
+
+#include "quiche/quic/core/quic_session.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace quic {
+
+QUICHE_EXPORT webtransport::DatagramStats
+WebTransportDatagramStatsForQuicSession(const QuicSession& session);
+
+QUICHE_EXPORT webtransport::SessionStats WebTransportStatsForQuicSession(
+    const QuicSession& session);
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_WEB_TRANSPORT_STATS_H_
diff --git a/quiche/quic/test_tools/simulator/test_harness.cc b/quiche/quic/test_tools/simulator/test_harness.cc
index 1dfc8a2..1b58c2e 100644
--- a/quiche/quic/test_tools/simulator/test_harness.cc
+++ b/quiche/quic/test_tools/simulator/test_harness.cc
@@ -4,13 +4,39 @@
 
 #include "quiche/quic/test_tools/simulator/test_harness.h"
 
+#include <memory>
+#include <string>
+
+#include "absl/strings/str_cat.h"
 #include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/test_tools/simulator/packet_filter.h"
+#include "quiche/quic/test_tools/simulator/port.h"
 #include "quiche/quic/test_tools/simulator/quic_endpoint_base.h"
+#include "quiche/quic/test_tools/simulator/simulator.h"
 
 namespace quic::simulator {
 
+class LoseEveryNFilter : public PacketFilter {
+ public:
+  LoseEveryNFilter(Endpoint* input, int n)
+      : PacketFilter(input->simulator(),
+                     absl::StrCat(input->name(), " (loss filter)"), input),
+        n_(n) {}
+
+ protected:
+  bool FilterPacket(const Packet& /*packet*/) {
+    ++counter_;
+    return (counter_ % n_) != 0;
+  }
+
+ private:
+  int n_;
+  int counter_ = 0;
+};
+
 QuicEndpointWithConnection::QuicEndpointWithConnection(
     Simulator* simulator, const std::string& name, const std::string& peer_name,
     Perspective perspective, const ParsedQuicVersionVector& supported_versions)
@@ -32,4 +58,13 @@
                        kServerPropagationDelay);
 }
 
+void TestHarness::WireUpEndpointsWithLoss(int lose_every_n) {
+  client_filter_ = std::make_unique<LoseEveryNFilter>(client_, lose_every_n);
+  server_filter_ = std::make_unique<LoseEveryNFilter>(server_, lose_every_n);
+  client_link_.emplace(client_filter_.get(), switch_.port(1), kClientBandwidth,
+                       kClientPropagationDelay);
+  server_link_.emplace(server_filter_.get(), switch_.port(2), kServerBandwidth,
+                       kServerPropagationDelay);
+}
+
 }  // namespace quic::simulator
diff --git a/quiche/quic/test_tools/simulator/test_harness.h b/quiche/quic/test_tools/simulator/test_harness.h
index 681cfa3..135d744 100644
--- a/quiche/quic/test_tools/simulator/test_harness.h
+++ b/quiche/quic/test_tools/simulator/test_harness.h
@@ -6,11 +6,16 @@
 #define QUICHE_QUIC_TEST_TOOLS_SIMULATOR_TEST_HARNESS_H_
 
 #include <memory>
+#include <string>
 
 #include "absl/types/optional.h"
+#include "quiche/quic/core/quic_bandwidth.h"
 #include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/test_tools/simulator/link.h"
+#include "quiche/quic/test_tools/simulator/packet_filter.h"
 #include "quiche/quic/test_tools/simulator/port.h"
 #include "quiche/quic/test_tools/simulator/quic_endpoint_base.h"
 #include "quiche/quic/test_tools/simulator/simulator.h"
@@ -61,6 +66,9 @@
   // set_client/set_server are called.
   void WireUpEndpoints();
 
+  // Same as above, except triggers loss of every Nth packet in both directions.
+  void WireUpEndpointsWithLoss(int lose_every_n);
+
   // A convenience wrapper around Simulator::RunUntilOrTimeout().
   template <class TerminationPredicate>
   bool RunUntilWithDefaultTimeout(TerminationPredicate termination_predicate) {
@@ -73,6 +81,8 @@
   Switch switch_;
   absl::optional<SymmetricLink> client_link_;
   absl::optional<SymmetricLink> server_link_;
+  std::unique_ptr<PacketFilter> client_filter_;
+  std::unique_ptr<PacketFilter> server_filter_;
 
   Endpoint* client_;
   Endpoint* server_;
diff --git a/quiche/web_transport/test_tools/mock_web_transport.h b/quiche/web_transport/test_tools/mock_web_transport.h
index e102a08..8855c9c 100644
--- a/quiche/web_transport/test_tools/mock_web_transport.h
+++ b/quiche/web_transport/test_tools/mock_web_transport.h
@@ -7,8 +7,19 @@
 #ifndef QUICHE_WEB_TRANSPORT_TEST_TOOLS_MOCK_WEB_TRANSPORT_H_
 #define QUICHE_WEB_TRANSPORT_TEST_TOOLS_MOCK_WEB_TRANSPORT_H_
 
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+#include "quiche/common/platform/api/quiche_export.h"
 #include "quiche/common/platform/api/quiche_test.h"
 #include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_stream.h"
 #include "quiche/web_transport/web_transport.h"
 
 namespace webtransport {
@@ -75,6 +86,8 @@
   MOCK_METHOD(uint64_t, GetMaxDatagramSize, (), (const, override));
   MOCK_METHOD(void, SetDatagramMaxTimeInQueue,
               (absl::Duration max_time_in_queue), (override));
+  MOCK_METHOD(DatagramStats, GetDatagramStats, (), (override));
+  MOCK_METHOD(SessionStats, GetSessionStats, (), (override));
   MOCK_METHOD(void, NotifySessionDraining, (), (override));
   MOCK_METHOD(void, SetOnDraining, (quiche::SingleUseCallback<void()>),
               (override));
diff --git a/quiche/web_transport/web_transport.h b/quiche/web_transport/web_transport.h
index 6cb7811..741be73 100644
--- a/quiche/web_transport/web_transport.h
+++ b/quiche/web_transport/web_transport.h
@@ -9,6 +9,7 @@
 #define QUICHE_WEB_TRANSPORT_WEB_TRANSPORT_H_
 
 #include <cstddef>
+#include <cstdint>
 #include <memory>
 #include <string>
 
@@ -63,6 +64,31 @@
   kBidirectional,
 };
 
+// Based on
+// https://w3c.github.io/webtransport/#dictdef-webtransportdatagramstats.
+struct QUICHE_EXPORT DatagramStats {
+  uint64_t expired_outgoing;
+  uint64_t lost_outgoing;
+
+  // droppedIncoming is not present, since in the C++ API, we immediately
+  // deliver datagrams via callback, meaning there is no queue where things
+  // would be dropped.
+};
+
+// Based on https://w3c.github.io/webtransport/#web-transport-stats
+// Note that this is currently not a complete implementation of that API, as
+// some of those still need to be clarified in
+// https://github.com/w3c/webtransport/issues/537
+struct QUICHE_EXPORT SessionStats {
+  absl::Duration min_rtt;
+  absl::Duration smoothed_rtt;
+  absl::Duration rtt_variation;
+
+  uint64_t estimated_send_rate_bps;  // In bits per second.
+
+  DatagramStats datagram_stats;
+};
+
 // The stream visitor is an application-provided object that gets notified about
 // events related to a WebTransport stream.  The visitor object is owned by the
 // stream itself, meaning that if the stream is ever fully closed, the visitor
@@ -215,6 +241,10 @@
   // being silently dropped.
   virtual void SetDatagramMaxTimeInQueue(absl::Duration max_time_in_queue) = 0;
 
+  // Returns stats that generally follow the semantics of W3C WebTransport API.
+  virtual DatagramStats GetDatagramStats() = 0;
+  virtual SessionStats GetSessionStats() = 0;
+
   // Sends a DRAIN_WEBTRANSPORT_SESSION capsule or an equivalent signal to the
   // peer indicating that the session is draining.
   virtual void NotifySessionDraining() = 0;