gfe-relnote: For QUIC BBR v1 and v2, only produce one bandwidth/rtt sample per congestion event. Protected by --gfe2_reloadable_flag_quic_one_bw_sample_per_ack_event.

PiperOrigin-RevId: 286899555
Change-Id: I48ddd4ca5c6988e29ef2fb45b9cc89e12da2bcd5
diff --git a/quic/core/congestion_control/bandwidth_sampler.cc b/quic/core/congestion_control/bandwidth_sampler.cc
index 8ad5459..21705a5 100644
--- a/quic/core/congestion_control/bandwidth_sampler.cc
+++ b/quic/core/congestion_control/bandwidth_sampler.cc
@@ -34,6 +34,16 @@
   if (aggregation_epoch_bytes_ <=
       GetQuicFlag(FLAGS_quic_ack_aggregation_bandwidth_threshold) *
           expected_bytes_acked) {
+    QUIC_DVLOG(3) << "Starting a new aggregation epoch because "
+                     "aggregation_epoch_bytes_ "
+                  << aggregation_epoch_bytes_
+                  << " is smaller than expected. "
+                     "quic_ack_aggregation_bandwidth_threshold:"
+                  << GetQuicFlag(FLAGS_quic_ack_aggregation_bandwidth_threshold)
+                  << ", expected_bytes_acked:" << expected_bytes_acked
+                  << ", bandwidth_estimate:" << bandwidth_estimate
+                  << ", aggregation_duration:"
+                  << (ack_time - aggregation_epoch_start_time_);
     // Reset to start measuring a new aggregation epoch.
     aggregation_epoch_bytes_ = bytes_acked;
     aggregation_epoch_start_time_ = ack_time;
@@ -46,6 +56,13 @@
   // Compute how many extra bytes were delivered vs max bandwidth.
   QuicByteCount extra_bytes_acked =
       aggregation_epoch_bytes_ - expected_bytes_acked;
+  QUIC_DVLOG(3) << "Updating MaxAckHeight. ack_time:" << ack_time
+                << ", round trip count:" << round_trip_count
+                << ", bandwidth_estimate:" << bandwidth_estimate
+                << ", bytes_acked:" << bytes_acked
+                << ", expected_bytes_acked:" << expected_bytes_acked
+                << ", aggregation_epoch_bytes_:" << aggregation_epoch_bytes_
+                << ", extra_bytes_acked:" << extra_bytes_acked;
   max_ack_height_filter_.Update(extra_bytes_acked, round_trip_count);
   return extra_bytes_acked;
 }
@@ -69,6 +86,9 @@
     QUIC_RELOADABLE_FLAG_COUNT(
         quic_bw_sampler_remove_packets_once_per_congestion_event2);
   }
+  if (one_bw_sample_per_ack_event_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_one_bw_sample_per_ack_event);
+  }
 }
 
 BandwidthSampler::~BandwidthSampler() {}
@@ -127,6 +147,85 @@
                            "in it.";
 }
 
+BandwidthSamplerInterface::CongestionEventSample
+BandwidthSampler::OnCongestionEvent(QuicTime ack_time,
+                                    const AckedPacketVector& acked_packets,
+                                    const LostPacketVector& lost_packets,
+                                    QuicBandwidth max_bandwidth,
+                                    QuicBandwidth est_bandwidth_upper_bound,
+                                    QuicRoundTripCount round_trip_count) {
+  DCHECK(one_bw_sample_per_ack_event());
+
+  CongestionEventSample event_sample;
+
+  SendTimeState last_lost_packet_send_state;
+
+  for (const LostPacket& packet : lost_packets) {
+    SendTimeState send_state =
+        OnPacketLost(packet.packet_number, packet.bytes_lost);
+    if (send_state.is_valid) {
+      last_lost_packet_send_state = send_state;
+    }
+  }
+
+  if (acked_packets.empty()) {
+    // Only populate send state for a loss-only event.
+    event_sample.last_packet_send_state = last_lost_packet_send_state;
+    return event_sample;
+  }
+
+  SendTimeState last_acked_packet_send_state;
+  for (const auto& packet : acked_packets) {
+    BandwidthSample sample =
+        OnPacketAcknowledged(ack_time, packet.packet_number);
+    if (!sample.state_at_send.is_valid) {
+      continue;
+    }
+
+    last_acked_packet_send_state = sample.state_at_send;
+
+    if (!sample.rtt.IsZero()) {
+      event_sample.sample_rtt = std::min(event_sample.sample_rtt, sample.rtt);
+    }
+    if (sample.bandwidth > event_sample.sample_max_bandwidth) {
+      event_sample.sample_max_bandwidth = sample.bandwidth;
+      event_sample.sample_is_app_limited = sample.state_at_send.is_app_limited;
+    }
+    const QuicByteCount inflight_sample =
+        total_bytes_acked() - last_acked_packet_send_state.total_bytes_acked;
+    if (inflight_sample > event_sample.sample_max_inflight) {
+      event_sample.sample_max_inflight = inflight_sample;
+    }
+  }
+
+  if (!last_lost_packet_send_state.is_valid) {
+    event_sample.last_packet_send_state = last_acked_packet_send_state;
+  } else if (!last_acked_packet_send_state.is_valid) {
+    event_sample.last_packet_send_state = last_lost_packet_send_state;
+  } else {
+    // If two packets are inflight and an alarm is armed to lose a packet and it
+    // wakes up late, then the first of two in flight packets could have been
+    // acknowledged before the wakeup, which re-evaluates loss detection, and
+    // could declare the later of the two lost. However, this is an edge case
+    // that should not happen in the test environments, hence the DCHECK.
+    DCHECK(lost_packets.back().packet_number <
+           acked_packets.back().packet_number)
+        << "Largest lost packet should be less than largest acked packet: "
+        << lost_packets.back().packet_number << " vs. "
+        << acked_packets.back().packet_number;
+    event_sample.last_packet_send_state =
+        lost_packets.back().packet_number > acked_packets.back().packet_number
+            ? last_lost_packet_send_state
+            : last_acked_packet_send_state;
+  }
+
+  max_bandwidth = std::max(max_bandwidth, event_sample.sample_max_bandwidth);
+  event_sample.extra_acked = OnAckEventEnd(
+      std::min(est_bandwidth_upper_bound, max_bandwidth), round_trip_count);
+
+  return event_sample;
+}
+
 QuicByteCount BandwidthSampler::OnAckEventEnd(
     QuicBandwidth bandwidth_estimate,
     QuicRoundTripCount round_trip_count) {
diff --git a/quic/core/congestion_control/bandwidth_sampler.h b/quic/core/congestion_control/bandwidth_sampler.h
index d2e0c7b..d93af36 100644
--- a/quic/core/congestion_control/bandwidth_sampler.h
+++ b/quic/core/congestion_control/bandwidth_sampler.h
@@ -14,6 +14,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_unacked_packet_map.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 
 namespace quic {
 
@@ -151,10 +152,47 @@
   // Notifies the sampler that the |packet_number| is acknowledged. Returns a
   // bandwidth sample. If no bandwidth sample is available,
   // QuicBandwidth::Zero() is returned.
+  // TODO(wub): Remove when deprecating --quic_one_bw_sample_per_ack_event.
   virtual BandwidthSample OnPacketAcknowledged(
       QuicTime ack_time,
       QuicPacketNumber packet_number) = 0;
 
+  struct QUIC_NO_EXPORT CongestionEventSample {
+    // The maximum bandwidth sample from all acked packets.
+    // QuicBandwidth::Zero() if no samples are available.
+    QuicBandwidth sample_max_bandwidth = QuicBandwidth::Zero();
+    // Whether |sample_max_bandwidth| is from a app-limited sample.
+    bool sample_is_app_limited = false;
+    // The minimum rtt sample from all acked packets.
+    // QuicTime::Delta::Infinite() if no samples are available.
+    QuicTime::Delta sample_rtt = QuicTime::Delta::Infinite();
+    // For each packet p in acked packets, this is the max value of INFLIGHT(p),
+    // where INFLIGHT(p) is the number of bytes acked while p is inflight.
+    QuicByteCount sample_max_inflight = 0;
+    // The send state of the largest packet in acked_packets, unless it is
+    // empty. If acked_packets is empty, it's the send state of the largest
+    // packet in lost_packets.
+    SendTimeState last_packet_send_state;
+    // The number of extra bytes acked from this ack event, compared to what is
+    // expected from the flow's bandwidth. Larger value means more ack
+    // aggregation.
+    QuicByteCount extra_acked = 0;
+  };
+  // Notifies the sampler that at |ack_time|, all packets in |acked_packets|
+  // have been acked, and all packets in |lost_packets| have been lost.
+  // See the comments in CongestionEventSample for the return value.
+  // |max_bandwidth| is the windowed maximum observed bandwidth.
+  // |est_bandwidth_upper_bound| is an upper bound of estimated bandwidth used
+  // to calculate extra_acked.
+  // Only used when --quic_one_bw_sample_per_ack_event=true.
+  virtual CongestionEventSample OnCongestionEvent(
+      QuicTime ack_time,
+      const AckedPacketVector& acked_packets,
+      const LostPacketVector& lost_packets,
+      QuicBandwidth max_bandwidth,
+      QuicBandwidth est_bandwidth_upper_bound,
+      QuicRoundTripCount round_trip_count) = 0;
+
   // Informs the sampler that a packet is considered lost and it should no
   // longer keep track of it.
   virtual SendTimeState OnPacketLost(QuicPacketNumber packet_number,
@@ -272,6 +310,13 @@
                     HasRetransmittableData has_retransmittable_data) override;
   BandwidthSample OnPacketAcknowledged(QuicTime ack_time,
                                        QuicPacketNumber packet_number) override;
+  CongestionEventSample OnCongestionEvent(
+      QuicTime ack_time,
+      const AckedPacketVector& acked_packets,
+      const LostPacketVector& lost_packets,
+      QuicBandwidth max_bandwidth,
+      QuicBandwidth est_bandwidth_upper_bound,
+      QuicRoundTripCount round_trip_count) override;
   QuicByteCount OnAckEventEnd(QuicBandwidth bandwidth_estimate,
                               QuicRoundTripCount round_trip_count);
   SendTimeState OnPacketLost(QuicPacketNumber packet_number,
@@ -308,6 +353,10 @@
     return remove_packets_once_per_congestion_event_;
   }
 
+  bool one_bw_sample_per_ack_event() const {
+    return one_bw_sample_per_ack_event_;
+  }
+
  private:
   friend class test::BandwidthSamplerPeer;
 
@@ -430,6 +479,12 @@
   // Latched value of quic_bw_sampler_remove_packets_once_per_congestion_event2.
   const bool remove_packets_once_per_congestion_event_ = GetQuicReloadableFlag(
       quic_bw_sampler_remove_packets_once_per_congestion_event2);
+
+  // Latched value of quic_bw_sampler_remove_packets_once_per_congestion_event2
+  // and quic_one_bw_sample_per_ack_event.
+  const bool one_bw_sample_per_ack_event_ =
+      remove_packets_once_per_congestion_event_ &&
+      GetQuicReloadableFlag(quic_one_bw_sample_per_ack_event);
 };
 
 }  // namespace quic
diff --git a/quic/core/congestion_control/bandwidth_sampler_test.cc b/quic/core/congestion_control/bandwidth_sampler_test.cc
index fc51db2..a5a2682 100644
--- a/quic/core/congestion_control/bandwidth_sampler_test.cc
+++ b/quic/core/congestion_control/bandwidth_sampler_test.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "net/third_party/quiche/src/quic/core/congestion_control/bandwidth_sampler.h"
+#include <cstdint>
+#include <set>
 
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
 #include "net/third_party/quiche/src/quic/core/quic_time.h"
@@ -36,7 +38,10 @@
  protected:
   BandwidthSamplerTest()
       : sampler_(nullptr, /*max_height_tracker_window_length=*/0),
-        bytes_in_flight_(0) {
+        bytes_in_flight_(0),
+        max_bandwidth_(QuicBandwidth::Zero()),
+        est_bandwidth_upper_bound_(QuicBandwidth::Infinite()),
+        round_trip_count_(0) {
     // Ensure that the clock does not start at zero.
     clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
   }
@@ -44,6 +49,9 @@
   MockClock clock_;
   BandwidthSampler sampler_;
   QuicByteCount bytes_in_flight_;
+  QuicBandwidth max_bandwidth_;  // Max observed bandwidth from acks.
+  QuicBandwidth est_bandwidth_upper_bound_;
+  QuicRoundTripCount round_trip_count_;  // Needed to calculate extra_acked.
 
   QuicByteCount PacketsToBytes(QuicPacketCount packet_count) {
     return packet_count * kRegularPacketSize;
@@ -68,8 +76,31 @@
     QuicByteCount size = BandwidthSamplerPeer::GetPacketSize(
         sampler_, QuicPacketNumber(packet_number));
     bytes_in_flight_ -= size;
-    return sampler_.OnPacketAcknowledged(clock_.Now(),
-                                         QuicPacketNumber(packet_number));
+    if (!sampler_.one_bw_sample_per_ack_event()) {
+      return sampler_.OnPacketAcknowledged(clock_.Now(),
+                                           QuicPacketNumber(packet_number));
+    }
+    BandwidthSampler::CongestionEventSample sample = sampler_.OnCongestionEvent(
+        clock_.Now(), {MakeAckedPacket(packet_number)}, {}, max_bandwidth_,
+        est_bandwidth_upper_bound_, round_trip_count_);
+    max_bandwidth_ = std::max(max_bandwidth_, sample.sample_max_bandwidth);
+    BandwidthSample bandwidth_sample;
+    bandwidth_sample.bandwidth = sample.sample_max_bandwidth;
+    bandwidth_sample.rtt = sample.sample_rtt;
+    bandwidth_sample.state_at_send = sample.last_packet_send_state;
+    return bandwidth_sample;
+  }
+
+  AckedPacket MakeAckedPacket(uint64_t packet_number) const {
+    QuicByteCount size = BandwidthSamplerPeer::GetPacketSize(
+        sampler_, QuicPacketNumber(packet_number));
+    return AckedPacket(QuicPacketNumber(packet_number), size, clock_.Now());
+  }
+
+  LostPacket MakeLostPacket(uint64_t packet_number) const {
+    return LostPacket(QuicPacketNumber(packet_number),
+                      BandwidthSamplerPeer::GetPacketSize(
+                          sampler_, QuicPacketNumber(packet_number)));
   }
 
   // Acknowledge receipt of a packet and expect it to be not app-limited.
@@ -80,14 +111,48 @@
     return sample.bandwidth;
   }
 
+  BandwidthSampler::CongestionEventSample OnCongestionEvent(
+      std::set<uint64_t> acked_packet_numbers,
+      std::set<uint64_t> lost_packet_numbers) {
+    AckedPacketVector acked_packets;
+    for (auto it = acked_packet_numbers.begin();
+         it != acked_packet_numbers.end(); ++it) {
+      acked_packets.push_back(MakeAckedPacket(*it));
+      bytes_in_flight_ -= acked_packets.back().bytes_acked;
+    }
+
+    LostPacketVector lost_packets;
+    for (auto it = lost_packet_numbers.begin(); it != lost_packet_numbers.end();
+         ++it) {
+      lost_packets.push_back(MakeLostPacket(*it));
+      bytes_in_flight_ -= lost_packets.back().bytes_lost;
+    }
+
+    BandwidthSampler::CongestionEventSample sample = sampler_.OnCongestionEvent(
+        clock_.Now(), acked_packets, lost_packets, max_bandwidth_,
+        est_bandwidth_upper_bound_, round_trip_count_);
+    max_bandwidth_ = std::max(max_bandwidth_, sample.sample_max_bandwidth);
+    return sample;
+  }
+
   SendTimeState LosePacket(uint64_t packet_number) {
     QuicByteCount size = BandwidthSamplerPeer::GetPacketSize(
         sampler_, QuicPacketNumber(packet_number));
     bytes_in_flight_ -= size;
-    SendTimeState send_time_state =
-        sampler_.OnPacketLost(QuicPacketNumber(packet_number), size);
-    EXPECT_TRUE(send_time_state.is_valid);
-    return send_time_state;
+    if (!sampler_.one_bw_sample_per_ack_event()) {
+      SendTimeState send_time_state =
+          sampler_.OnPacketLost(QuicPacketNumber(packet_number), size);
+      EXPECT_TRUE(send_time_state.is_valid);
+      return send_time_state;
+    }
+    LostPacket lost_packet(QuicPacketNumber(packet_number), size);
+    BandwidthSampler::CongestionEventSample sample = sampler_.OnCongestionEvent(
+        clock_.Now(), {}, {lost_packet}, max_bandwidth_,
+        est_bandwidth_upper_bound_, round_trip_count_);
+    EXPECT_TRUE(sample.last_packet_send_state.is_valid);
+    EXPECT_EQ(sample.sample_max_bandwidth, QuicBandwidth::Zero());
+    EXPECT_EQ(sample.sample_rtt, QuicTime::Delta::Infinite());
+    return sample.last_packet_send_state;
   }
 
   // Sends one packet and acks it.  Then, send 20 packets.  Finally, send
@@ -500,6 +565,119 @@
   EXPECT_EQ(0u, BandwidthSamplerPeer::GetNumberOfTrackedPackets(sampler_));
 }
 
+TEST_F(BandwidthSamplerTest, CongestionEventSampleDefaultValues) {
+  // Make sure a default constructed CongestionEventSample has the correct
+  // initial values for BandwidthSampler::OnCongestionEvent() to work.
+  BandwidthSampler::CongestionEventSample sample;
+
+  DCHECK_EQ(QuicBandwidth::Zero(), sample.sample_max_bandwidth);
+  DCHECK(!sample.sample_is_app_limited);
+  DCHECK_EQ(QuicTime::Delta::Infinite(), sample.sample_rtt);
+  DCHECK_EQ(0u, sample.sample_max_inflight);
+  DCHECK_EQ(0u, sample.extra_acked);
+}
+
+// 1) Send 2 packets, 2) Ack both in 1 event, 3) Repeat.
+TEST_F(BandwidthSamplerTest, TwoAckedPacketsPerEvent) {
+  if (!sampler_.one_bw_sample_per_ack_event()) {
+    return;
+  }
+
+  QuicTime::Delta time_between_packets = QuicTime::Delta::FromMilliseconds(10);
+  QuicBandwidth sending_rate = QuicBandwidth::FromBytesAndTimeDelta(
+      kRegularPacketSize, time_between_packets);
+
+  for (uint64_t i = 1; i < 21; i++) {
+    SendPacket(i);
+    clock_.AdvanceTime(time_between_packets);
+    if (i % 2 != 0) {
+      continue;
+    }
+
+    BandwidthSampler::CongestionEventSample sample =
+        OnCongestionEvent({i - 1, i}, {});
+    EXPECT_EQ(sending_rate, sample.sample_max_bandwidth);
+    EXPECT_EQ(time_between_packets, sample.sample_rtt);
+    EXPECT_EQ(2 * kRegularPacketSize, sample.sample_max_inflight);
+    EXPECT_TRUE(sample.last_packet_send_state.is_valid);
+    EXPECT_EQ(2 * kRegularPacketSize,
+              sample.last_packet_send_state.bytes_in_flight);
+    EXPECT_EQ(i * kRegularPacketSize,
+              sample.last_packet_send_state.total_bytes_sent);
+    EXPECT_EQ((i - 2) * kRegularPacketSize,
+              sample.last_packet_send_state.total_bytes_acked);
+    EXPECT_EQ(0u, sample.last_packet_send_state.total_bytes_lost);
+    sampler_.RemoveObsoletePackets(QuicPacketNumber(i - 2));
+  }
+}
+
+TEST_F(BandwidthSamplerTest, LoseEveryOtherPacket) {
+  if (!sampler_.one_bw_sample_per_ack_event()) {
+    return;
+  }
+
+  QuicTime::Delta time_between_packets = QuicTime::Delta::FromMilliseconds(10);
+  QuicBandwidth sending_rate = QuicBandwidth::FromBytesAndTimeDelta(
+      kRegularPacketSize, time_between_packets);
+
+  for (uint64_t i = 1; i < 21; i++) {
+    SendPacket(i);
+    clock_.AdvanceTime(time_between_packets);
+    if (i % 2 != 0) {
+      continue;
+    }
+
+    // Ack packet i and lose i-1.
+    BandwidthSampler::CongestionEventSample sample =
+        OnCongestionEvent({i}, {i - 1});
+    // Losing 50% packets means sending rate is twice the bandwidth.
+    EXPECT_EQ(sending_rate, sample.sample_max_bandwidth * 2);
+    EXPECT_EQ(time_between_packets, sample.sample_rtt);
+    EXPECT_EQ(kRegularPacketSize, sample.sample_max_inflight);
+    EXPECT_TRUE(sample.last_packet_send_state.is_valid);
+    EXPECT_EQ(2 * kRegularPacketSize,
+              sample.last_packet_send_state.bytes_in_flight);
+    EXPECT_EQ(i * kRegularPacketSize,
+              sample.last_packet_send_state.total_bytes_sent);
+    EXPECT_EQ((i - 2) * kRegularPacketSize / 2,
+              sample.last_packet_send_state.total_bytes_acked);
+    EXPECT_EQ((i - 2) * kRegularPacketSize / 2,
+              sample.last_packet_send_state.total_bytes_lost);
+    sampler_.RemoveObsoletePackets(QuicPacketNumber(i - 2));
+  }
+}
+
+TEST_F(BandwidthSamplerTest, AckHeightRespectBandwidthEstimateUpperBound) {
+  if (!sampler_.one_bw_sample_per_ack_event()) {
+    return;
+  }
+
+  QuicTime::Delta time_between_packets = QuicTime::Delta::FromMilliseconds(10);
+  QuicBandwidth first_packet_sending_rate =
+      QuicBandwidth::FromBytesAndTimeDelta(kRegularPacketSize,
+                                           time_between_packets);
+
+  // Send and ack packet 1.
+  SendPacket(1);
+  clock_.AdvanceTime(time_between_packets);
+  BandwidthSampler::CongestionEventSample sample = OnCongestionEvent({1}, {});
+  EXPECT_EQ(first_packet_sending_rate, sample.sample_max_bandwidth);
+  EXPECT_EQ(first_packet_sending_rate, max_bandwidth_);
+
+  // Send and ack packet 2, 3 and 4.
+  round_trip_count_++;
+  est_bandwidth_upper_bound_ = first_packet_sending_rate * 0.9;
+  SendPacket(2);
+  SendPacket(3);
+  SendPacket(4);
+  clock_.AdvanceTime(time_between_packets);
+  sample = OnCongestionEvent({2, 3, 4}, {});
+  EXPECT_EQ(first_packet_sending_rate * 3, sample.sample_max_bandwidth);
+  EXPECT_EQ(max_bandwidth_, sample.sample_max_bandwidth);
+
+  EXPECT_LT(2 * kRegularPacketSize, sample.extra_acked);
+}
+
 class MaxAckHeightTrackerTest : public QuicTest {
  protected:
   MaxAckHeightTrackerTest() : tracker_(/*initial_filter_window=*/10) {}
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index 419238c..07fb352 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -98,6 +98,11 @@
     const AckedPacketVector& acked_packets,
     const LostPacketVector& lost_packets,
     Bbr2CongestionEvent* congestion_event) {
+  if (one_bw_sample_per_ack_event()) {
+    OnCongestionEventStartNew(event_time, acked_packets, lost_packets,
+                              congestion_event);
+    return;
+  }
   const QuicByteCount prior_bytes_acked = total_bytes_acked();
   const QuicByteCount prior_bytes_lost = total_bytes_lost();
 
@@ -191,6 +196,87 @@
   AdaptLowerBounds(*congestion_event);
 }
 
+void Bbr2NetworkModel::OnCongestionEventStartNew(
+    QuicTime event_time,
+    const AckedPacketVector& acked_packets,
+    const LostPacketVector& lost_packets,
+    Bbr2CongestionEvent* congestion_event) {
+  DCHECK(one_bw_sample_per_ack_event());
+  const QuicByteCount prior_bytes_acked = total_bytes_acked();
+  const QuicByteCount prior_bytes_lost = total_bytes_lost();
+
+  congestion_event->event_time = event_time;
+  congestion_event->end_of_round_trip =
+      acked_packets.empty() ? false
+                            : round_trip_counter_.OnPacketsAcked(
+                                  acked_packets.rbegin()->packet_number);
+
+  BandwidthSamplerInterface::CongestionEventSample sample =
+      bandwidth_sampler_.OnCongestionEvent(event_time, acked_packets,
+                                           lost_packets, MaxBandwidth(),
+                                           bandwidth_lo(), RoundTripCount());
+
+  if (sample.last_packet_send_state.is_valid) {
+    congestion_event->last_packet_send_state = sample.last_packet_send_state;
+    congestion_event->last_sample_is_app_limited =
+        sample.last_packet_send_state.is_app_limited;
+  }
+
+  if (!sample.sample_is_app_limited ||
+      sample.sample_max_bandwidth > MaxBandwidth()) {
+    congestion_event->sample_max_bandwidth = sample.sample_max_bandwidth;
+    max_bandwidth_filter_.Update(congestion_event->sample_max_bandwidth);
+  }
+
+  if (!sample.sample_rtt.IsInfinite()) {
+    congestion_event->sample_min_rtt = sample.sample_rtt;
+    min_rtt_filter_.Update(congestion_event->sample_min_rtt, event_time);
+  }
+
+  congestion_event->bytes_acked = total_bytes_acked() - prior_bytes_acked;
+  congestion_event->bytes_lost = total_bytes_lost() - prior_bytes_lost;
+
+  if (congestion_event->prior_bytes_in_flight >=
+      congestion_event->bytes_acked + congestion_event->bytes_lost) {
+    congestion_event->bytes_in_flight =
+        congestion_event->prior_bytes_in_flight -
+        congestion_event->bytes_acked - congestion_event->bytes_lost;
+  } else {
+    QUIC_LOG_FIRST_N(ERROR, 1)
+        << "prior_bytes_in_flight:" << congestion_event->prior_bytes_in_flight
+        << " is smaller than the sum of bytes_acked:"
+        << congestion_event->bytes_acked
+        << " and bytes_lost:" << congestion_event->bytes_lost;
+    congestion_event->bytes_in_flight = 0;
+  }
+
+  bytes_lost_in_round_ += congestion_event->bytes_lost;
+
+  // |bandwidth_latest_| and |inflight_latest_| only increased within a round.
+  if (sample.sample_max_bandwidth > bandwidth_latest_) {
+    bandwidth_latest_ = sample.sample_max_bandwidth;
+  }
+
+  if (sample.sample_max_inflight > inflight_latest_) {
+    inflight_latest_ = sample.sample_max_inflight;
+  }
+
+  if (!congestion_event->end_of_round_trip) {
+    return;
+  }
+
+  // Per round-trip updates.
+  AdaptLowerBounds(*congestion_event);
+
+  if (!sample.sample_max_bandwidth.IsZero()) {
+    bandwidth_latest_ = sample.sample_max_bandwidth;
+  }
+
+  if (sample.sample_max_inflight > 0) {
+    inflight_latest_ = sample.sample_max_inflight;
+  }
+}
+
 void Bbr2NetworkModel::AdaptLowerBounds(
     const Bbr2CongestionEvent& congestion_event) {
   if (!congestion_event.end_of_round_trip ||
@@ -220,10 +306,12 @@
     QuicPacketNumber least_unacked_packet,
     const Bbr2CongestionEvent& congestion_event) {
   if (congestion_event.end_of_round_trip) {
-    const auto& last_acked_sample = congestion_event.last_acked_sample;
-    if (last_acked_sample.bandwidth_sample.state_at_send.is_valid) {
-      bandwidth_latest_ = last_acked_sample.bandwidth_sample.bandwidth;
-      inflight_latest_ = last_acked_sample.inflight_sample;
+    if (!one_bw_sample_per_ack_event()) {
+      const auto& last_acked_sample = congestion_event.last_acked_sample;
+      if (last_acked_sample.bandwidth_sample.state_at_send.is_valid) {
+        bandwidth_latest_ = last_acked_sample.bandwidth_sample.bandwidth;
+        inflight_latest_ = last_acked_sample.inflight_sample;
+      }
     }
 
     bytes_lost_in_round_ = 0;
@@ -270,7 +358,10 @@
 
 bool Bbr2NetworkModel::IsInflightTooHigh(
     const Bbr2CongestionEvent& congestion_event) const {
-  const SendTimeState& send_state = SendStateOfLargestPacket(congestion_event);
+  const SendTimeState& send_state =
+      one_bw_sample_per_ack_event()
+          ? congestion_event.last_packet_send_state
+          : SendStateOfLargestPacket(congestion_event);
   if (!send_state.is_valid) {
     // Not enough information.
     return false;
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 0c5a1eb..b97184b 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -247,6 +247,8 @@
   // Whether acked_packets indicates the end of a round trip.
   bool end_of_round_trip = false;
 
+  // TODO(wub): After deprecating --quic_one_bw_sample_per_ack_event, use
+  // last_packet_send_state.is_app_limited instead of this field.
   // Whether the last bandwidth sample from acked_packets is app limited.
   // false if acked_packets is empty.
   bool last_sample_is_app_limited = false;
@@ -261,6 +263,13 @@
   // Maximum bandwidth of all bandwidth samples from acked_packets.
   QuicBandwidth sample_max_bandwidth = QuicBandwidth::Zero();
 
+  // The send state of the largest packet in acked_packets, unless it is empty.
+  // If acked_packets is empty, it's the send state of the largest packet in
+  // lost_packets.
+  SendTimeState last_packet_send_state;
+
+  // TODO(wub): Remove |last_acked_sample| and |last_lost_sample| when
+  // deprecating --quic_one_bw_sample_per_ack_event.
   // Send time state of the largest-numbered packet in this event.
   // SendTimeState send_time_state;
   struct {
@@ -276,6 +285,7 @@
   } last_lost_sample;
 };
 
+// TODO(wub): Remove this when deprecating --quic_one_bw_sample_per_ack_event.
 QUIC_EXPORT_PRIVATE const SendTimeState& SendStateOfLargestPacket(
     const Bbr2CongestionEvent& congestion_event);
 
@@ -300,6 +310,12 @@
                               const AckedPacketVector& acked_packets,
                               const LostPacketVector& lost_packets,
                               Bbr2CongestionEvent* congestion_event);
+  // The new version of OnCongestionEventStart.
+  // Called only when --quic_one_bw_sample_per_ack_event=true.
+  void OnCongestionEventStartNew(QuicTime event_time,
+                                 const AckedPacketVector& acked_packets,
+                                 const LostPacketVector& lost_packets,
+                                 Bbr2CongestionEvent* congestion_event);
 
   void OnCongestionEventFinish(QuicPacketNumber least_unacked_packet,
                                const Bbr2CongestionEvent& congestion_event);
@@ -416,6 +432,10 @@
   float pacing_gain() const { return pacing_gain_; }
   void set_pacing_gain(float pacing_gain) { pacing_gain_ = pacing_gain; }
 
+  bool one_bw_sample_per_ack_event() const {
+    return bandwidth_sampler_.one_bw_sample_per_ack_event();
+  }
+
  private:
   const Bbr2Params& Params() const { return *params_; }
   const Bbr2Params* const params_;
diff --git a/quic/core/congestion_control/bbr2_probe_bw.cc b/quic/core/congestion_control/bbr2_probe_bw.cc
index ec9235b..d419de2 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.cc
+++ b/quic/core/congestion_control/bbr2_probe_bw.cc
@@ -148,7 +148,10 @@
 
 Bbr2ProbeBwMode::AdaptUpperBoundsResult Bbr2ProbeBwMode::MaybeAdaptUpperBounds(
     const Bbr2CongestionEvent& congestion_event) {
-  const SendTimeState& send_state = SendStateOfLargestPacket(congestion_event);
+  const SendTimeState& send_state =
+      model_->one_bw_sample_per_ack_event()
+          ? congestion_event.last_packet_send_state
+          : SendStateOfLargestPacket(congestion_event);
   if (!send_state.is_valid) {
     QUIC_DVLOG(3) << sender_ << " " << cycle_.phase
                   << ": NOT_ADAPTED_INVALID_SAMPLE";
diff --git a/quic/core/congestion_control/bbr_sender.cc b/quic/core/congestion_control/bbr_sender.cc
index 4973744..4a583bb 100644
--- a/quic/core/congestion_control/bbr_sender.cc
+++ b/quic/core/congestion_control/bbr_sender.cc
@@ -389,23 +389,65 @@
                                   const AckedPacketVector& acked_packets,
                                   const LostPacketVector& lost_packets) {
   const QuicByteCount total_bytes_acked_before = sampler_.total_bytes_acked();
+  const QuicByteCount total_bytes_lost_before = sampler_.total_bytes_lost();
 
   bool is_round_start = false;
   bool min_rtt_expired = false;
-
-  DiscardLostPackets(lost_packets);
-
-  // Input the new data into the BBR model of the connection.
   QuicByteCount excess_acked = 0;
-  if (!acked_packets.empty()) {
-    QuicPacketNumber last_acked_packet = acked_packets.rbegin()->packet_number;
-    is_round_start = UpdateRoundTripCounter(last_acked_packet);
-    min_rtt_expired = UpdateBandwidthAndMinRtt(event_time, acked_packets);
-    UpdateRecoveryState(last_acked_packet, !lost_packets.empty(),
-                        is_round_start);
+  QuicByteCount bytes_lost = 0;
 
-    excess_acked =
-        sampler_.OnAckEventEnd(max_bandwidth_.GetBest(), round_trip_count_);
+  if (!sampler_.one_bw_sample_per_ack_event()) {
+    DiscardLostPackets(lost_packets);
+
+    // Input the new data into the BBR model of the connection.
+    if (!acked_packets.empty()) {
+      QuicPacketNumber last_acked_packet =
+          acked_packets.rbegin()->packet_number;
+      is_round_start = UpdateRoundTripCounter(last_acked_packet);
+
+      min_rtt_expired = UpdateBandwidthAndMinRtt(event_time, acked_packets);
+      UpdateRecoveryState(last_acked_packet, !lost_packets.empty(),
+                          is_round_start);
+
+      excess_acked =
+          sampler_.OnAckEventEnd(max_bandwidth_.GetBest(), round_trip_count_);
+    }
+  } else {
+    if (!acked_packets.empty()) {
+      QuicPacketNumber last_acked_packet =
+          acked_packets.rbegin()->packet_number;
+      is_round_start = UpdateRoundTripCounter(last_acked_packet);
+      UpdateRecoveryState(last_acked_packet, !lost_packets.empty(),
+                          is_round_start);
+    }
+
+    BandwidthSamplerInterface::CongestionEventSample sample =
+        sampler_.OnCongestionEvent(
+            event_time, acked_packets, lost_packets, max_bandwidth_.GetBest(),
+            QuicBandwidth::Infinite(), round_trip_count_);
+    if (sample.last_packet_send_state.is_valid) {
+      last_sample_is_app_limited_ =
+          sample.last_packet_send_state.is_app_limited;
+      has_non_app_limited_sample_ |= !last_sample_is_app_limited_;
+    }
+    if (!sample.sample_is_app_limited ||
+        sample.sample_max_bandwidth > max_bandwidth_.GetBest()) {
+      max_bandwidth_.Update(sample.sample_max_bandwidth, round_trip_count_);
+    }
+    if (!sample.sample_rtt.IsInfinite()) {
+      min_rtt_expired = MaybeUpdateMinRtt(event_time, sample.sample_rtt);
+    }
+    if (mode_ == STARTUP) {
+      bytes_lost = sampler_.total_bytes_lost() - total_bytes_lost_before;
+      if (stats_) {
+        stats_->slowstart_packets_lost += lost_packets.size();
+        stats_->slowstart_bytes_lost += bytes_lost;
+      }
+      if (startup_rate_reduction_multiplier_ != 0) {
+        startup_bytes_lost_ += bytes_lost;
+      }
+    }
+    excess_acked = sample.extra_acked;
   }
 
   // Handle logic specific to PROBE_BW mode.
@@ -425,9 +467,10 @@
   // Calculate number of packets acked and lost.
   QuicByteCount bytes_acked =
       sampler_.total_bytes_acked() - total_bytes_acked_before;
-  QuicByteCount bytes_lost = 0;
-  for (const auto& packet : lost_packets) {
-    bytes_lost += packet.bytes_lost;
+  if (!sampler_.one_bw_sample_per_ack_event()) {
+    for (const auto& packet : lost_packets) {
+      bytes_lost += packet.bytes_lost;
+    }
   }
 
   // After the model is updated, recalculate the pacing rate and congestion
@@ -494,6 +537,7 @@
 }
 
 void BbrSender::DiscardLostPackets(const LostPacketVector& lost_packets) {
+  DCHECK(!sampler_.one_bw_sample_per_ack_event());
   for (const LostPacket& packet : lost_packets) {
     sampler_.OnPacketLost(packet.packet_number, packet.bytes_lost);
     if (mode_ == STARTUP) {
@@ -525,6 +569,7 @@
 bool BbrSender::UpdateBandwidthAndMinRtt(
     QuicTime now,
     const AckedPacketVector& acked_packets) {
+  DCHECK(!sampler_.one_bw_sample_per_ack_event());
   QuicTime::Delta sample_min_rtt = QuicTime::Delta::Infinite();
   for (const auto& packet : acked_packets) {
     BandwidthSample bandwidth_sample =
@@ -552,6 +597,12 @@
   if (sample_min_rtt.IsInfinite()) {
     return false;
   }
+
+  return MaybeUpdateMinRtt(now, sample_min_rtt);
+}
+
+bool BbrSender::MaybeUpdateMinRtt(QuicTime now,
+                                  QuicTime::Delta sample_min_rtt) {
   min_rtt_since_last_probe_rtt_ =
       std::min(min_rtt_since_last_probe_rtt_, sample_min_rtt);
 
diff --git a/quic/core/congestion_control/bbr_sender.h b/quic/core/congestion_control/bbr_sender.h
index dc512cf..9a811fa 100644
--- a/quic/core/congestion_control/bbr_sender.h
+++ b/quic/core/congestion_control/bbr_sender.h
@@ -190,6 +190,7 @@
   // Returns true if the current min_rtt should be kept and we should not enter
   // PROBE_RTT immediately.
   bool ShouldExtendMinRttExpiry() const;
+  bool MaybeUpdateMinRtt(QuicTime now, QuicTime::Delta sample_min_rtt);
 
   // Enters the STARTUP mode.
   void EnterStartupMode(QuicTime now);