| // Copyright 2023 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/structured/structured_metrics_service.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "components/metrics/log_decoder.h" |
| #include "components/metrics/metrics_provider.h" |
| #include "components/metrics/structured/recorder.h" |
| #include "components/metrics/structured/reporting/structured_metrics_reporting_service.h" |
| #include "components/metrics/structured/structured_events.h" |
| #include "components/metrics/structured/structured_metrics_client.h" |
| #include "components/metrics/structured/structured_metrics_prefs.h" |
| #include "components/metrics/structured/structured_metrics_recorder.h" |
| #include "components/metrics/structured/test/test_event_storage.h" |
| #include "components/metrics/structured/test/test_key_data_provider.h" |
| #include "components/metrics/test/test_metrics_service_client.h" |
| #include "components/metrics/unsent_log_store.h" |
| #include "components/metrics/unsent_log_store_metrics_impl.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace metrics::structured { |
| namespace { |
| |
| using events::v2::test_project_one::TestEventOne; |
| using events::v2::test_project_six::TestEventSeven; |
| using ::testing::ElementsAre; |
| using ::testing::SizeIs; |
| |
| // The name hash of "TestProjectOne". |
| constexpr uint64_t kProjectOneHash = UINT64_C(16881314472396226433); |
| // The name hash of "TestProjectThree". |
| constexpr uint64_t kProjectThreeHash = UINT64_C(10860358748803291132); |
| |
| class TestRecorder : public StructuredMetricsClient::RecordingDelegate { |
| public: |
| TestRecorder() = default; |
| TestRecorder(const TestRecorder& recorder) = delete; |
| TestRecorder& operator=(const TestRecorder& recorder) = delete; |
| ~TestRecorder() override = default; |
| |
| void RecordEvent(Event&& event) override { |
| Recorder::GetInstance()->RecordEvent(std::move(event)); |
| } |
| |
| bool IsReadyToRecord() const override { return true; } |
| }; |
| |
| class TestMetricsProvider : public metrics::MetricsProvider { |
| public: |
| TestMetricsProvider() = default; |
| ~TestMetricsProvider() override = default; |
| |
| void ProvideSystemProfileMetrics( |
| SystemProfileProto* system_profile) override {} |
| |
| void ProvideSystemProfileMetricsWithLogCreationTime( |
| base::TimeTicks log_creation_time, |
| SystemProfileProto* system_profile) override { |
| system_profile->add_field_trial()->set_name_id(1); |
| system_profile->add_field_trial()->set_name_id(2); |
| } |
| }; |
| } // namespace |
| |
| class StructuredMetricsServiceTest : public testing::Test { |
| public: |
| StructuredMetricsServiceTest() { |
| reporting::StructuredMetricsReportingService::RegisterPrefs( |
| prefs_.registry()); |
| } |
| |
| ~StructuredMetricsServiceTest() override = default; |
| |
| void SetUp() override { |
| Recorder::GetInstance()->SetUiTaskRunner( |
| task_environment_.GetMainThreadTaskRunner()); |
| StructuredMetricsClient::Get()->SetDelegate(&test_recorder_); |
| |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| |
| WriteTestingDeviceKeys(); |
| |
| WriteTestingProfileKeys(); |
| } |
| |
| void TearDown() override { |
| StructuredMetricsClient::Get()->UnsetDelegate(); |
| } |
| |
| void Init() { |
| auto key_data_provider = |
| std::make_unique<TestKeyDataProvider>(DeviceKeyFilePath()); |
| TestKeyDataProvider* test_key_data_provider = key_data_provider.get(); |
| auto recorder = base::MakeRefCounted<StructuredMetricsRecorder>( |
| std::move(key_data_provider), std::make_unique<TestEventStorage>()); |
| |
| service_ = std::make_unique<StructuredMetricsService>(&client_, &prefs_, |
| std::move(recorder)); |
| // Register the profile with the key data provider. |
| test_key_data_provider->OnProfileAdded(temp_dir_.GetPath()); |
| Wait(); |
| } |
| |
| void EnableRecording() { service_->EnableRecording(); } |
| void EnableReporting() { service_->EnableReporting(); } |
| |
| void DisableRecording() { service_->DisableRecording(); } |
| void DisableReporting() { service_->DisableReporting(); } |
| |
| base::FilePath ProfileKeyFilePath() { |
| return temp_dir_.GetPath() |
| .Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("keys")); |
| } |
| |
| base::FilePath DeviceKeyFilePath() { |
| return temp_dir_.GetPath() |
| .Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("device_keys")); |
| } |
| |
| base::FilePath DeviceEventsFilePath() { |
| return temp_dir_.GetPath() |
| .Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("events")); |
| } |
| |
| void WriteTestingProfileKeys() { |
| const int today = (base::Time::Now() - base::Time::UnixEpoch()).InDays(); |
| |
| KeyDataProto proto; |
| KeyProto& key_one = (*proto.mutable_keys())[kProjectOneHash]; |
| key_one.set_key("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); |
| key_one.set_last_rotation(today); |
| key_one.set_rotation_period(90); |
| |
| KeyProto& key_three = (*proto.mutable_keys())[kProjectThreeHash]; |
| key_three.set_key("cccccccccccccccccccccccccccccccc"); |
| key_three.set_last_rotation(today); |
| key_three.set_rotation_period(90); |
| |
| base::CreateDirectory(ProfileKeyFilePath().DirName()); |
| ASSERT_TRUE( |
| base::WriteFile(ProfileKeyFilePath(), proto.SerializeAsString())); |
| Wait(); |
| } |
| |
| void WriteTestingDeviceKeys() { |
| base::CreateDirectory(DeviceKeyFilePath().DirName()); |
| ASSERT_TRUE(base::WriteFile(DeviceKeyFilePath(), |
| KeyDataProto().SerializeAsString())); |
| Wait(); |
| } |
| |
| int GetPersistedLogCount() { |
| return prefs_.GetList(prefs::kLogStoreName).size(); |
| } |
| |
| ChromeUserMetricsExtension GetPersistedLog() { |
| EXPECT_THAT(GetPersistedLogCount(), 1); |
| metrics::UnsentLogStore result_unsent_log_store( |
| std::make_unique<UnsentLogStoreMetricsImpl>(), &prefs_, |
| prefs::kLogStoreName, /*metadata_pref_name=*/nullptr, |
| // Set to 3 so logs are not dropped in the test. |
| UnsentLogStore::UnsentLogStoreLimits{ |
| .min_log_count = 3, |
| }, |
| /*signing_key=*/std::string(), |
| /*logs_event_manager=*/nullptr); |
| |
| result_unsent_log_store.LoadPersistedUnsentLogs(); |
| result_unsent_log_store.StageNextLog(); |
| |
| ChromeUserMetricsExtension uma_proto; |
| EXPECT_TRUE(metrics::DecodeLogDataToProto( |
| result_unsent_log_store.staged_log(), &uma_proto)); |
| return uma_proto; |
| } |
| |
| StructuredMetricsService& service() { return *service_.get(); } |
| |
| void Wait() { task_environment_.RunUntilIdle(); } |
| |
| void AdvanceClock(int hours) { |
| task_environment_.AdvanceClock(base::Hours(hours)); |
| } |
| |
| private: |
| // Test resources must be declared before TaskEnvironment so they outlive |
| // the tasks that TaskEnvironment flushes during its destruction. |
| base::ScopedTempDir temp_dir_; |
| TestingPrefServiceSimple prefs_; |
| |
| // The TaskEnvironment is the manager for the test's threads and tasks. It |
| // must be declared after the resources that its tasks may access. Its |
| // destructor will block and flush all tasks, ensuring they can safely use |
| // the resources above before they are destroyed. |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::MainThreadType::UI, |
| base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED, |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| // TestRecorder is a delegate for the process-wide StructuredMetricsClient |
| // singleton. Its lifetime is managed by Set/UnsetDelegate in |
| // SetUp/TearDown. |
| TestRecorder test_recorder_; |
| |
| protected: |
| // The service holds a raw pointer to this client, so `client_` must be |
| // destroyed after `service_`. |
| metrics::TestMetricsServiceClient client_; |
| |
| // The service under test. Declared last so it is destroyed first, before |
| // its dependencies and before the TaskEnvironment that manages its tasks. |
| std::unique_ptr<StructuredMetricsService> service_; |
| }; |
| |
| TEST_F(StructuredMetricsServiceTest, PurgeInMemory) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1.0))); |
| Wait(); |
| |
| service_->Purge(); |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| // Nothing should be stored. |
| EXPECT_THAT(GetPersistedLogCount(), 0); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, PurgePersisted) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1.0))); |
| Wait(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| service_->Purge(); |
| |
| // Need to make sure there is a log to read. |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| // Nothing should be stored. |
| EXPECT_THAT(GetPersistedLogCount(), 0); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, RotateLogs) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1))); |
| Wait(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| service_.reset(); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, SystemProfileFilled) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1))); |
| Wait(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| EXPECT_TRUE(uma_proto.has_system_profile()); |
| |
| const SystemProfileProto& system_profile = uma_proto.system_profile(); |
| EXPECT_EQ(system_profile.channel(), client_.GetChannel()); |
| EXPECT_EQ(system_profile.app_version(), client_.GetVersionString()); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, MetricsProviderDataInSystemProfile) { |
| Init(); |
| |
| service().RegisterMetricsProvider(std::make_unique<TestMetricsProvider>()); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| Wait(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_TRUE(uma_proto.has_system_profile()); |
| EXPECT_THAT(uma_proto.system_profile().field_trial(), SizeIs(2)); |
| EXPECT_EQ(uma_proto.system_profile().field_trial(0).name_id(), 1u); |
| EXPECT_EQ(uma_proto.system_profile().field_trial(1).name_id(), 2u); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, DoesNotRecordWhenRecordingDisabled) { |
| Init(); |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1))); |
| Wait(); |
| |
| DisableRecording(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1))); |
| Wait(); |
| |
| EnableRecording(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, FlushOnShutdown) { |
| Init(); |
| EnableRecording(); |
| EnableReporting(); |
| |
| StructuredMetricsClient::Record( |
| std::move(TestEventOne().SetTestMetricTwo(1))); |
| StructuredMetricsClient::Record( |
| std::move(TestEventSeven().SetTestMetricSeven(1))); |
| Wait(); |
| |
| // Will flush the log. |
| service_.reset(); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| } |
| |
| } // namespace metrics::structured |