-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce event storage for structured metrics
Bug: b/307748082 Change-Id: I49202d18783bbd14b9a434704b8864efca881a1a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4973593 Commit-Queue: Andrew Bregger <andrewbregger@google.com> Reviewed-by: Jong Ahn <jongahn@chromium.org> Cr-Commit-Position: refs/heads/main@{#1216467}
- Loading branch information
Andrew Bregger
authored and
Chromium LUCI CQ
committed
Oct 28, 2023
1 parent
3badd86
commit c5e48db
Showing
8 changed files
with
305 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// 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 "chrome/browser/metrics/structured/ash_event_storage.h" | ||
|
||
#include "base/functional/callback_forward.h" | ||
#include "base/task/current_thread.h" | ||
#include "components/metrics/structured/histogram_util.h" | ||
|
||
namespace metrics::structured { | ||
AshEventStorage::AshEventStorage(base::TimeDelta write_delay) | ||
: write_delay_(write_delay) {} | ||
|
||
AshEventStorage::~AshEventStorage() = default; | ||
|
||
// EventStorage: | ||
bool AshEventStorage::IsReady() { | ||
return events_.get() != nullptr && is_initialized_; | ||
} | ||
|
||
void AshEventStorage::OnReady() { | ||
is_initialized_ = true; | ||
|
||
for (auto& event : pre_storage_events_) { | ||
AddEvent(std::move(event)); | ||
} | ||
|
||
pre_storage_events_.reserve(0); | ||
} | ||
|
||
void AshEventStorage::AddEvent(StructuredEventProto&& event) { | ||
if (IsReady()) { | ||
*events()->add_non_uma_events() = event; | ||
} else { | ||
pre_storage_events_.emplace_back(event); | ||
} | ||
} | ||
|
||
void AshEventStorage::MoveEvents(ChromeUserMetricsExtension& uma_proto) { | ||
StructuredDataProto* proto = uma_proto.mutable_structured_data(); | ||
proto->mutable_events()->Swap(events()->mutable_non_uma_events()); | ||
|
||
events()->clear_uma_events(); | ||
events()->clear_non_uma_events(); | ||
} | ||
|
||
void AshEventStorage::Purge() { | ||
if (IsReady()) { | ||
events_->Purge(); | ||
} | ||
// Make sure it is null. | ||
events_.reset(); | ||
} | ||
|
||
void AshEventStorage::OnProfileAdded(const base::FilePath& path) { | ||
DCHECK(base::CurrentUIThread::IsSet()); | ||
|
||
if (is_initialized_) { | ||
return; | ||
} | ||
|
||
// The directory used to store unsent logs. Relative to the user's cryptohome. | ||
// This file is created by chromium. | ||
events_ = std::make_unique<PersistentProto<EventsProto>>( | ||
path.Append(FILE_PATH_LITERAL("structured_metrics")) | ||
.Append(FILE_PATH_LITERAL("events")), | ||
write_delay_, | ||
base::BindOnce(&AshEventStorage::OnRead, weak_factory_.GetWeakPtr()), | ||
base::BindRepeating(&AshEventStorage::OnWrite, | ||
weak_factory_.GetWeakPtr())); | ||
} | ||
|
||
void AshEventStorage::AddBatchEvents( | ||
const google::protobuf::RepeatedPtrField<StructuredEventProto>& events) { | ||
AshEventStorage::events()->mutable_non_uma_events()->MergeFrom(events); | ||
} | ||
|
||
void AshEventStorage::OnWrite(const WriteStatus status) { | ||
DCHECK(base::CurrentUIThread::IsSet()); | ||
|
||
switch (status) { | ||
case WriteStatus::kOk: | ||
break; | ||
case WriteStatus::kWriteError: | ||
LogInternalError(StructuredMetricsError::kEventWriteError); | ||
break; | ||
case WriteStatus::kSerializationError: | ||
LogInternalError(StructuredMetricsError::kEventSerializationError); | ||
break; | ||
} | ||
} | ||
|
||
void AshEventStorage::OnRead(const ReadStatus status) { | ||
DCHECK(base::CurrentUIThread::IsSet()); | ||
|
||
switch (status) { | ||
case ReadStatus::kOk: | ||
case ReadStatus::kMissing: | ||
break; | ||
case ReadStatus::kReadError: | ||
LogInternalError(StructuredMetricsError::kEventReadError); | ||
break; | ||
case ReadStatus::kParseError: | ||
LogInternalError(StructuredMetricsError::kEventParseError); | ||
break; | ||
} | ||
|
||
OnReady(); | ||
} | ||
|
||
} // namespace metrics::structured |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// 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. | ||
|
||
#ifndef CHROME_BROWSER_METRICS_STRUCTURED_ASH_EVENT_STORAGE_H_ | ||
#define CHROME_BROWSER_METRICS_STRUCTURED_ASH_EVENT_STORAGE_H_ | ||
|
||
#include <memory> | ||
|
||
#include "base/memory/weak_ptr.h" | ||
#include "base/time/time.h" | ||
#include "components/metrics/structured/event_storage.h" | ||
#include "components/metrics/structured/persistent_proto.h" | ||
#include "components/metrics/structured/storage.pb.h" | ||
|
||
namespace metrics::structured { | ||
|
||
// Storage for Structured Metrics events on Ash. | ||
// | ||
// Events are stored in a proto called EventsProto. It is persisted to disk in | ||
// the user's cryptohome. This proto is not ready until a user has logged in. | ||
// Events are stored in an in-memory vector until then. | ||
class AshEventStorage : public EventStorage { | ||
public: | ||
explicit AshEventStorage(base::TimeDelta write_delay); | ||
|
||
~AshEventStorage() override; | ||
|
||
// EventStorage: | ||
bool IsReady() override; | ||
void OnReady() override; | ||
void AddEvent(StructuredEventProto&& event) override; | ||
void MoveEvents(ChromeUserMetricsExtension& uma_proto) override; | ||
void Purge() override; | ||
void OnProfileAdded(const base::FilePath& path) override; | ||
void AddBatchEvents( | ||
const google::protobuf::RepeatedPtrField<StructuredEventProto>& events) | ||
override; | ||
|
||
EventsProto* events() { return events_->get(); } | ||
|
||
private: | ||
void OnWrite(const WriteStatus status); | ||
void OnRead(const ReadStatus status); | ||
|
||
bool is_initialized_ = false; | ||
|
||
// Delay period for PersistentProto writes. Default value of 1000 ms used if | ||
// not specified in ctor. | ||
base::TimeDelta write_delay_; | ||
|
||
// On-device storage within the user's cryptohome for unsent logs. | ||
std::unique_ptr<PersistentProto<EventsProto>> events_; | ||
|
||
// Storage for events to be stored if they are recorded before the storage is | ||
// ready. Should never be used once `OnReady` is called. | ||
std::vector<StructuredEventProto> pre_storage_events_; | ||
|
||
base::WeakPtrFactory<AshEventStorage> weak_factory_{this}; | ||
}; | ||
|
||
} // namespace metrics::structured | ||
|
||
#endif // CHROME_BROWSER_METRICS_STRUCTURED_ASH_EVENT_STORAGE_H_ |
69 changes: 69 additions & 0 deletions
69
chrome/browser/metrics/structured/ash_event_storage_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// 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 "chrome/browser/metrics/structured/ash_event_storage.h" | ||
#include "base/files/file_path.h" | ||
#include "base/files/scoped_temp_dir.h" | ||
#include "base/test/task_environment.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" | ||
#include "third_party/metrics_proto/structured_data.pb.h" | ||
|
||
namespace metrics::structured { | ||
namespace { | ||
StructuredEventProto BuildTestEvent() { | ||
return StructuredEventProto(); | ||
} | ||
} // namespace | ||
|
||
class AshEventStorageTest : public testing::Test { | ||
public: | ||
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } | ||
|
||
void Wait() { task_environment_.RunUntilIdle(); } | ||
|
||
base::FilePath GetUserDirectory() { return temp_dir_.GetPath(); } | ||
|
||
private: | ||
base::test::TaskEnvironment task_environment_{ | ||
base::test::TaskEnvironment::MainThreadType::UI, | ||
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED, | ||
base::test::TaskEnvironment::TimeSource::MOCK_TIME}; | ||
|
||
base::ScopedTempDir temp_dir_; | ||
}; | ||
|
||
TEST_F(AshEventStorageTest, StoreAndProvideEvents) { | ||
AshEventStorage storage(/*write_delay=*/base::Seconds(0)); | ||
storage.OnProfileAdded(GetUserDirectory()); | ||
|
||
Wait(); | ||
|
||
ASSERT_TRUE(storage.IsReady()); | ||
|
||
storage.AddEvent(BuildTestEvent()); | ||
EXPECT_EQ(storage.events()->non_uma_events_size(), 1); | ||
|
||
ChromeUserMetricsExtension uma_proto; | ||
storage.MoveEvents(uma_proto); | ||
EXPECT_EQ(uma_proto.structured_data().events_size(), 1); | ||
EXPECT_EQ(storage.events()->non_uma_events_size(), 0); | ||
} | ||
|
||
TEST_F(AshEventStorageTest, PreRecordedEventsProcessedCorrectly) { | ||
AshEventStorage storage(/*write_delay=*/base::Seconds(0)); | ||
|
||
storage.AddEvent(BuildTestEvent()); | ||
|
||
// Add Profile and wait for the storage to be ready. | ||
storage.OnProfileAdded(GetUserDirectory()); | ||
Wait(); | ||
|
||
ASSERT_TRUE(storage.IsReady()); | ||
|
||
// The proto should not be stored in the proto. | ||
EXPECT_EQ(storage.events()->non_uma_events_size(), 1); | ||
} | ||
|
||
} // namespace metrics::structured |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// 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. | ||
|
||
#ifndef COMPONENTS_METRICS_STRUCTURED_EVENT_STORAGE_H_ | ||
#define COMPONENTS_METRICS_STRUCTURED_EVENT_STORAGE_H_ | ||
|
||
#include "base/files/file_path.h" | ||
#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" | ||
#include "third_party/metrics_proto/structured_data.pb.h" | ||
|
||
namespace metrics { | ||
class StructuredEventProto; | ||
class ChromeUserMetricsExtension; | ||
} // namespace metrics | ||
|
||
namespace metrics::structured { | ||
|
||
// Abstraction for how events are stored in Structured Metrics. | ||
class EventStorage { | ||
public: | ||
EventStorage() = default; | ||
|
||
virtual ~EventStorage() = default; | ||
|
||
EventStorage(const EventStorage&) = delete; | ||
EventStorage& operator=(const EventStorage&) = delete; | ||
|
||
virtual bool IsReady() = 0; | ||
|
||
// A callback to be run when the storage is ready. | ||
virtual void OnReady() = 0; | ||
|
||
// Add a new StructuredEventProto to be stored. | ||
virtual void AddEvent(StructuredEventProto&& event) = 0; | ||
|
||
// Events are moved to UMA proto to be uploaded. | ||
virtual void MoveEvents(ChromeUserMetricsExtension& uma_proto) = 0; | ||
|
||
// Delete all events. | ||
virtual void Purge() = 0; | ||
|
||
// Temporary API for notifying storage that a profile has been added. | ||
virtual void OnProfileAdded(const base::FilePath& path) {} | ||
|
||
// Temporary API for external metrics. | ||
virtual void AddBatchEvents( | ||
const google::protobuf::RepeatedPtrField<StructuredEventProto>& events) {} | ||
}; | ||
|
||
} // namespace metrics::structured | ||
|
||
#endif // COMPONENTS_METRICS_STRUCTURED_EVENT_STORAGE_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters