Count the total number of application bytes acknowledged.

This can be used to estimate the goodput of the connection.

Note that this currently does not include datagrams since we don't retain datagram lengths in the unacked packet map.

PiperOrigin-RevId: 845890095
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index a4d3d15..ef7f2b1 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -673,6 +673,10 @@
 
   // Returns statistics tracked for this connection.
   const QuicConnectionStats& GetStats();
+  // Same as above, but const.  Note that since GetStats() internally updates a
+  // lot of fields, some of the fields in the resulting QuicConnectionStats
+  // might be stale.
+  const QuicConnectionStats& GetStatsPotentiallyStale() const { return stats_; }
 
   // Processes an incoming UDP packet (consisting of a QuicEncryptedPacket) from
   // the peer.
@@ -2193,8 +2197,8 @@
 
   QuicFramer framer_;
 
-  QuicConnectionHelperInterface* helper_;  // Not owned.
-  QuicAlarmFactory* alarm_factory_;        // Not owned.
+  QuicConnectionHelperInterface* helper_;           // Not owned.
+  QuicAlarmFactory* alarm_factory_;                 // Not owned.
   PerPacketOptions* per_packet_options_ = nullptr;  // Not owned.
   QuicPacketWriterParams packet_writer_params_;
   QuicPacketWriter* writer_;  // Owned or not depending on |owns_writer_|.
diff --git a/quiche/quic/core/quic_connection_stats.h b/quiche/quic/core/quic_connection_stats.h
index 0c21aca..724d172 100644
--- a/quiche/quic/core/quic_connection_stats.h
+++ b/quiche/quic/core/quic_connection_stats.h
@@ -266,6 +266,11 @@
   // Statistics to measure how many client path probes are reset.
   uint32_t num_client_probing_attempts = 0;
   uint32_t num_stateless_resets_on_alternate_path = 0;
+
+  // Total number of application data bytes acknowledged on the connection.
+  // TODO: b/469371101 - this currently does not include datagrams, since we do
+  // not retain the datagram length.
+  QuicByteCount application_data_acked = 0;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_generic_session_test.cc b/quiche/quic/core/quic_generic_session_test.cc
index 985403b..45dfef5 100644
--- a/quiche/quic/core/quic_generic_session_test.cc
+++ b/quiche/quic/core/quic_generic_session_test.cc
@@ -544,6 +544,28 @@
   EXPECT_EQ(total_received, 128u * 1024u + 2);
 }
 
+TEST_F(QuicGenericSessionTest, ApplicationBytesAcknowledged) {
+  CreateDefaultEndpoints(kDiscardServer);
+  WireUpEndpoints();
+  RunHandshake();
+
+  webtransport::SessionStats start_stats =
+      client_->session()->GetSessionStats();
+
+  constexpr QuicByteCount kBytesToSend = 12345;
+  webtransport::Stream* stream =
+      client_->session()->OpenOutgoingBidirectionalStream();
+  ASSERT_TRUE(stream != nullptr);
+  QUICHE_ASSERT_OK(SendZeroes(*stream, kBytesToSend, /*fin=*/true));
+  bool finished = test_harness_.RunUntilWithDefaultTimeout(
+      [&]() { return stream->PeekNextReadableRegion().fin_next; });
+  ASSERT_TRUE(finished);
+
+  webtransport::SessionStats end_stats = client_->session()->GetSessionStats();
+  EXPECT_EQ(kBytesToSend, end_stats.application_bytes_acknowledged -
+                              start_stats.application_bytes_acknowledged);
+}
+
 // Disable bandwidth measurement test in non-opt builds since it's slow.
 #if defined(NDEBUG)
 #define BANDWIDTH_MEASUREMENT_TEST BandwidthMeasurement
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index d0a766a..99cc1a9 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -2561,6 +2561,7 @@
         frame.stream_frame.offset, frame.stream_frame.data_length,
         frame.stream_frame.fin, ack_delay_time, receive_timestamp,
         &newly_acked_length, is_retransmission);
+    connection_->mutable_stats().application_data_acked += newly_acked_length;
     if (!stream->HasPendingRetransmission()) {
       streams_with_pending_retransmission_.erase(stream->id());
     }
diff --git a/quiche/quic/core/web_transport_stats.cc b/quiche/quic/core/web_transport_stats.cc
index 302da2e..ea9467b 100644
--- a/quiche/quic/core/web_transport_stats.cc
+++ b/quiche/quic/core/web_transport_stats.cc
@@ -6,6 +6,7 @@
 
 #include "absl/time/time.h"
 #include "quiche/quic/core/congestion_control/rtt_stats.h"
+#include "quiche/quic/core/quic_connection_stats.h"
 #include "quiche/quic/core/quic_session.h"
 #include "quiche/web_transport/web_transport.h"
 
@@ -29,6 +30,11 @@
   result.rtt_variation = rtt_stats->mean_deviation().ToAbsl();
   result.datagram_stats = WebTransportDatagramStatsForQuicSession(session);
 
+  const QuicConnectionStats& connection_stats =
+      session.connection()->GetStatsPotentiallyStale();
+  result.application_bytes_acknowledged =
+      connection_stats.application_data_acked;
+
   // "This estimate excludes any framing overhead and represents the rate at
   // which an application payload might be sent."
   // https://w3c.github.io/webtransport/#web-transport-connection-stats
diff --git a/quiche/web_transport/web_transport.h b/quiche/web_transport/web_transport.h
index 5cd92a0..2c60d72 100644
--- a/quiche/web_transport/web_transport.h
+++ b/quiche/web_transport/web_transport.h
@@ -106,6 +106,8 @@
   absl::Duration smoothed_rtt;
   absl::Duration rtt_variation;
 
+  uint64_t application_bytes_acknowledged = 0;
+
   uint64_t estimated_send_rate_bps;  // In bits per second.
 
   DatagramStats datagram_stats;