| // 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 "chrome/browser/profiles/profile.h" |
| #include "components/metrics/structured/lib/histogram_util.h" |
| #include "third_party/metrics_proto/structured_data.pb.h" |
| |
| namespace metrics::structured { |
| |
| using ::google::protobuf::RepeatedPtrField; |
| |
| AshEventStorage::AshEventStorage(base::TimeDelta write_delay, |
| const base::FilePath& pre_user_event_path) |
| : write_delay_(write_delay) { |
| // Store to persist events before a user has logged-in. |
| pre_user_events_ = std::make_unique<PersistentProto<EventsProto>>( |
| pre_user_event_path, write_delay_, |
| base::BindOnce(&AshEventStorage::OnRead, weak_factory_.GetWeakPtr()), |
| base::BindRepeating(&AshEventStorage::OnWrite, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| AshEventStorage::~AshEventStorage() = default; |
| |
| void AshEventStorage::OnReady() { |
| CHECK(pre_user_events_.get()); |
| is_initialized_ = true; |
| |
| for (auto& event : pre_storage_events_) { |
| AddEvent(std::move(event)); |
| } |
| } |
| |
| void AshEventStorage::AddEvent(StructuredEventProto event) { |
| PersistentProto<EventsProto>* event_store_to_write = GetStoreToWriteEvent(); |
| |
| if (!event_store_to_write) { |
| pre_storage_events_.emplace_back(std::move(event)); |
| return; |
| } |
| |
| event_store_to_write->get()->mutable_events()->Add(std::move(event)); |
| event_store_to_write->QueueWrite(); |
| } |
| |
| RepeatedPtrField<StructuredEventProto> AshEventStorage::TakeEvents() { |
| if (IsPreUserStorageReadable()) { |
| RepeatedPtrField<StructuredEventProto> events = |
| std::move(*pre_user_events()->mutable_events()); |
| pre_user_events_->Purge(); |
| return events; |
| } |
| |
| // Profile must be ready if |pre_user_events| has been cleanedup. |
| CHECK(IsProfileReady()); |
| |
| RepeatedPtrField<StructuredEventProto> events = |
| std::move(*user_events()->mutable_events()); |
| user_events_->Purge(); |
| return events; |
| } |
| |
| int AshEventStorage::RecordedEventsCount() const { |
| int total_event_count = 0; |
| if (IsPreUserStorageReadable()) { |
| total_event_count += pre_user_events_->get()->events_size(); |
| } |
| if (is_user_initialized_) { |
| total_event_count += user_events_->get()->events_size(); |
| } |
| return total_event_count; |
| } |
| |
| void AshEventStorage::Purge() { |
| if (IsProfileReady()) { |
| user_events_->Purge(); |
| } |
| if (IsPreUserStorageReadable()) { |
| pre_user_events_->Purge(); |
| } |
| if (!pre_storage_events_.empty()) { |
| pre_storage_events_.clear(); |
| } |
| } |
| |
| void AshEventStorage::AddBatchEvents( |
| const google::protobuf::RepeatedPtrField<StructuredEventProto>& events) { |
| PersistentProto<EventsProto>* event_store = GetStoreToWriteEvent(); |
| if (event_store) { |
| event_store->get()->mutable_events()->MergeFrom(events); |
| event_store->QueueWrite(); |
| } else if (!is_initialized_) { |
| pre_storage_events_.insert(pre_storage_events_.end(), events.begin(), |
| events.end()); |
| } |
| } |
| |
| void AshEventStorage::ProfileAdded(const Profile& profile) { |
| DCHECK(base::CurrentUIThread::IsSet()); |
| |
| if (is_user_initialized_) { |
| return; |
| } |
| |
| const base::FilePath& path = profile.GetPath(); |
| |
| // The directory used to store unsent logs. Relative to the user's cryptohome. |
| // This file is created by chromium. |
| user_events_ = std::make_unique<PersistentProto<EventsProto>>( |
| path.Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("events")), |
| write_delay_, |
| base::BindOnce(&AshEventStorage::OnProfileRead, |
| weak_factory_.GetWeakPtr()), |
| base::BindRepeating(&AshEventStorage::OnWrite, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void AshEventStorage::CopyEvents(EventsProto* events_proto) const { |
| if (IsPreUserStorageReadable() && pre_user_events()->events_size() > 0) { |
| events_proto->mutable_events()->MergeFrom(pre_user_events()->events()); |
| } |
| if (IsProfileReady() && user_events()->events_size() > 0) { |
| events_proto->mutable_events()->MergeFrom(user_events()->events()); |
| } |
| } |
| |
| void AshEventStorage::OnWrite(const WriteStatus status) { |
| 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) { |
| switch (status) { |
| case ReadStatus::kOk: |
| case ReadStatus::kMissing: |
| break; |
| case ReadStatus::kReadError: |
| LogInternalError(StructuredMetricsError::kEventReadError); |
| break; |
| case ReadStatus::kParseError: |
| LogInternalError(StructuredMetricsError::kEventParseError); |
| break; |
| } |
| |
| OnReady(); |
| } |
| |
| void AshEventStorage::OnProfileRead(const ReadStatus status) { |
| switch (status) { |
| case ReadStatus::kOk: |
| case ReadStatus::kMissing: |
| break; |
| case ReadStatus::kReadError: |
| LogInternalError(StructuredMetricsError::kEventReadError); |
| break; |
| case ReadStatus::kParseError: |
| LogInternalError(StructuredMetricsError::kEventParseError); |
| break; |
| } |
| |
| OnProfileReady(); |
| } |
| |
| void AshEventStorage::OnProfileReady() { |
| CHECK(user_events_.get()); |
| is_user_initialized_ = true; |
| |
| // Move any events that are current in |pre_user_events_| into the |
| // |user_events_|. |
| if (pre_user_events() && pre_user_events()->events_size() > 0) { |
| RepeatedPtrField<StructuredEventProto>* users_events = |
| user_events()->mutable_events(); |
| RepeatedPtrField<StructuredEventProto>* pre_users_events = |
| pre_user_events()->mutable_events(); |
| |
| // Moving events from |pre_users_events| to |users_events|. |
| users_events->Reserve(users_events->size() + pre_users_events->size()); |
| |
| // Temporary buffer to extract the |pre_users_events| into. |
| std::vector<StructuredEventProto*> extracted(pre_users_events->size(), |
| nullptr); |
| |
| // Extract and add the elements into |users_events| |
| pre_users_events->ExtractSubrange(0, pre_users_events->size(), |
| extracted.data()); |
| |
| for (auto* element : extracted) { |
| users_events->AddAllocated(element); |
| } |
| } |
| |
| // Regardless of if there are any events cleanup the storage. |
| if (pre_user_events()) { |
| (*pre_user_events_)->Clear(); |
| pre_user_events_->QueueWrite(); |
| } |
| |
| // The write is fine because it will add to a task that is not tied to the |
| // lifetime of |pre_user_events_|. |
| pre_user_events_.reset(); |
| |
| // Dealloc any memory that the vector is occupying as it will not be used |
| // anymore. |
| std::vector<StructuredEventProto>().swap(pre_storage_events_); |
| } |
| |
| bool AshEventStorage::IsProfileReady() const { |
| return is_user_initialized_ && user_events_.get(); |
| } |
| |
| bool AshEventStorage::IsPreUserStorageReadable() const { |
| return pre_user_events_ && is_initialized_; |
| } |
| |
| PersistentProto<EventsProto>* AshEventStorage::GetStoreToWriteEvent() { |
| // If user storage is ready, all events should be stored in user event store |
| // regardless of the type. |
| if (IsProfileReady()) { |
| return user_events_.get(); |
| } |
| // Use the shared storage if user storage is not ready. |
| if (IsPreUserStorageReadable()) { |
| return pre_user_events_.get(); |
| } |
| return nullptr; |
| } |
| |
| } // namespace metrics::structured |