For quic loss detection tuning, add a new reward function using average http latency and run it side by side with the existing reward function. protected by --gfe2_reloadable_flag_quic_enable_loss_detection_experiment_at_gfe.

PiperOrigin-RevId: 319825747
Change-Id: Ic990510ce43f71990edb1ecc7180b8459e706173
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 0888a0e..e3dab64 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -2631,9 +2631,11 @@
  public:
   TestAckListener() {}
 
-  void OnPacketAcked(int acked_bytes,
-                     QuicTime::Delta /*delta_largest_observed*/) override {
+  QuicTime::Delta OnPacketAcked(
+      int acked_bytes,
+      QuicTime::Delta /*delta_largest_observed*/) override {
     total_bytes_acked_ += acked_bytes;
+    return QuicTime::Delta::Zero();
   }
 
   void OnPacketRetransmitted(int /*retransmitted_bytes*/) override {}
diff --git a/quic/core/http/quic_headers_stream.cc b/quic/core/http/quic_headers_stream.cc
index 8157dae..7fc10dc 100644
--- a/quic/core/http/quic_headers_stream.cc
+++ b/quic/core/http/quic_headers_stream.cc
@@ -91,7 +91,11 @@
         return false;
       }
       if (header.ack_listener != nullptr && header_length > 0) {
-        header.ack_listener->OnPacketAcked(header_length, ack_delay_time);
+        QuicTime::Delta response_time =
+            header.ack_listener->OnPacketAcked(header_length, ack_delay_time);
+        if (!response_time.IsZero()) {
+          spdy_session_->RecordServerResponseTime(response_time);
+        }
       }
       header.unacked_length -= header_length;
       acked_offset += header_length;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index d6b5a0e..f470322 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -1368,6 +1368,14 @@
       quiche::QuicheStrCat(type, " stream is received twice."));
 }
 
+void QuicSpdySession::RecordServerResponseTime(QuicTime::Delta response_time) {
+  DCHECK_EQ(perspective(), Perspective::IS_SERVER);
+  QuicConnectionStats& stats = connection()->mutable_stats();
+
+  ++stats.num_responses;
+  stats.total_response_time = stats.total_response_time + response_time;
+}
+
 // static
 void QuicSpdySession::LogHeaderCompressionRatioHistogram(
     bool using_qpack,
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 4d6ee63..3773b88 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -22,6 +22,7 @@
 #include "net/third_party/quiche/src/quic/core/qpack/qpack_receive_stream.h"
 #include "net/third_party/quiche/src/quic/core/qpack/qpack_send_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
@@ -384,6 +385,9 @@
   // Decode SETTINGS from |cached_state| and apply it to the session.
   bool ResumeApplicationState(ApplicationState* cached_state) override;
 
+  // (Server only) Records the response time of a completed request.
+  void RecordServerResponseTime(QuicTime::Delta response_time);
+
  protected:
   // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
   // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 4c979c1..c80ecc9 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -919,8 +919,11 @@
   DCHECK_LE(newly_acked_header_length, *newly_acked_length);
   unacked_frame_headers_offsets_.Difference(offset, offset + data_length);
   if (ack_listener_ != nullptr && new_data_acked) {
-    ack_listener_->OnPacketAcked(
+    QuicTime::Delta response_time = ack_listener_->OnPacketAcked(
         *newly_acked_length - newly_acked_header_length, ack_delay_time);
+    if (!response_time.IsZero()) {
+      spdy_session_->RecordServerResponseTime(response_time);
+    }
   }
   return new_data_acked;
 }
diff --git a/quic/core/quic_ack_listener_interface.h b/quic/core/quic_ack_listener_interface.h
index 0a9c694..873015a 100644
--- a/quic/core/quic_ack_listener_interface.h
+++ b/quic/core/quic_ack_listener_interface.h
@@ -20,8 +20,10 @@
 
   // Called when a packet is acked.  Called once per packet.
   // |acked_bytes| is the number of data bytes acked.
-  virtual void OnPacketAcked(int acked_bytes,
-                             QuicTime::Delta ack_delay_time) = 0;
+  // Return the http response time if this is the last ack of a server stream.
+  // Otherwise return QuicTime::Delta::Zero().
+  virtual QuicTime::Delta OnPacketAcked(int acked_bytes,
+                                        QuicTime::Delta ack_delay_time) = 0;
 
   // Called when a packet is retransmitted.  Called once per packet.
   // |retransmitted_bytes| is the number of data bytes retransmitted.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 4cb08c5..69f00e4 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -3449,8 +3449,11 @@
   FlushPackets();
   connected_ = false;
   DCHECK(visitor_ != nullptr);
-  sent_packet_manager_.OnConnectionClosed();
   visitor_->OnConnectionClosed(frame, source);
+  // LossDetectionTunerInterface::Finish() may be called from
+  // sent_packet_manager_.OnConnectionClosed. Which may require the session to
+  // finish its business first.
+  sent_packet_manager_.OnConnectionClosed();
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnConnectionClosed(frame, source);
   }
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index dc2a00f..b4f6445 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -466,6 +466,8 @@
       const std::string& details,
       ConnectionCloseBehavior connection_close_behavior);
 
+  QuicConnectionStats& mutable_stats() { return stats_; }
+
   // Returns statistics tracked for this connection.
   const QuicConnectionStats& GetStats();
 
@@ -850,6 +852,7 @@
   bool ack_frame_updated() const;
 
   QuicConnectionHelperInterface* helper() { return helper_; }
+  const QuicConnectionHelperInterface* helper() const { return helper_; }
   QuicAlarmFactory* alarm_factory() { return alarm_factory_; }
 
   quiche::QuicheStringPiece GetCurrentPacket();
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 3579be0..529a4ef 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -162,6 +162,15 @@
   // Number of sent packets that were encapsulated using Legacy Version
   // Encapsulation.
   QuicPacketCount sent_legacy_version_encapsulated_packets = 0;
+
+  // HTTP server metrics.
+  // Number of success streams. i.e. streams in which all data are acked and
+  // have no stream error.
+  uint64_t num_responses = 0;
+  // Sum of response times for all success streams. A stream's response time is
+  // the time between stream creation to the time its last ack is received,
+  // minus the peer ack delay in the last ack.
+  QuicTime::Delta total_response_time = QuicTime::Delta::Zero();
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index c936767..706eb64 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -481,6 +481,10 @@
     connection()->OnUserAgentIdKnown();
   }
 
+  const QuicClock* GetClock() const {
+    return connection()->helper()->GetClock();
+  }
+
  protected:
   using StreamMap = QuicSmallMap<QuicStreamId, std::unique_ptr<QuicStream>, 10>;
 
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc
index 0dba17b..32a8450 100644
--- a/quic/test_tools/quic_test_utils.cc
+++ b/quic/test_tools/quic_test_utils.cc
@@ -835,7 +835,10 @@
 
 MockLossAlgorithm::~MockLossAlgorithm() {}
 
-MockAckListener::MockAckListener() {}
+MockAckListener::MockAckListener() {
+  ON_CALL(*this, OnPacketAcked(_, _))
+      .WillByDefault(testing::Return(QuicTime::Delta::Zero()));
+}
 
 MockAckListener::~MockAckListener() {}
 
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 650213e..7be7edf 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -1320,7 +1320,7 @@
   MockAckListener(const MockAckListener&) = delete;
   MockAckListener& operator=(const MockAckListener&) = delete;
 
-  MOCK_METHOD(void,
+  MOCK_METHOD(QuicTime::Delta,
               OnPacketAcked,
               (int acked_bytes, QuicTime::Delta ack_delay_time),
               (override));