Record header compression ratio.

Record compression ratio for all headers sent and received via QUIC, separately
for HPACK and QPACK compressed ones, so that they can be compared.  Compressed
size excludes stream frame overhead in all cases.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 272241030
Change-Id: I07a9f9b03ae85f13590c740ece06671c887fadc2
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 3bd13bc..07b67e3 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -14,6 +14,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_exported_stats.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
@@ -102,6 +103,12 @@
 
   void OnHeaderFrameEnd(SpdyStreamId /* stream_id */) override {
     DCHECK(!VersionUsesQpack(session_->transport_version()));
+
+    LogHeaderCompressionRatioHistogram(
+        /* using_qpack = */ false,
+        /* is_sent = */ false, header_list_.compressed_header_bytes(),
+        header_list_.uncompressed_header_bytes());
+
     if (session_->IsConnected()) {
       session_->OnHeaderList(header_list_);
     }
@@ -655,6 +662,7 @@
     QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
   DCHECK(!VersionUsesQpack(transport_version()));
 
+  const QuicByteCount uncompressed_size = headers.TotalBytesUsed();
   SpdyHeadersIR headers_frame(id, std::move(headers));
   headers_frame.set_fin(fin);
   if (perspective() == Perspective::IS_CLIENT) {
@@ -667,6 +675,19 @@
   headers_stream()->WriteOrBufferData(
       QuicStringPiece(frame.data(), frame.size()), false,
       std::move(ack_listener));
+
+  // Calculate compressed header block size without framing overhead.
+  QuicByteCount compressed_size = frame.size();
+  compressed_size -= spdy::kFrameHeaderSize;
+  if (perspective() == Perspective::IS_CLIENT) {
+    // Exclusive bit and Stream Dependency are four bytes, weight is one more.
+    compressed_size -= 5;
+  }
+
+  LogHeaderCompressionRatioHistogram(
+      /* using_qpack = */ false,
+      /* is_sent = */ true, compressed_size, uncompressed_size);
+
   return frame.size();
 }
 
@@ -1041,4 +1062,50 @@
                              QuicStrCat(type, " stream is received twice."));
 }
 
+// static
+void QuicSpdySession::LogHeaderCompressionRatioHistogram(
+    bool using_qpack,
+    bool is_sent,
+    QuicByteCount compressed,
+    QuicByteCount uncompressed) {
+  if (compressed <= 0 || uncompressed <= 0) {
+    return;
+  }
+
+  int ratio = 100 * (compressed) / (uncompressed);
+  if (ratio < 1) {
+    ratio = 1;
+  } else if (ratio > 200) {
+    ratio = 200;
+  }
+
+  // Note that when using histogram macros in Chromium, the histogram name must
+  // be the same across calls for any given call site.
+  if (using_qpack) {
+    if (is_sent) {
+      QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioQpackSent",
+                            ratio, 1, 200, 200,
+                            "Header compression ratio as percentage for sent "
+                            "headers using QPACK.");
+    } else {
+      QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioQpackReceived",
+                            ratio, 1, 200, 200,
+                            "Header compression ratio as percentage for "
+                            "received headers using QPACK.");
+    }
+  } else {
+    if (is_sent) {
+      QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioHpackSent",
+                            ratio, 1, 200, 200,
+                            "Header compression ratio as percentage for sent "
+                            "headers using HPACK.");
+    } else {
+      QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioHpackReceived",
+                            ratio, 1, 200, 200,
+                            "Header compression ratio as percentage for "
+                            "received headers using HPACK.");
+    }
+  }
+}
+
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 3d82cd7..51e3cea 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -240,6 +240,24 @@
 
   Http3DebugVisitor* debug_visitor() { return debug_visitor_; }
 
+  // Log header compression ratio histogram.
+  // |using_qpack| is true for QPACK, false for HPACK.
+  // |is_sent| is true for sent headers, false for received ones.
+  // Ratio is recorded as percentage.  Smaller value means more efficient
+  // compression.  Compressed size might be larger than uncompressed size, but
+  // recorded ratio is trunckated at 200%.
+  // Uncompressed size can be zero for an empty header list, and compressed size
+  // can be zero for an empty header list when using HPACK.  (QPACK always emits
+  // a header block prefix of at least two bytes.)  This method records nothing
+  // if either |compressed| or |uncompressed| is not positive.
+  // In order for measurements for different protocol to be comparable, the
+  // caller must ensure that uncompressed size is the total length of header
+  // names and values without any overhead.
+  static void LogHeaderCompressionRatioHistogram(bool using_qpack,
+                                                 bool is_sent,
+                                                 QuicByteCount compressed,
+                                                 QuicByteCount uncompressed);
+
  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 ea8e669..d2e48d1 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -995,6 +995,11 @@
 }
 
 void QuicSpdyStream::ProcessDecodedHeaders(const QuicHeaderList& headers) {
+  QuicSpdySession::LogHeaderCompressionRatioHistogram(
+      /* using_qpack = */ true,
+      /* is_sent = */ false, headers.compressed_header_bytes(),
+      headers.uncompressed_header_bytes());
+
   if (spdy_session_->promised_stream_id() ==
       QuicUtils::GetInvalidStreamId(session()->transport_version())) {
     const QuicByteCount frame_length = headers_decompressed_
@@ -1052,6 +1057,12 @@
                   << encoded_headers.length();
   WriteOrBufferData(encoded_headers, fin, nullptr);
 
+  QuicSpdySession::LogHeaderCompressionRatioHistogram(
+      /* using_qpack = */ true,
+      /* is_sent = */ true,
+      encoded_headers.size() + encoder_stream_sent_byte_count,
+      header_block.TotalBytesUsed());
+
   return encoded_headers.size() + encoder_stream_sent_byte_count;
 }