| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/metrics/structured/arena_event_buffer.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "components/metrics/structured/lib/event_buffer.h" |
| #include "components/metrics/structured/structured_metrics_features.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/metrics_proto/structured_data.pb.h" |
| |
| namespace metrics::structured { |
| namespace { |
| |
| using ::google::protobuf::RepeatedPtrField; |
| |
| // Creates an event for testing. The serialized size of this event is about 9 |
| // bytes. |
| StructuredEventProto TestEvent(uint64_t id) { |
| StructuredEventProto event; |
| event.set_device_project_id(id); |
| return event; |
| } |
| |
| EventsProto ReadEvents(const base::FilePath& path) { |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(path, &content)); |
| EventsProto proto; |
| EXPECT_TRUE(proto.MergeFromString(content)); |
| return proto; |
| } |
| |
| } // namespace |
| |
| class ArenaEventBufferTest : public testing::Test { |
| public: |
| const base::TimeDelta kWriteDelay = base::Seconds(0); |
| |
| ArenaEventBufferTest() |
| : profile_manager_(TestingBrowserProcess::GetGlobal()) {} |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| ASSERT_TRUE(profile_manager_.SetUp()); |
| } |
| |
| base::FilePath GetPath() { |
| return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("proto_file")); |
| } |
| |
| base::FilePath GetAltPath() { |
| return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("alt_proto_file")); |
| } |
| |
| std::unique_ptr<ArenaEventBuffer> CreateTestBuffer(uint64_t max_size) { |
| return std::make_unique<ArenaEventBuffer>(GetPath(), kWriteDelay, max_size); |
| } |
| |
| void Wait() { task_environment_.RunUntilIdle(); } |
| |
| TestingProfile* AddProfile() { |
| return profile_manager_.CreateTestingProfile("p1"); |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_{ |
| content::BrowserTaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| base::ScopedTempDir temp_dir_; |
| TestingProfileManager profile_manager_; |
| }; |
| |
| TEST_F(ArenaEventBufferTest, OkEvent) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/128); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| } |
| |
| TEST_F(ArenaEventBufferTest, FullEvent) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/128); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| |
| // Create an event that is larger then the heuristic. |
| auto event2 = TestEvent(2); |
| |
| // Add 10 metrics |
| for (int i = 0; i < 10; ++i) { |
| auto* metric = event2.add_metrics(); |
| metric->set_name_hash(i); |
| metric->set_value_string("metric value"); |
| } |
| |
| EXPECT_EQ(buffer->AddEvent(event2), Result::kFull); |
| } |
| |
| TEST_F(ArenaEventBufferTest, Purge) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/128); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| |
| buffer->Purge(); |
| Wait(); |
| |
| EXPECT_EQ(buffer->resource_info().used_size_bytes, 0ul); |
| |
| EXPECT_FALSE(base::PathExists(GetPath())); |
| } |
| |
| TEST_F(ArenaEventBufferTest, UpdatePath) { |
| EventsProto events; |
| events.mutable_events()->Add(TestEvent(2)); |
| |
| std::string content; |
| ASSERT_TRUE(events.SerializeToString(&content)); |
| ASSERT_TRUE(base::WriteFile(GetAltPath(), content)); |
| |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| Wait(); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| EXPECT_EQ(buffer->proto()->events_size(), 1); |
| |
| buffer->UpdatePath(GetAltPath()); |
| Wait(); |
| EXPECT_EQ(buffer->proto()->events_size(), 2); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| EXPECT_EQ(buffer->proto()->events_size(), 3); |
| } |
| |
| TEST_F(ArenaEventBufferTest, PeriodicEventBackup) { |
| base::test::ScopedFeatureList features; |
| features.InitAndEnableFeatureWithParameters(kEventStorageManager, |
| {{ |
| "event_backup_time_s", |
| "3" // seconds |
| }}); |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| Wait(); |
| |
| // Add an event. |
| buffer->AddEvent(TestEvent(1)); |
| EXPECT_EQ(buffer->proto()->events_size(), 1); |
| |
| // Wait for 3 seconds for the timer to trigger a backup. |
| task_environment_.FastForwardBy(base::Seconds(3)); |
| Wait(); |
| |
| // Read the events from disk to see if the file has the expected content. |
| EventsProto events = ReadEvents(GetPath()); |
| ASSERT_EQ(events.events_size(), 1); |
| const auto& event = events.events(0); |
| EXPECT_EQ(event.device_project_id(), 1ul); |
| } |
| |
| TEST_F(ArenaEventBufferTest, Serialize) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(2)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(3)), Result::kOk); |
| |
| RepeatedPtrField<StructuredEventProto> events = buffer->Serialize(); |
| |
| // Expect |events| to not be associated with an arena. |
| EXPECT_EQ(events.GetArena(), nullptr); |
| |
| ASSERT_EQ(events.size(), 3); |
| for (size_t i = 0; i < static_cast<size_t>(events.size()); ++i) { |
| EXPECT_EQ(events[i].device_project_id(), i + 1); |
| } |
| |
| // Serialize is a copy. |
| EXPECT_EQ(buffer->proto()->events_size(), 3); |
| } |
| |
| TEST_F(ArenaEventBufferTest, Flush) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(2)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(3)), Result::kOk); |
| |
| buffer->proto().QueueWrite(); |
| Wait(); |
| |
| const base::FilePath& path = GetPath(); |
| base::File::Info info; |
| CHECK(base::GetFileInfo(path, &info)); |
| |
| const base::FilePath new_path = |
| temp_dir_.GetPath().Append(FILE_PATH_LITERAL("new_proto_file")); |
| |
| bool flushed = false; |
| buffer->Flush(new_path, base::BindLambdaForTesting( |
| [&](base::expected<FlushedKey, FlushError> key) { |
| flushed = true; |
| EXPECT_TRUE(key.has_value()); |
| EXPECT_EQ(key->size, info.size); |
| EXPECT_EQ(key->path, new_path); |
| // A new file is created, the creation time must |
| // be different. |
| EXPECT_GE(key->creation_time, |
| info.creation_time); |
| EXPECT_EQ(buffer->proto()->events_size(), 0); |
| EXPECT_EQ( |
| buffer->resource_info().used_size_bytes, |
| 0ul); |
| })); |
| Wait(); |
| EXPECT_TRUE(flushed); |
| } |
| |
| // ArenaEventBuffer functions the same regardless of the path, so there is only |
| // one test for this. |
| TEST_F(ArenaEventBufferTest, ProfileUpdatePath) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| Wait(); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| |
| AddProfile(); |
| Wait(); |
| |
| EXPECT_NE(GetPath(), buffer->proto().path()); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(2)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(3)), Result::kOk); |
| |
| // Write to disk at the new path. |
| buffer->proto().StartWriteForTesting(); |
| Wait(); |
| |
| // Expect the flushed size to be the same as the serialized size of the |
| // in-memory content. |
| std::optional<int64_t> size = base::GetFileSize(buffer->proto().path()); |
| ASSERT_TRUE(size.has_value()); |
| EXPECT_EQ(static_cast<uint64_t>(size.value()), |
| buffer->proto()->ByteSizeLong()); |
| } |
| |
| TEST_F(ArenaEventBufferTest, PreInitEvents) { |
| std::unique_ptr<ArenaEventBuffer> buffer = CreateTestBuffer(/*max_size=*/512); |
| |
| EXPECT_EQ(buffer->AddEvent(TestEvent(1)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(2)), Result::kOk); |
| EXPECT_EQ(buffer->AddEvent(TestEvent(3)), Result::kOk); |
| |
| Wait(); |
| |
| EXPECT_EQ(buffer->proto()->events_size(), 3); |
| } |
| |
| } // namespace metrics::structured |