Let lossdetectioninterface::detectlosses to return a detectionstats which includes the max sequence reordering observed from acked packets. protected by existing flag --gfe2_reloadable_flag_quic_enable_loss_detection_experiment_at_gfe. The previous change, cl/309076773, used number of reordered _incoming_ packets to decide whether to send feedback to reordering_threshold_session. This CL changed it to use maximum reordering from _acked_ packets instead. PiperOrigin-RevId: 311210547 Change-Id: I1e1d4d5f842ee7036f23c3832ee3ece8ab38a497
diff --git a/quic/core/congestion_control/general_loss_algorithm.cc b/quic/core/congestion_control/general_loss_algorithm.cc index 1e67db6..f00045e 100644 --- a/quic/core/congestion_control/general_loss_algorithm.cc +++ b/quic/core/congestion_control/general_loss_algorithm.cc
@@ -23,13 +23,15 @@ packet_number_space_(NUM_PACKET_NUMBER_SPACES) {} // Uses nack counts to decide when packets are lost. -void GeneralLossAlgorithm::DetectLosses( +LossDetectionInterface::DetectionStats GeneralLossAlgorithm::DetectLosses( const QuicUnackedPacketMap& unacked_packets, QuicTime time, const RttStats& rtt_stats, QuicPacketNumber largest_newly_acked, const AckedPacketVector& packets_acked, LostPacketVector* packets_lost) { + DetectionStats detection_stats; + loss_detection_timeout_ = QuicTime::Zero(); if (!packets_acked.empty() && least_in_flight_.IsInitialized() && packets_acked.front().packet_number == least_in_flight_) { @@ -40,7 +42,7 @@ // do not use this optimization if largest_newly_acked is not the largest // packet in packets_acked. least_in_flight_ = largest_newly_acked + 1; - return; + return detection_stats; } // There is hole in acked_packets, increment least_in_flight_ if possible. for (const auto& acked : packets_acked) { @@ -50,6 +52,7 @@ ++least_in_flight_; } } + QuicTime::Delta max_rtt = std::max(rtt_stats.previous_srtt(), rtt_stats.latest_rtt()); max_rtt = std::max(kAlarmGranularity, max_rtt); @@ -77,9 +80,17 @@ // Skip packets of different packet number space. continue; } + if (!it->in_flight) { continue; } + + if (largest_newly_acked - packet_number > + detection_stats.sent_packets_max_sequence_reordering) { + detection_stats.sent_packets_max_sequence_reordering = + largest_newly_acked - packet_number; + } + // Packet threshold loss detection. // Skip packet threshold loss detection if largest_newly_acked is a runt. const bool skip_packet_threshold_detection = @@ -108,6 +119,8 @@ // There is no in flight packet. least_in_flight_ = largest_newly_acked + 1; } + + return detection_stats; } QuicTime GeneralLossAlgorithm::GetLossTimeout() const {
diff --git a/quic/core/congestion_control/general_loss_algorithm.h b/quic/core/congestion_control/general_loss_algorithm.h index b9e5d8e..ee8ba64 100644 --- a/quic/core/congestion_control/general_loss_algorithm.h +++ b/quic/core/congestion_control/general_loss_algorithm.h
@@ -31,12 +31,12 @@ Perspective /*perspective*/) override {} // Uses |largest_acked| and time to decide when packets are lost. - void DetectLosses(const QuicUnackedPacketMap& unacked_packets, - QuicTime time, - const RttStats& rtt_stats, - QuicPacketNumber largest_newly_acked, - const AckedPacketVector& packets_acked, - LostPacketVector* packets_lost) override; + DetectionStats DetectLosses(const QuicUnackedPacketMap& unacked_packets, + QuicTime time, + const RttStats& rtt_stats, + QuicPacketNumber largest_newly_acked, + const AckedPacketVector& packets_acked, + LostPacketVector* packets_lost) override; // Returns a non-zero value when the early retransmit timer is active. QuicTime GetLossTimeout() const override;
diff --git a/quic/core/congestion_control/general_loss_algorithm_test.cc b/quic/core/congestion_control/general_loss_algorithm_test.cc index 7bef933..1a58ade 100644 --- a/quic/core/congestion_control/general_loss_algorithm_test.cc +++ b/quic/core/congestion_control/general_loss_algorithm_test.cc
@@ -61,12 +61,25 @@ void VerifyLosses(uint64_t largest_newly_acked, const AckedPacketVector& packets_acked, const std::vector<uint64_t>& losses_expected) { + return VerifyLosses(largest_newly_acked, packets_acked, losses_expected, + quiche::QuicheOptional<QuicPacketCount>()); + } + + void VerifyLosses(uint64_t largest_newly_acked, + const AckedPacketVector& packets_acked, + const std::vector<uint64_t>& losses_expected, + quiche::QuicheOptional<QuicPacketCount> + max_sequence_reordering_expected) { unacked_packets_.MaybeUpdateLargestAckedOfPacketNumberSpace( APPLICATION_DATA, QuicPacketNumber(largest_newly_acked)); LostPacketVector lost_packets; - loss_algorithm_.DetectLosses(unacked_packets_, clock_.Now(), rtt_stats_, - QuicPacketNumber(largest_newly_acked), - packets_acked, &lost_packets); + LossDetectionInterface::DetectionStats stats = loss_algorithm_.DetectLosses( + unacked_packets_, clock_.Now(), rtt_stats_, + QuicPacketNumber(largest_newly_acked), packets_acked, &lost_packets); + if (max_sequence_reordering_expected.has_value()) { + EXPECT_EQ(stats.sent_packets_max_sequence_reordering, + max_sequence_reordering_expected.value()); + } ASSERT_EQ(losses_expected.size(), lost_packets.size()); for (size_t i = 0; i < losses_expected.size(); ++i) { EXPECT_EQ(lost_packets[i].packet_number, @@ -91,19 +104,19 @@ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(2)); packets_acked.push_back(AckedPacket( QuicPacketNumber(2), kMaxOutgoingPacketSize, QuicTime::Zero())); - VerifyLosses(2, packets_acked, std::vector<uint64_t>{}); + VerifyLosses(2, packets_acked, std::vector<uint64_t>{}, 1); packets_acked.clear(); // No loss on two acks. unacked_packets_.RemoveFromInFlight(QuicPacketNumber(3)); packets_acked.push_back(AckedPacket( QuicPacketNumber(3), kMaxOutgoingPacketSize, QuicTime::Zero())); - VerifyLosses(3, packets_acked, std::vector<uint64_t>{}); + VerifyLosses(3, packets_acked, std::vector<uint64_t>{}, 2); packets_acked.clear(); // Loss on three acks. unacked_packets_.RemoveFromInFlight(QuicPacketNumber(4)); packets_acked.push_back(AckedPacket( QuicPacketNumber(4), kMaxOutgoingPacketSize, QuicTime::Zero())); - VerifyLosses(4, packets_acked, {1}); + VerifyLosses(4, packets_acked, {1}, 3); EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout()); }
diff --git a/quic/core/congestion_control/loss_detection_interface.h b/quic/core/congestion_control/loss_detection_interface.h index 2ed7952..8d91976 100644 --- a/quic/core/congestion_control/loss_detection_interface.h +++ b/quic/core/congestion_control/loss_detection_interface.h
@@ -26,13 +26,19 @@ virtual void SetFromConfig(const QuicConfig& config, Perspective perspective) = 0; + struct QUIC_NO_EXPORT DetectionStats { + // Maximum sequence reordering observed in newly acked packets. + QuicPacketCount sent_packets_max_sequence_reordering = 0; + }; + // Called when a new ack arrives or the loss alarm fires. - virtual void DetectLosses(const QuicUnackedPacketMap& unacked_packets, - QuicTime time, - const RttStats& rtt_stats, - QuicPacketNumber largest_newly_acked, - const AckedPacketVector& packets_acked, - LostPacketVector* packets_lost) = 0; + virtual DetectionStats DetectLosses( + const QuicUnackedPacketMap& unacked_packets, + QuicTime time, + const RttStats& rtt_stats, + QuicPacketNumber largest_newly_acked, + const AckedPacketVector& packets_acked, + LostPacketVector* packets_lost) = 0; // Get the time the LossDetectionAlgorithm wants to re-evaluate losses. // Returns QuicTime::Zero if no alarm needs to be set.
diff --git a/quic/core/congestion_control/uber_loss_algorithm.cc b/quic/core/congestion_control/uber_loss_algorithm.cc index 5059a81..0f294d6 100644 --- a/quic/core/congestion_control/uber_loss_algorithm.cc +++ b/quic/core/congestion_control/uber_loss_algorithm.cc
@@ -27,13 +27,15 @@ } } -void UberLossAlgorithm::DetectLosses( +LossDetectionInterface::DetectionStats UberLossAlgorithm::DetectLosses( const QuicUnackedPacketMap& unacked_packets, QuicTime time, const RttStats& rtt_stats, QuicPacketNumber /*largest_newly_acked*/, const AckedPacketVector& packets_acked, LostPacketVector* packets_lost) { + DetectionStats overall_stats; + for (int8_t i = INITIAL_DATA; i < NUM_PACKET_NUMBER_SPACES; ++i) { const QuicPacketNumber largest_acked = unacked_packets.GetLargestAckedOfPacketNumberSpace( @@ -45,10 +47,16 @@ continue; } - general_loss_algorithms_[i].DetectLosses(unacked_packets, time, rtt_stats, - largest_acked, packets_acked, - packets_lost); + DetectionStats stats = general_loss_algorithms_[i].DetectLosses( + unacked_packets, time, rtt_stats, largest_acked, packets_acked, + packets_lost); + + overall_stats.sent_packets_max_sequence_reordering = + std::max(overall_stats.sent_packets_max_sequence_reordering, + stats.sent_packets_max_sequence_reordering); } + + return overall_stats; } QuicTime UberLossAlgorithm::GetLossTimeout() const {
diff --git a/quic/core/congestion_control/uber_loss_algorithm.h b/quic/core/congestion_control/uber_loss_algorithm.h index 1b1c1e8..86b6525 100644 --- a/quic/core/congestion_control/uber_loss_algorithm.h +++ b/quic/core/congestion_control/uber_loss_algorithm.h
@@ -51,12 +51,12 @@ Perspective perspective) override; // Detects lost packets. - void DetectLosses(const QuicUnackedPacketMap& unacked_packets, - QuicTime time, - const RttStats& rtt_stats, - QuicPacketNumber largest_newly_acked, - const AckedPacketVector& packets_acked, - LostPacketVector* packets_lost) override; + DetectionStats DetectLosses(const QuicUnackedPacketMap& unacked_packets, + QuicTime time, + const RttStats& rtt_stats, + QuicPacketNumber largest_newly_acked, + const AckedPacketVector& packets_acked, + LostPacketVector* packets_lost) override; // Returns the earliest time the early retransmit timer should be active. QuicTime GetLossTimeout() const override;
diff --git a/quic/core/congestion_control/uber_loss_algorithm_test.cc b/quic/core/congestion_control/uber_loss_algorithm_test.cc index a3daf09..6fd949f 100644 --- a/quic/core/congestion_control/uber_loss_algorithm_test.cc +++ b/quic/core/congestion_control/uber_loss_algorithm_test.cc
@@ -11,6 +11,7 @@ #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" #include "net/third_party/quiche/src/quic/test_tools/mock_clock.h" #include "net/third_party/quiche/src/quic/test_tools/quic_unacked_packet_map_peer.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h" namespace quic { namespace test { @@ -64,10 +65,23 @@ void VerifyLosses(uint64_t largest_newly_acked, const AckedPacketVector& packets_acked, const std::vector<uint64_t>& losses_expected) { + return VerifyLosses(largest_newly_acked, packets_acked, losses_expected, + quiche::QuicheOptional<QuicPacketCount>()); + } + + void VerifyLosses(uint64_t largest_newly_acked, + const AckedPacketVector& packets_acked, + const std::vector<uint64_t>& losses_expected, + quiche::QuicheOptional<QuicPacketCount> + max_sequence_reordering_expected) { LostPacketVector lost_packets; - loss_algorithm_.DetectLosses(*unacked_packets_, clock_.Now(), rtt_stats_, - QuicPacketNumber(largest_newly_acked), - packets_acked, &lost_packets); + LossDetectionInterface::DetectionStats stats = loss_algorithm_.DetectLosses( + *unacked_packets_, clock_.Now(), rtt_stats_, + QuicPacketNumber(largest_newly_acked), packets_acked, &lost_packets); + if (max_sequence_reordering_expected.has_value()) { + EXPECT_EQ(stats.sent_packets_max_sequence_reordering, + max_sequence_reordering_expected.value()); + } ASSERT_EQ(losses_expected.size(), lost_packets.size()); for (size_t i = 0; i < losses_expected.size(); ++i) { EXPECT_EQ(lost_packets[i].packet_number, @@ -98,7 +112,7 @@ unacked_packets_->MaybeUpdateLargestAckedOfPacketNumberSpace( HANDSHAKE_DATA, QuicPacketNumber(4)); // Verify no packet is detected lost. - VerifyLosses(4, packets_acked_, std::vector<uint64_t>{}); + VerifyLosses(4, packets_acked_, std::vector<uint64_t>{}, 0); EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout()); } @@ -114,7 +128,7 @@ unacked_packets_->MaybeUpdateLargestAckedOfPacketNumberSpace( APPLICATION_DATA, QuicPacketNumber(4)); // No packet loss by acking 4. - VerifyLosses(4, packets_acked_, std::vector<uint64_t>{}); + VerifyLosses(4, packets_acked_, std::vector<uint64_t>{}, 1); EXPECT_EQ(clock_.Now() + 1.25 * rtt_stats_.smoothed_rtt(), loss_algorithm_.GetLossTimeout()); @@ -122,14 +136,14 @@ AckPackets({6}); unacked_packets_->MaybeUpdateLargestAckedOfPacketNumberSpace( APPLICATION_DATA, QuicPacketNumber(6)); - VerifyLosses(6, packets_acked_, std::vector<uint64_t>{3}); + VerifyLosses(6, packets_acked_, std::vector<uint64_t>{3}, 3); EXPECT_EQ(clock_.Now() + 1.25 * rtt_stats_.smoothed_rtt(), loss_algorithm_.GetLossTimeout()); packets_acked_.clear(); clock_.AdvanceTime(1.25 * rtt_stats_.latest_rtt()); // Verify 5 will be early retransmitted. - VerifyLosses(6, packets_acked_, {5}); + VerifyLosses(6, packets_acked_, {5}, 1); } TEST_F(UberLossAlgorithmTest, ScenarioC) { @@ -151,14 +165,14 @@ unacked_packets_->MaybeUpdateLargestAckedOfPacketNumberSpace( HANDSHAKE_DATA, QuicPacketNumber(5)); // No packet loss by acking 5. - VerifyLosses(5, packets_acked_, std::vector<uint64_t>{}); + VerifyLosses(5, packets_acked_, std::vector<uint64_t>{}, 2); EXPECT_EQ(clock_.Now() + 1.25 * rtt_stats_.smoothed_rtt(), loss_algorithm_.GetLossTimeout()); packets_acked_.clear(); clock_.AdvanceTime(1.25 * rtt_stats_.latest_rtt()); // Verify 2 and 3 will be early retransmitted. - VerifyLosses(5, packets_acked_, std::vector<uint64_t>{2, 3}); + VerifyLosses(5, packets_acked_, std::vector<uint64_t>{2, 3}, 2); } // Regression test for b/133771183.
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h index 911ea6d..3b0c85d 100644 --- a/quic/core/quic_connection_stats.h +++ b/quic/core/quic_connection_stats.h
@@ -102,6 +102,9 @@ // Maximum reordering observed in microseconds int64_t max_time_reordering_us = 0; + // Maximum sequence reordering observed from acked packets. + QuicPacketCount sent_packets_max_sequence_reordering = 0; + // The following stats are used only in TcpCubicSender. // The number of loss events from TCP's perspective. Each loss event includes // one or more lost packets.
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index e9165b2..ffe43c6 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -2853,7 +2853,8 @@ LostPacketVector lost_packets; lost_packets.push_back(LostPacket(original, kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); QuicPacketNumber retransmission; // Packet 1 is short header for IETF QUIC because the encryption level @@ -3573,7 +3574,8 @@ lost_packets.push_back( LostPacket(QuicPacketNumber(2), kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); EXPECT_FALSE(QuicPacketCreatorPeer::SendVersionInPacket(creator_)); @@ -3655,7 +3657,8 @@ LostPacketVector lost_packets; lost_packets.push_back(LostPacket(last_packet - 1, kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AtLeast(1)); ProcessAckPacket(&nack_two); @@ -3775,7 +3778,8 @@ LostPacketVector lost_packets; lost_packets.push_back(LostPacket(last_packet - 1, kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0); ProcessAckPacket(&ack); @@ -3814,7 +3818,8 @@ lost_packets.push_back( LostPacket(QuicPacketNumber(2), kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(4), _, _)) .Times(1); @@ -3851,7 +3856,8 @@ LostPacketVector lost_packets; lost_packets.push_back(LostPacket(original, kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); // Packet 1 is short header for IETF QUIC because the encryption level // switched to ENCRYPTION_FORWARD_SECURE in SendStreamDataToPeer. @@ -4068,7 +4074,8 @@ LostPacket(QuicPacketNumber(i), kMaxOutgoingPacketSize)); } EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); ProcessAckPacket(&nack); @@ -7156,7 +7163,8 @@ lost_packets.push_back( LostPacket(QuicPacketNumber(1), kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); ProcessAckPacket(&ack); size_t padding_frame_count = writer_->padding_frames().size(); @@ -7501,7 +7509,8 @@ lost_packets.push_back( LostPacket(QuicPacketNumber(3), kMaxOutgoingPacketSize)); EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)) - .WillOnce(SetArgPointee<5>(lost_packets)); + .WillOnce(DoAll(SetArgPointee<5>(lost_packets), + Return(LossDetectionInterface::DetectionStats()))); EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); ProcessAckPacket(&nack_three);
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc index 0523b3c..5c99279 100644 --- a/quic/core/quic_sent_packet_manager.cc +++ b/quic/core/quic_sent_packet_manager.cc
@@ -934,9 +934,17 @@ packets_acked_.back().packet_number); largest_newly_acked_ = packets_acked_.back().packet_number; } - loss_algorithm_->DetectLosses(unacked_packets_, time, rtt_stats_, - largest_newly_acked_, packets_acked_, - &packets_lost_); + LossDetectionInterface::DetectionStats detection_stats = + loss_algorithm_->DetectLosses(unacked_packets_, time, rtt_stats_, + largest_newly_acked_, packets_acked_, + &packets_lost_); + + if (detection_stats.sent_packets_max_sequence_reordering > + stats_->sent_packets_max_sequence_reordering) { + stats_->sent_packets_max_sequence_reordering = + detection_stats.sent_packets_max_sequence_reordering; + } + for (const LostPacket& packet : packets_lost_) { QuicTransmissionInfo* info = unacked_packets_.GetMutableTransmissionInfo(packet.packet_number);
diff --git a/quic/core/quic_sent_packet_manager_test.cc b/quic/core/quic_sent_packet_manager_test.cc index 9d62bb6..e11f16e 100644 --- a/quic/core/quic_sent_packet_manager_test.cc +++ b/quic/core/quic_sent_packet_manager_test.cc
@@ -622,6 +622,7 @@ EXPECT_EQ(1u, stats_.packets_spuriously_retransmitted); EXPECT_EQ(1u, stats_.packets_lost); EXPECT_LT(QuicTime::Delta::Zero(), stats_.total_loss_detection_time); + EXPECT_LE(1u, stats_.sent_packets_max_sequence_reordering); } TEST_F(QuicSentPacketManagerTest, AckOriginalTransmission) {
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index cec38a1..f7e98ae 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -1264,7 +1264,7 @@ (const QuicConfig& config, Perspective perspective), (override)); - MOCK_METHOD(void, + MOCK_METHOD(DetectionStats, DetectLosses, (const QuicUnackedPacketMap& unacked_packets, QuicTime time,