puma: Implement PumaHistogramEncoder

PumaHistogramEncoder is responsible for encoding PUMA histogram deltas
into Private Metrics proto.  It uses the existing histogram encoder from
metrics.

Bug: b:452034784
Change-Id: Iad53fef89aa7fe8557c525a0faade1c36f1e74ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7172292
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Kamil Jarosz <kjarosz@google.com>
Cr-Commit-Position: refs/heads/main@{#1549885}
NOKEYCHECK=True
GitOrigin-RevId: 1f50e3bc34075aa995e02b9a52e42def09dd30dc
diff --git a/private_metrics/BUILD.gn b/private_metrics/BUILD.gn
index 2e377af..81b9cc1 100644
--- a/private_metrics/BUILD.gn
+++ b/private_metrics/BUILD.gn
@@ -70,6 +70,8 @@
     "private_metrics_reporting_service.h",
     "private_metrics_unsent_log_store_metrics.cc",
     "private_metrics_unsent_log_store_metrics.h",
+    "puma_histogram_encoder.cc",
+    "puma_histogram_encoder.h",
   ]
   public_deps = [
     ":dwa_recorder",
@@ -85,6 +87,7 @@
     "//base",
     "//build:buildflag_header_h",
     "//components/metrics",
+    "//components/metrics:library_support",
     "//components/metrics:metrics_pref_names",
     "//components/prefs",
     "//components/version_info",
@@ -103,6 +106,7 @@
     "//components/metrics/dwa/dwa_entry_builder_unittest.cc",
     "//components/metrics/dwa/dwa_recorder_unittest.cc",
     "//components/metrics/dwa/dwa_service_unittest.cc",
+    "//components/metrics/private_metrics/puma_histogram_encoder_unittest.cc",
     "data_upload_config_downloader_unittest.cc",
     "dkm_entry_builder_unittest.cc",
   ]
diff --git a/private_metrics/puma_histogram_encoder.cc b/private_metrics/puma_histogram_encoder.cc
new file mode 100644
index 0000000..24ff4b8
--- /dev/null
+++ b/private_metrics/puma_histogram_encoder.cc
@@ -0,0 +1,41 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/private_metrics/puma_histogram_encoder.h"
+
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/puma_histogram_functions.h"
+#include "base/metrics/statistics_recorder.h"
+#include "components/metrics/histogram_encoder.h"
+
+namespace metrics::private_metrics {
+
+using ::private_metrics::PrivateUserMetrics;
+
+PumaHistogramEncoder::PumaHistogramEncoder(PrivateUserMetrics& puma_proto)
+    : puma_proto_(&puma_proto) {}
+
+PumaHistogramEncoder::~PumaHistogramEncoder() = default;
+
+void PumaHistogramEncoder::RecordDelta(const base::HistogramBase& histogram,
+                                       const base::HistogramSamples& snapshot) {
+  EncodeHistogramDelta(histogram.histogram_name(), snapshot,
+                       puma_proto_->add_histogram_events());
+}
+
+// static
+void PumaHistogramEncoder::EncodeHistogramDeltas(
+    base::PumaType puma_type,
+    PrivateUserMetrics& puma_proto) {
+  PumaHistogramEncoder encoder(puma_proto);
+  base::HistogramSnapshotManager snapshot_manager(&encoder);
+
+  base::StatisticsRecorder::PrepareDeltas(
+      /*include_persistent=*/true,
+      /*flags_to_set=*/base::Histogram::kNoFlags,
+      /*required_flags=*/PumaTypeToHistogramFlags(puma_type),
+      &snapshot_manager);
+}
+
+}  // namespace metrics::private_metrics
diff --git a/private_metrics/puma_histogram_encoder.h b/private_metrics/puma_histogram_encoder.h
new file mode 100644
index 0000000..9d37ab5
--- /dev/null
+++ b/private_metrics/puma_histogram_encoder.h
@@ -0,0 +1,45 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
+#define COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/puma_histogram_functions.h"
+#include "third_party/metrics_proto/private_metrics/private_user_metrics.pb.h"
+
+namespace metrics::private_metrics {
+
+// PumaHistogramEncoder is responsible for encoding histograms into PUMA protos,
+// which then can be used to upload PUMA records.
+class PumaHistogramEncoder : public base::HistogramFlattener {
+ public:
+  // Creates a new encoder which will encode histograms into the given proto.
+  explicit PumaHistogramEncoder(
+      ::private_metrics::PrivateUserMetrics& puma_proto);
+
+  PumaHistogramEncoder(const PumaHistogramEncoder&) = delete;
+  PumaHistogramEncoder& operator=(const PumaHistogramEncoder&) = delete;
+
+  ~PumaHistogramEncoder() override;
+
+  // Encodes histogram deltas (i.e. data logged since the last call) into the
+  // given PUMA proto. Only histograms with `required_flags` are included.
+  static void EncodeHistogramDeltas(
+      base::PumaType puma_type,
+      ::private_metrics::PrivateUserMetrics& puma_proto);
+
+ private:
+  // base::HistogramFlattener:
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override;
+
+  // Target proto object where the histograms are being encoded.
+  raw_ptr<::private_metrics::PrivateUserMetrics> puma_proto_;
+};
+
+}  // namespace metrics::private_metrics
+
+#endif  // COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_HISTOGRAM_ENCODER_H_
diff --git a/private_metrics/puma_histogram_encoder_unittest.cc b/private_metrics/puma_histogram_encoder_unittest.cc
new file mode 100644
index 0000000..68f8f1b
--- /dev/null
+++ b/private_metrics/puma_histogram_encoder_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/private_metrics/puma_histogram_encoder.h"
+
+#include "base/metrics/puma_histogram_functions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics::private_metrics {
+
+namespace {
+
+using ::metrics::HistogramEventProto;
+using ::private_metrics::PrivateUserMetrics;
+
+}  // namespace
+
+TEST(PumaHistogramEncoderTest, EncodesEmptyHistogramDeltas) {
+  PrivateUserMetrics puma_proto;
+
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto);
+
+  EXPECT_EQ(puma_proto.histogram_events_size(), 0);
+}
+
+TEST(PumaHistogramEncoderTest, EncodesHistogramDeltas) {
+  base::PumaHistogramExactLinear(
+      base::PumaType::kRc, "PumaHistogramEncoderTest.TestHistogram", 12, 100);
+
+  PrivateUserMetrics puma_proto;
+
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto);
+
+  EXPECT_EQ(puma_proto.histogram_events_size(), 1);
+
+  HistogramEventProto histogram_proto = puma_proto.histogram_events()[0];
+
+  EXPECT_TRUE(histogram_proto.has_name_hash());
+  EXPECT_EQ(histogram_proto.bucket_size(), 1);
+  EXPECT_EQ(histogram_proto.bucket()[0].count(), 1);
+}
+
+TEST(PumaHistogramEncoderTest, DoesNotDoubleEncodeHistogramDeltas) {
+  base::PumaHistogramBoolean(base::PumaType::kRc,
+                             "PumaHistogramEncoderTest.TestHistogram1", true);
+
+  PrivateUserMetrics puma_proto1;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto1);
+  EXPECT_EQ(puma_proto1.histogram_events_size(), 1);
+
+  PrivateUserMetrics puma_proto2;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto2);
+  EXPECT_EQ(puma_proto2.histogram_events_size(), 0);
+
+  base::PumaHistogramBoolean(base::PumaType::kRc,
+                             "PumaHistogramEncoderTest.TestHistogram2", true);
+
+  PrivateUserMetrics puma_proto3;
+  PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, puma_proto3);
+  EXPECT_EQ(puma_proto3.histogram_events_size(), 1);
+}
+
+}  // namespace metrics::private_metrics