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;