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