| // 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 "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/scoped_feature_list.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_features.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; |
| |
| // 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; } |
| }; |
| |
| } // namespace |
| |
| class StructuredMetricsServiceTest : public testing::Test { |
| public: |
| StructuredMetricsServiceTest() { |
| reporting::StructuredMetricsReportingService::RegisterPrefs( |
| prefs_.registry()); |
| } |
| |
| void SetUp() override { |
| feature_list_.InitWithFeatures({kEnabledStructuredMetricsService}, {}); |
| |
| 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 recorder = std::make_unique<StructuredMetricsRecorder>( |
| std::make_unique<TestKeyDataProvider>(DeviceKeyFilePath(), |
| ProfileKeyFilePath()), |
| std::make_unique<TestEventStorage>()); |
| |
| recorder->OnProfileAdded(temp_dir_.GetPath()); |
| service_ = std::make_unique<StructuredMetricsService>(&client_, &prefs_, |
| std::move(recorder)); |
| 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)); |
| } |
| |
| protected: |
| std::unique_ptr<StructuredMetricsService> service_; |
| metrics::TestMetricsServiceClient client_; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| TestingPrefServiceSimple prefs_; |
| |
| TestRecorder test_recorder_; |
| base::ScopedTempDir temp_dir_; |
| |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::MainThreadType::UI, |
| base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED, |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| }; |
| |
| TEST_F(StructuredMetricsServiceTest, PurgeInMemory) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1.0).Record(); |
| |
| service_->Purge(); |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| // Nothing should be stored. |
| EXPECT_THAT(GetPersistedLogCount(), 0); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, PurgePersisted) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1.0).Record(); |
| |
| 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(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1).Record(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| } |
| |
| TEST_F(StructuredMetricsServiceTest, SystemProfileFilled) { |
| Init(); |
| |
| EnableRecording(); |
| EnableReporting(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1).Record(); |
| |
| 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, DoesNotRecordWhenRecordingDisabled) { |
| Init(); |
| EnableRecording(); |
| EnableReporting(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1).Record(); |
| |
| DisableRecording(); |
| |
| TestEventOne().SetTestMetricTwo(1).Record(); |
| TestEventSeven().SetTestMetricSeven(1).Record(); |
| |
| EnableRecording(); |
| |
| service_->Flush(metrics::MetricsLogsEventManager::CreateReason::kUnknown); |
| |
| const auto uma_proto = GetPersistedLog(); |
| EXPECT_THAT(uma_proto.structured_data().events().size(), 2); |
| } |
| |
| } // namespace metrics::structured |