puma: Implement PumaService for sending PUMA metrics PumaService is responsible for uploading PUMA histograms to the Private Metrics endpoint. It does so periodically, similarly to other metrics services (e.g. DWA). Bug: b:452034784, b:456094891 Change-Id: Iba78534938f759873acb7567d48af2421a50a221 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7176328 Reviewed-by: Alexei Svitkine <asvitkine@chromium.org> Commit-Queue: Kamil Jarosz <kjarosz@google.com> Reviewed-by: Luc Nguyen <lucnguyen@google.com> Reviewed-by: Matt Dembski <dembski@google.com> Cr-Commit-Position: refs/heads/main@{#1551654} NOKEYCHECK=True GitOrigin-RevId: 4162fa81a003df8e715e77ee64a7410186727e5c
diff --git a/private_metrics/BUILD.gn b/private_metrics/BUILD.gn index 81b9cc1..033a665 100644 --- a/private_metrics/BUILD.gn +++ b/private_metrics/BUILD.gn
@@ -64,7 +64,6 @@ "dkm_entry_builder.h", "dkm_entry_builder_base.cc", "dkm_entry_builder_base.h", - "private_metrics_pref_names.cc", "private_metrics_pref_names.h", "private_metrics_reporting_service.cc", "private_metrics_reporting_service.h", @@ -72,6 +71,8 @@ "private_metrics_unsent_log_store_metrics.h", "puma_histogram_encoder.cc", "puma_histogram_encoder.h", + "puma_service.cc", + "puma_service.h", ] public_deps = [ ":dwa_recorder", @@ -107,6 +108,7 @@ "//components/metrics/dwa/dwa_recorder_unittest.cc", "//components/metrics/dwa/dwa_service_unittest.cc", "//components/metrics/private_metrics/puma_histogram_encoder_unittest.cc", + "//components/metrics/private_metrics/puma_service_unittest.cc", "data_upload_config_downloader_unittest.cc", "dkm_entry_builder_unittest.cc", ]
diff --git a/private_metrics/private_metrics_features.cc b/private_metrics/private_metrics_features.cc index 05c6c91..46b6422 100644 --- a/private_metrics/private_metrics_features.cc +++ b/private_metrics/private_metrics_features.cc
@@ -8,4 +8,18 @@ BASE_FEATURE(kPrivateMetricsFeature, base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kPrivateMetricsPuma, base::FEATURE_DISABLED_BY_DEFAULT); + +const base::FeatureParam<size_t> kPrivateMetricsPumaMinLogQueueCount{ + &kPrivateMetricsPuma, "puma_min_log_queue_count", 10}; + +const base::FeatureParam<size_t> kPrivateMetricsPumaMinLogQueueSizeBytes{ + &kPrivateMetricsPuma, "puma_min_log_queue_size_bytes", + 300 * 1024}; // 300 KiB + +const base::FeatureParam<size_t> kPrivateMetricsPumaMaxLogSizeBytes{ + &kPrivateMetricsPuma, "puma_max_log_size_bytes", 1024 * 1024}; // 1 MiB + +BASE_FEATURE(kPrivateMetricsPumaRc, base::FEATURE_DISABLED_BY_DEFAULT); + } // namespace metrics::private_metrics
diff --git a/private_metrics/private_metrics_features.h b/private_metrics/private_metrics_features.h index 2b5a031..3d894df 100644 --- a/private_metrics/private_metrics_features.h +++ b/private_metrics/private_metrics_features.h
@@ -7,6 +7,7 @@ #include "base/component_export.h" #include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" namespace metrics::private_metrics { @@ -15,6 +16,23 @@ // go/chrome-trusted-private-metrics and go/etld-plus-one-metrics. BASE_DECLARE_FEATURE(kPrivateMetricsFeature); +// Enables Private UMA service. When enabled, PUMA histograms will be reported +// to the Private Metrics endpoint. +// +// Note: it's likely this is not the only feature you want to enable, as a +// specific PUMA type can also be implemented behind a feature. +BASE_DECLARE_FEATURE(kPrivateMetricsPuma); + +// The following feature params are used to parameterize unsent log store +// limits for PUMA. See UnsentLogStoreLimits. +extern const base::FeatureParam<size_t> kPrivateMetricsPumaMinLogQueueCount; +extern const base::FeatureParam<size_t> kPrivateMetricsPumaMinLogQueueSizeBytes; +extern const base::FeatureParam<size_t> kPrivateMetricsPumaMaxLogSizeBytes; + +// Enables Private UMA for Regional Capabilities. Enabling this feature will +// collect and uploads logs of this type of PUMA. +BASE_DECLARE_FEATURE(kPrivateMetricsPumaRc); + } // namespace metrics::private_metrics #endif // COMPONENTS_METRICS_PRIVATE_METRICS_PRIVATE_METRICS_FEATURES_H_
diff --git a/private_metrics/private_metrics_pref_names.cc b/private_metrics/private_metrics_pref_names.cc deleted file mode 100644 index 1f815d5..0000000 --- a/private_metrics/private_metrics_pref_names.cc +++ /dev/null
@@ -1,11 +0,0 @@ -// 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/private_metrics_pref_names.h" - -namespace metrics::private_metrics::prefs { - -const char kUnsentLogStoreName[] = "private_metrics.persistent_logs"; - -} // namespace metrics::private_metrics::prefs
diff --git a/private_metrics/private_metrics_pref_names.h b/private_metrics/private_metrics_pref_names.h index d0e4ed2..f67f8df 100644 --- a/private_metrics/private_metrics_pref_names.h +++ b/private_metrics/private_metrics_pref_names.h
@@ -8,7 +8,10 @@ namespace metrics::private_metrics::prefs { // Preference which stores serialized private metrics logs to be uploaded. -extern const char kUnsentLogStoreName[]; +inline constexpr char kUnsentLogStoreName[] = "private_metrics.persistent_logs"; + +// Preference which stores client_id for PUMA Regional Capabilities. +inline constexpr char kPumaRcClientId[] = "private_metrics.puma.client_id.rc"; } // namespace metrics::private_metrics::prefs
diff --git a/private_metrics/puma_service.cc b/private_metrics/puma_service.cc new file mode 100644 index 0000000..31346a9 --- /dev/null +++ b/private_metrics/puma_service.cc
@@ -0,0 +1,247 @@ +// 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_service.h" + +#include <optional> + +#include "base/metrics/puma_histogram_functions.h" +#include "base/rand_util.h" +#include "base/version.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/private_metrics/private_metrics_features.h" +#include "components/metrics/private_metrics/private_metrics_pref_names.h" +#include "components/metrics/private_metrics/puma_histogram_encoder.h" +#include "components/version_info/version_info.h" +#include "third_party/metrics_proto/private_metrics/private_metrics.pb.h" +#include "third_party/metrics_proto/private_metrics/private_user_metrics.pb.h" +#include "third_party/metrics_proto/private_metrics/system_profiles/coarse_system_profile.pb.h" +#include "third_party/metrics_proto/private_metrics/system_profiles/rc_coarse_system_profile.pb.h" + +namespace metrics::private_metrics { + +namespace { + +// TODO(b/463573197): Unify namespaces used for code and protos +using ::private_metrics::Platform; +using ::private_metrics::PrivateMetricEndpointPayload; +using ::private_metrics::PrivateUserMetrics; +using ::private_metrics::RcCoarseSystemProfile; +using ::private_metrics::RcCoarseSystemProfile_Channel; + +// Retrieves the storage parameters to control the reporting service. +UnsentLogStore::UnsentLogStoreLimits GetLogStoreLimits() { + return UnsentLogStore::UnsentLogStoreLimits{ + .min_log_count = kPrivateMetricsPumaMinLogQueueCount.Get(), + .min_queue_size_bytes = kPrivateMetricsPumaMinLogQueueSizeBytes.Get(), + .max_log_size_bytes = kPrivateMetricsPumaMaxLogSizeBytes.Get(), + }; +} + +PrivateMetricEndpointPayload BuildPrivateMetricEndpointPayload( + PrivateUserMetrics private_uma_report) { + PrivateMetricEndpointPayload payload; + payload.set_report_type(PrivateMetricEndpointPayload::PUMA_RC); + payload.mutable_private_uma_report()->Swap(&private_uma_report); + return payload; +} + +RcCoarseSystemProfile_Channel MapChannelToRcChannel( + SystemProfileProto::Channel channel) { + switch (channel) { + case SystemProfileProto::CHANNEL_CANARY: + return RcCoarseSystemProfile::CHANNEL_CANARY; + case SystemProfileProto::CHANNEL_DEV: + return RcCoarseSystemProfile::CHANNEL_DEV; + case SystemProfileProto::CHANNEL_BETA: + return RcCoarseSystemProfile::CHANNEL_BETA; + case SystemProfileProto::CHANNEL_STABLE: + return RcCoarseSystemProfile::CHANNEL_STABLE; + default: + return RcCoarseSystemProfile::CHANNEL_UNKNOWN; + } +} + +Platform GetCurrentPlatform() { +#if BUILDFLAG(IS_WIN) + return Platform::PLATFORM_WINDOWS; +#elif BUILDFLAG(IS_MAC) + return Platform::PLATFORM_MACOS; +#elif BUILDFLAG(IS_LINUX) + return Platform::PLATFORM_LINUX; +#elif BUILDFLAG(IS_ANDROID) + // TODO(b/463580425): Differentiate between Android platforms. + return Platform::PLATFORM_ANDROID; +#elif BUILDFLAG(IS_IOS) + return Platform::PLATFORM_IOS; +#elif BUILDFLAG(IS_CHROMEOS) + return Platform::PLATFORM_CHROMEOS; +#else + return Platform::PLATFORM_OTHER; +#endif +} + +} // namespace + +PumaService::PumaService(MetricsServiceClient* client, PrefService* local_state) + : client_(client), + local_state_(local_state), + reporting_service_(client, local_state, GetLogStoreLimits(), false) { + reporting_service_.Initialize(); + + // Set up the scheduler for PumaService. + auto rotate_callback = base::BindRepeating(&PumaService::RotateLog, + self_ptr_factory_.GetWeakPtr()); + auto get_upload_interval_callback = base::BindRepeating( + &MetricsServiceClient::GetUploadInterval, base::Unretained(client_)); + bool fast_startup = client_->ShouldStartUpFast(); + scheduler_ = std::make_unique<metrics::MetricsRotationScheduler>( + rotate_callback, get_upload_interval_callback, fast_startup); + scheduler_->InitTaskComplete(); +} + +PumaService::~PumaService() { + DisableReporting(); +} + +PrivateMetricsReportingService* PumaService::reporting_service() { + return &reporting_service_; +} + +void PumaService::EnableReporting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (reporting_service_.reporting_active()) { + return; + } + + scheduler_->Start(); + reporting_service_.EnableReporting(); +} + +void PumaService::DisableReporting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!reporting_service_.reporting_active()) { + return; + } + + reporting_service_.DisableReporting(); + scheduler_->Stop(); + Flush(metrics::MetricsLogsEventManager::CreateReason::kServiceShutdown); +} + +void PumaService::Flush(metrics::MetricsLogsEventManager::CreateReason reason) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + BuildPrivateMetricRcReportAndStoreLog(reason); + reporting_service_.unsent_log_store()->TrimAndPersistUnsentLogs(true); +} + +// static +bool PumaService::IsPumaEnabled() { + if (base::FeatureList::IsEnabled(kPrivateMetricsFeature)) { + // Note: PUMA should never be enabled with Private Metrics, as PUMA's + // implementation assumes that the Private Metrics feature is disabled. + return false; + } + + return base::FeatureList::IsEnabled(kPrivateMetricsPuma); +} + +// static +void PumaService::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterUint64Pref(prefs::kPumaRcClientId, 0u); + // PrivateMetricsReportingService prefs are registered already by DWA. +} + +std::optional<uint64_t> PumaService::GetPumaRcClientId() { + if (!base::FeatureList::IsEnabled(kPrivateMetricsPumaRc)) { + // This is a failsafe mechanism that prevents reporting IDs in case the + // feature is disabled. + return std::nullopt; + } + + uint64_t client_id = local_state_->GetUint64(prefs::kPumaRcClientId); + + // Generate the ID in case it's not available. + if (client_id == 0u) { + while (client_id == 0u) { + client_id = base::RandUint64(); + } + local_state_->SetUint64(prefs::kPumaRcClientId, client_id); + } + + return client_id; +} + +void PumaService::RotateLog() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!reporting_service_.unsent_log_store()->has_unsent_logs()) { + BuildPrivateMetricRcReportAndStoreLog( + metrics::MetricsLogsEventManager::CreateReason::kPeriodic); + } + reporting_service_.Start(); + scheduler_->RotationFinished(); +} + +void PumaService::RecordRcProfile(RcCoarseSystemProfile* rc_profile) { + rc_profile->set_channel(MapChannelToRcChannel(client_->GetChannel())); + + rc_profile->set_is_extended_stable_channel( + client_->IsExtendedStableChannel()); + + rc_profile->set_platform(GetCurrentPlatform()); + rc_profile->set_milestone(version_info::GetMajorVersionNumberAsInt()); +} + +std::optional<::private_metrics::PrivateUserMetrics> +PumaService::BuildPrivateMetricRcReport() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!base::FeatureList::IsEnabled(kPrivateMetricsPumaRc)) { + return std::nullopt; + } + + ::private_metrics::PrivateUserMetrics report; + PumaHistogramEncoder::EncodeHistogramDeltas(base::PumaType::kRc, report); + + if (report.histogram_events_size() == 0) { + // No histograms to report. + return std::nullopt; + } + + std::optional<uint64_t> client_id = GetPumaRcClientId(); + if (!client_id.has_value()) { + return std::nullopt; + } + report.set_client_id(client_id.value()); + RecordRcProfile(report.mutable_rc_profile()); + + return std::move(report); +} + +void PumaService::BuildPrivateMetricRcReportAndStoreLog( + metrics::MetricsLogsEventManager::CreateReason reason) { + std::optional<::private_metrics::PrivateUserMetrics> report = + BuildPrivateMetricRcReport(); + + if (!report) { + // No report to store. + return; + } + + auto payload = BuildPrivateMetricEndpointPayload(*std::move(report)); + + std::string serialized_log; + if (!payload.SerializeToString(&serialized_log)) { + // Drop the report in case serialization fails. + // TODO(b/463571627): Add a histogram + return; + } + + LogMetadata metadata; + reporting_service_.unsent_log_store()->StoreLog(serialized_log, metadata, + reason); +} + +} // namespace metrics::private_metrics
diff --git a/private_metrics/puma_service.h b/private_metrics/puma_service.h new file mode 100644 index 0000000..ad78dce --- /dev/null +++ b/private_metrics/puma_service.h
@@ -0,0 +1,110 @@ +// 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_SERVICE_H_ +#define COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_SERVICE_H_ + +#include "components/metrics/metrics_rotation_scheduler.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/private_metrics/private_metrics_reporting_service.h" +#include "components/prefs/pref_service.h" +#include "third_party/metrics_proto/private_metrics/private_user_metrics.pb.h" + +namespace private_metrics { +class RcCoarseSystemProfile; +} + +namespace metrics::private_metrics { + +// PumaService is responsible for uploading Private UMA histograms. +class PumaService { + public: + PumaService(MetricsServiceClient* client, PrefService* pref_service); + PumaService(const PumaService&) = delete; + PumaService& operator=(const PumaService&) = delete; + + ~PumaService(); + + PrivateMetricsReportingService* reporting_service(); + + void EnableReporting(); + void DisableReporting(); + + // Flushes any event currently in the recorder to prefs. + void Flush(metrics::MetricsLogsEventManager::CreateReason reason); + + // Returns true if the PUMA feature is enabled, false otherwise. + static bool IsPumaEnabled(); + + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, + RcBuildReportAndStore_DoesCreateAndStoreReport); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, + RcBuildReport_DoesCreateReportWithEvents); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, + RcBuildReport_DoesNotCreateReportWithoutEvents); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, + RcBuildReport_PayloadProperlyFilled); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, RcClientId_IsNonNull); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, + RcClientId_SameWhenExecutedMultipleTimes); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, RcClientId_UpdatesPref); + FRIEND_TEST_ALL_PREFIXES(PumaServiceRcTest, RcRecordCoarseSystemProfile); + FRIEND_TEST_ALL_PREFIXES( + PumaServiceTest, + RcBuildReport_DoesNotCreateReportWithFeatureDisabled); + FRIEND_TEST_ALL_PREFIXES(PumaServiceTest, + RcClientId_IsNullWhenPumaRcIsDisabled); + + // Gets or generates client ID for PUMA for Regional Capabilities. + // + // This client ID is persistent and not joinable with any other client ID. + // `std::nullopt` is returned when the feature is disabled, and IDs should not + // be collected. + std::optional<uint64_t> GetPumaRcClientId(); + + // Records coarse system profile for PUMA for Regional Capabilities. + void RecordRcProfile(::private_metrics::RcCoarseSystemProfile* rc_profile); + + // Constructs a new PrivateMetricReport from available data from PUMA for + // Regional Capabilities, and returns it. + // + // Marks the data as processed, and makes sure it will not be processed again. + std::optional<::private_metrics::PrivateUserMetrics> + BuildPrivateMetricRcReport(); + + // Constructs a new PrivateMetricReport from available data from PUMA for + // Regional Capabilities, and stores it in the unsent log store. + // + // Marks the data as processed, and makes sure it will not be processed again. + void BuildPrivateMetricRcReportAndStoreLog( + metrics::MetricsLogsEventManager::CreateReason reason); + + // Periodically called by `scheduler_` to advance processing of logs. + void RotateLog(); + + SEQUENCE_CHECKER(sequence_checker_); + + // The metrics client `this` is service is associated with. + raw_ptr<MetricsServiceClient> client_; + + // Local state; preferences not associated with a specific profile. + raw_ptr<PrefService> local_state_; + + // Service for uploading serialized logs to Private Metrics endpoint. + PrivateMetricsReportingService reporting_service_; + + // The scheduler for determining when uploads should happen. + std::unique_ptr<MetricsRotationScheduler> scheduler_; + + // Weak pointers factory used to post task on different threads. All weak + // pointers managed by this factory have the same lifetime as PumaService. + base::WeakPtrFactory<PumaService> self_ptr_factory_{this}; +}; + +} // namespace metrics::private_metrics + +#endif // COMPONENTS_METRICS_PRIVATE_METRICS_PUMA_SERVICE_H_
diff --git a/private_metrics/puma_service_unittest.cc b/private_metrics/puma_service_unittest.cc new file mode 100644 index 0000000..c511d3a --- /dev/null +++ b/private_metrics/puma_service_unittest.cc
@@ -0,0 +1,395 @@ +// 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_service.h" + +#include <memory> + +#include "base/metrics/puma_histogram_functions.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "components/metrics/private_metrics/private_metrics_features.h" +#include "components/metrics/private_metrics/private_metrics_pref_names.h" +#include "components/metrics/test/test_metrics_service_client.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/private_metrics/system_profiles/coarse_system_profile.pb.h" +#include "third_party/metrics_proto/private_metrics/system_profiles/rc_coarse_system_profile.pb.h" + +namespace metrics::private_metrics { + +namespace { + +using ::private_metrics::RcCoarseSystemProfile; + +class PumaServiceTest : public testing::Test { + public: + PumaServiceTest() = default; + PumaServiceTest(const PumaServiceTest&) = delete; + PumaServiceTest& operator=(const PumaServiceTest&) = delete; + ~PumaServiceTest() override = default; + + void SetUp() override { + scoped_feature_list_.InitWithFeatures(GetEnabledFeatures(), {}); + + PumaService::RegisterPrefs(prefs_.registry()); + PrivateMetricsReportingService::RegisterPrefs(prefs_.registry()); + + puma_service_ = std::make_unique<PumaService>(&client_, &prefs_); + } + + void TearDown() override { puma_service_ = nullptr; } + + virtual std::vector<base::test::FeatureRef> GetEnabledFeatures() { + return {kPrivateMetricsPuma}; + } + + size_t GetUnsentLogCount() { + return puma_service_->reporting_service()->unsent_log_store()->size(); + } + + size_t GetPersistedLogCount() { + return prefs_.GetList(prefs::kUnsentLogStoreName).size(); + } + + protected: + TestMetricsServiceClient client_; + TestingPrefServiceSimple prefs_; + base::test::ScopedFeatureList scoped_feature_list_; + + std::unique_ptr<PumaService> puma_service_; + + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::MainThreadType::UI, + base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED, + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; +}; + +class PumaServiceFeaturesTest + : public PumaServiceTest, + public testing::WithParamInterface< + std::tuple<std::vector<base::test::FeatureRef>, bool>> { + public: + PumaServiceFeaturesTest() = default; + + PumaServiceFeaturesTest(const PumaServiceFeaturesTest&) = delete; + PumaServiceFeaturesTest& operator=(const PumaServiceFeaturesTest&) = delete; + + ~PumaServiceFeaturesTest() override = default; + + std::vector<base::test::FeatureRef> GetEnabledFeatures() override { + return std::get<0>(GetParam()); + } +}; + +class PumaServiceRcTest : public PumaServiceTest { + public: + PumaServiceRcTest() = default; + + PumaServiceRcTest(const PumaServiceRcTest&) = delete; + PumaServiceRcTest& operator=(const PumaServiceRcTest&) = delete; + + ~PumaServiceRcTest() override = default; + + std::vector<base::test::FeatureRef> GetEnabledFeatures() override { + return {kPrivateMetricsPuma, kPrivateMetricsPumaRc}; + } +}; + +} // namespace + +INSTANTIATE_TEST_SUITE_P( + All, + PumaServiceFeaturesTest, + testing::ValuesIn( + std::vector<std::tuple<std::vector<base::test::FeatureRef>, bool>>{ + {{}, false}, + {{kPrivateMetricsFeature}, false}, + {{kPrivateMetricsPuma}, true}, + {{kPrivateMetricsPuma, kPrivateMetricsFeature}, false}, + })); + +TEST_P(PumaServiceFeaturesTest, IsPumaEnabled) { + EXPECT_EQ(PumaService::IsPumaEnabled(), std::get<1>(GetParam())); +} + +TEST_F(PumaServiceRcTest, RcRecordCoarseSystemProfile) { + client_.set_is_extended_stable_channel(true); + + RcCoarseSystemProfile rc_profile; + + puma_service_->RecordRcProfile(&rc_profile); + + EXPECT_TRUE(rc_profile.has_channel()); + EXPECT_EQ(rc_profile.channel(), RcCoarseSystemProfile::CHANNEL_BETA); + + EXPECT_TRUE(rc_profile.has_is_extended_stable_channel()); + EXPECT_EQ(rc_profile.is_extended_stable_channel(), true); + + EXPECT_TRUE(rc_profile.has_milestone()); + EXPECT_TRUE(rc_profile.has_platform()); + + // TODO(b:452034784) Implement profile_country_id mapping. + EXPECT_FALSE(rc_profile.has_profile_country_id()); +} + +TEST_F(PumaServiceTest, RcClientId_IsNullWhenPumaRcIsDisabled) { + EXPECT_FALSE(puma_service_->GetPumaRcClientId().has_value()); +} + +TEST_F(PumaServiceRcTest, RcClientId_IsNonNull) { + EXPECT_TRUE(puma_service_->GetPumaRcClientId().has_value()); +} + +TEST_F(PumaServiceRcTest, RcClientId_SameWhenExecutedMultipleTimes) { + std::optional<uint64_t> client_id_1 = puma_service_->GetPumaRcClientId(); + std::optional<uint64_t> client_id_2 = puma_service_->GetPumaRcClientId(); + + EXPECT_TRUE(client_id_1.has_value()); + EXPECT_EQ(client_id_1, client_id_2); +} + +TEST_F(PumaServiceRcTest, RcClientId_UpdatesPref) { + EXPECT_EQ(prefs_.GetUint64(prefs::kPumaRcClientId), 0u); + + std::optional<uint64_t> client_id_1 = puma_service_->GetPumaRcClientId(); + uint64_t pref_value_1 = prefs_.GetUint64(prefs::kPumaRcClientId); + + EXPECT_NE(pref_value_1, 0u); + EXPECT_EQ(client_id_1, pref_value_1); + + std::optional<uint64_t> client_id_2 = puma_service_->GetPumaRcClientId(); + uint64_t pref_value_2 = prefs_.GetUint64(prefs::kPumaRcClientId); + + EXPECT_EQ(client_id_2, pref_value_2); + EXPECT_EQ(pref_value_1, pref_value_2); +} + +TEST_F(PumaServiceRcTest, RcBuildReport_DoesNotCreateReportWithoutEvents) { + auto report = puma_service_->BuildPrivateMetricRcReport(); + EXPECT_FALSE(report.has_value()); +} + +TEST_F(PumaServiceRcTest, RcBuildReport_DoesCreateReportWithEvents) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean1", true); + + auto report = puma_service_->BuildPrivateMetricRcReport(); + EXPECT_TRUE(report.has_value()); +} + +TEST_F(PumaServiceTest, RcBuildReport_DoesNotCreateReportWithFeatureDisabled) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean2", true); + + auto report = puma_service_->BuildPrivateMetricRcReport(); + EXPECT_FALSE(report.has_value()); +} + +TEST_F(PumaServiceRcTest, RcBuildReport_PayloadProperlyFilled) { + base::PumaHistogramExactLinear( + base::PumaType::kRc, "PUMA.PumaServiceTestHistogram.Linear1", 12, 100); + + auto report = puma_service_->BuildPrivateMetricRcReport(); + + EXPECT_TRUE(report.has_value()); + + EXPECT_TRUE(report->has_client_id()); + EXPECT_NE(report->client_id(), 0u); + + EXPECT_TRUE(report->has_rc_profile()); + + EXPECT_EQ(report->histogram_events_size(), 1); + + auto histogram_event = report->histogram_events().at(0); + EXPECT_TRUE(histogram_event.has_name_hash()); + EXPECT_EQ(histogram_event.bucket_size(), 1); +} + +TEST_F(PumaServiceRcTest, RcBuildReportAndStore_DoesCreateAndStoreReport) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean3", true); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + + puma_service_->BuildPrivateMetricRcReportAndStoreLog( + metrics::MetricsLogsEventManager::CreateReason::kPeriodic); + + EXPECT_EQ(GetUnsentLogCount(), 1u); +} + +TEST_F(PumaServiceRcTest, RcLogsArePersistedAfterFlush) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean4", true); + + EXPECT_EQ(GetPersistedLogCount(), 0u); + puma_service_->Flush( + metrics::MetricsLogsEventManager::CreateReason::kPeriodic); + EXPECT_EQ(GetPersistedLogCount(), 1u); +} + +TEST_F(PumaServiceRcTest, RcLogsArePersistedAfterDisablingReporting) { + puma_service_->EnableReporting(); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean5", true); + + EXPECT_EQ(GetPersistedLogCount(), 0u); + puma_service_->DisableReporting(); + EXPECT_EQ(GetPersistedLogCount(), 1u); +} + +TEST_F(PumaServiceRcTest, + RcLogsAreNotPersistedAfterDisablingReportingWhenReportingWasDisabled) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean6", true); + + EXPECT_EQ(GetPersistedLogCount(), 0u); + puma_service_->DisableReporting(); + EXPECT_EQ(GetPersistedLogCount(), 0u); +} + +TEST_F(PumaServiceRcTest, RcLogsArePersistedAfterDestruction) { + puma_service_->EnableReporting(); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean7", true); + + EXPECT_EQ(GetPersistedLogCount(), 0u); + puma_service_ = nullptr; + EXPECT_EQ(GetPersistedLogCount(), 1u); +} + +TEST_F(PumaServiceRcTest, + RcLogsAreNotPersistedAfterDestructionWhenReportingWasInactive) { + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean8", true); + + EXPECT_EQ(GetPersistedLogCount(), 0u); + puma_service_ = nullptr; + EXPECT_EQ(GetPersistedLogCount(), 0u); +} + +TEST_F(PumaServiceTest, EnableReportingStartsSchedulerTask) { + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u); + + // When the reporting is enabled, we should have: + // 1. the log rotation scheduler, and + // 2. the uploader. + puma_service_->EnableReporting(); + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 2u); + + puma_service_->DisableReporting(); + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u); +} + +TEST_F(PumaServiceTest, EnableReportingTwiceDoesNotStartAdditionalTasks) { + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u); + + puma_service_->EnableReporting(); + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 2u); + + puma_service_->EnableReporting(); + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 2u); +} + +TEST_F(PumaServiceRcTest, UploadUnsentLogs) { + puma_service_->EnableReporting(); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean9", true); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + EXPECT_EQ(GetPersistedLogCount(), 0u); + + base::TimeDelta upload_interval = client_.GetUploadInterval(); + task_environment_.FastForwardBy(upload_interval); + + EXPECT_EQ(GetUnsentLogCount(), 1u); + EXPECT_EQ(GetPersistedLogCount(), 0u); + + EXPECT_NE(client_.uploader(), nullptr); + EXPECT_TRUE(client_.uploader()->is_uploading()); + + // Simulate logs upload. + client_.uploader()->CompleteUpload(200); + EXPECT_FALSE(client_.uploader()->is_uploading()); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + EXPECT_EQ(GetPersistedLogCount(), 0u); +} + +TEST_F(PumaServiceRcTest, UploadPersistedLogs) { + puma_service_->EnableReporting(); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean10", true); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + EXPECT_EQ(GetPersistedLogCount(), 0u); + + // Simulate forced shutdown + puma_service_ = nullptr; + + EXPECT_EQ(GetPersistedLogCount(), 1u); + + puma_service_ = std::make_unique<PumaService>(&client_, &prefs_); + + EXPECT_EQ(GetUnsentLogCount(), 1u); + EXPECT_EQ(GetPersistedLogCount(), 1u); + + puma_service_->EnableReporting(); + + base::TimeDelta upload_interval = client_.GetUploadInterval(); + task_environment_.FastForwardBy(upload_interval); + + EXPECT_EQ(GetUnsentLogCount(), 1u); + EXPECT_EQ(GetPersistedLogCount(), 1u); + + EXPECT_NE(client_.uploader(), nullptr); + EXPECT_TRUE(client_.uploader()->is_uploading()); + + // Simulate logs upload. + client_.uploader()->CompleteUpload(200); + EXPECT_FALSE(client_.uploader()->is_uploading()); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + EXPECT_EQ(GetPersistedLogCount(), 0u); + + EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 1u); +} + +TEST_F(PumaServiceRcTest, LogsUploadedPeriodically) { + puma_service_->EnableReporting(); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean11", true); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + + base::TimeDelta upload_interval = client_.GetUploadInterval(); + task_environment_.FastForwardBy(upload_interval); + + EXPECT_EQ(GetUnsentLogCount(), 1u); + + // Simulate logs upload. + client_.uploader()->CompleteUpload(200); + + EXPECT_EQ(GetUnsentLogCount(), 0u); + + base::PumaHistogramBoolean(base::PumaType::kRc, + "PUMA.PumaServiceTestHistogram.Boolean12", true); + + task_environment_.FastForwardBy(upload_interval); + + EXPECT_EQ(GetUnsentLogCount(), 1u); + + // Simulate logs upload. + client_.uploader()->CompleteUpload(200); + + EXPECT_EQ(GetUnsentLogCount(), 0u); +} + +} // namespace metrics::private_metrics