Skip to content

Commit

Permalink
Introduce event storage for structured metrics
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 5 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4968,6 +4968,8 @@ static_library("browser") {
"metrics/perf/windowed_incognito_observer.h",
"metrics/profile_pref_names.cc",
"metrics/profile_pref_names.h",
"metrics/structured/ash_event_storage.cc",
"metrics/structured/ash_event_storage.h",
"metrics/structured/ash_structured_metrics_recorder.cc",
"metrics/structured/ash_structured_metrics_recorder.h",
"metrics/structured/cros_events_processor.cc",
Expand Down
112 changes: 112 additions & 0 deletions chrome/browser/metrics/structured/ash_event_storage.cc
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
64 changes: 64 additions & 0 deletions chrome/browser/metrics/structured/ash_event_storage.h
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 chrome/browser/metrics/structured/ash_event_storage_unittest.cc
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
1 change: 1 addition & 0 deletions chrome/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -8205,6 +8205,7 @@ test("unit_tests") {
"../browser/metrics/perf/process_type_collector_unittest.cc",
"../browser/metrics/perf/profile_provider_chromeos_unittest.cc",
"../browser/metrics/perf/windowed_incognito_observer_unittest.cc",
"../browser/metrics/structured/ash_event_storage_unittest.cc",
"../browser/metrics/structured/cros_events_processor_unittest.cc",
"../browser/nearby_sharing/bluetooth_advertising_interval_client_unittest.cc",
"../browser/nearby_sharing/fast_initiation/fast_initiation_advertiser_unittest.cc",
Expand Down
3 changes: 2 additions & 1 deletion components/metrics/structured/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import("//third_party/protobuf/proto_library.gni")
# events with several attached metrics.
static_library("structured") {
sources = [
"event_storage.h",
"key_data.cc",
"key_data.h",
"key_data_provider.h",
Expand Down Expand Up @@ -41,11 +42,11 @@ static_library("structured") {
public_deps = [
":common",
":events",
":storage",
"//third_party/metrics_proto",
]

deps = [
":storage",
":structured_events",
":structured_metrics_validator",
"//base",
Expand Down
53 changes: 53 additions & 0 deletions components/metrics/structured/event_storage.h
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_
6 changes: 2 additions & 4 deletions components/metrics/structured/persistent_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"

namespace metrics {
namespace structured {
namespace metrics::structured {
// The result of reading a backing file from disk.
enum class ReadStatus {
kOk = 0,
Expand Down Expand Up @@ -121,7 +120,6 @@ class PersistentProto {
base::WeakPtrFactory<PersistentProto> weak_factory_{this};
};

} // namespace structured
} // namespace metrics
} // namespace metrics::structured

#endif // COMPONENTS_METRICS_STRUCTURED_PERSISTENT_PROTO_H_

0 comments on commit c5e48db

Please sign in to comment.