blob: f061a3d6100eaffc5b96122fc9195fff851fc62c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feed/core/feed_journal_database.h"
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/feed/core/feed_journal_mutation.h"
#include "components/feed/core/feed_journal_operation.h"
#include "components/feed/core/proto/journal_storage.pb.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
namespace feed {
namespace {
using StorageEntryVector =
leveldb_proto::ProtoDatabase<JournalStorageProto>::KeyEntryVector;
// Statistics are logged to UMA with this string as part of histogram name. They
// can all be found under LevelDB.*.FeedJournalDatabase. Changing this needs to
// synchronize with histograms.xml, AND will also become incompatible with older
// browsers still reporting the previous values.
const char kJournalDatabaseUMAClientName[] = "FeedJournalDatabase";
const char kJournalDatabaseFolder[] = "journal";
const size_t kDatabaseWriteBufferSizeBytes = 64 * 1024; // 64KB
const size_t kDatabaseWriteBufferSizeBytesForLowEndDevice = 32 * 1024; // 32KB
void ReportLoadEntriesHistograms(bool success, base::TimeTicks start_time) {
UMA_HISTOGRAM_BOOLEAN("ContentSuggestions.Feed.JournalStorage.LoadSuccess",
success);
base::TimeDelta load_time = base::TimeTicks::Now() - start_time;
UMA_HISTOGRAM_TIMES("ContentSuggestions.Feed.JournalStorage.LoadTime",
load_time);
}
} // namespace
FeedJournalDatabase::FeedJournalDatabase(const base::FilePath& database_folder)
: FeedJournalDatabase(
database_folder,
leveldb_proto::ProtoDatabaseProvider::CreateUniqueDB<
JournalStorageProto>(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}))) {}
FeedJournalDatabase::FeedJournalDatabase(
const base::FilePath& database_folder,
std::unique_ptr<leveldb_proto::ProtoDatabase<JournalStorageProto>>
storage_database)
: database_status_(UNINITIALIZED),
storage_database_(std::move(storage_database)),
weak_ptr_factory_(this) {
leveldb_env::Options options = leveldb_proto::CreateSimpleOptions();
if (base::SysInfo::IsLowEndDevice()) {
options.write_buffer_size = kDatabaseWriteBufferSizeBytesForLowEndDevice;
} else {
options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
}
base::FilePath storage_folder =
database_folder.AppendASCII(kJournalDatabaseFolder);
storage_database_->Init(
kJournalDatabaseUMAClientName, storage_folder, options,
base::BindOnce(&FeedJournalDatabase::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
FeedJournalDatabase::~FeedJournalDatabase() = default;
bool FeedJournalDatabase::IsInitialized() const {
return INITIALIZED == database_status_;
}
void FeedJournalDatabase::LoadJournal(const std::string& key,
JournalLoadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
storage_database_->GetEntry(
key, base::BindOnce(&FeedJournalDatabase::OnGetEntryForLoadJournal,
weak_ptr_factory_.GetWeakPtr(),
base::TimeTicks::Now(), std::move(callback)));
}
void FeedJournalDatabase::DoesJournalExist(const std::string& key,
CheckExistingCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
storage_database_->GetEntry(
key, base::BindOnce(&FeedJournalDatabase::OnGetEntryForDoesJournalExist,
weak_ptr_factory_.GetWeakPtr(),
base::TimeTicks::Now(), std::move(callback)));
}
void FeedJournalDatabase::CommitJournalMutation(
std::unique_ptr<JournalMutation> journal_mutation,
ConfirmationCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(journal_mutation);
UMA_HISTOGRAM_COUNTS_100(
"ContentSuggestions.Feed.JournalStorage.CommitMutationCount",
journal_mutation->Size());
if (journal_mutation->Empty()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
return;
}
// Skip loading journal if the first operation is JOURNAL_DELETE.
if (journal_mutation->FirstOperationType() ==
JournalOperation::JOURNAL_DELETE) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&FeedJournalDatabase::PerformOperations,
weak_ptr_factory_.GetWeakPtr(), nullptr,
std::move(journal_mutation), std::move(callback)));
return;
}
std::string journal_name = journal_mutation->journal_name();
storage_database_->GetEntry(
journal_name,
base::BindOnce(&FeedJournalDatabase::OnGetEntryForCommitJournalMutation,
weak_ptr_factory_.GetWeakPtr(),
std::move(journal_mutation), std::move(callback)));
}
void FeedJournalDatabase::LoadAllJournalKeys(JournalLoadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
storage_database_->LoadKeys(
base::BindOnce(&FeedJournalDatabase::OnLoadKeysForLoadAllJournalKeys,
weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
std::move(callback)));
}
void FeedJournalDatabase::DeleteAllJournals(ConfirmationCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// For deleting all, filter method always return true.
storage_database_->UpdateEntriesWithRemoveFilter(
std::make_unique<StorageEntryVector>(),
base::BindRepeating([](const std::string& x) { return true; }),
base::BindOnce(&FeedJournalDatabase::OnOperationCommitted,
weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
std::move(callback)));
}
void FeedJournalDatabase::PerformOperations(
std::unique_ptr<JournalStorageProto> journal,
std::unique_ptr<JournalMutation> journal_mutation,
ConfirmationCallback callback) {
DCHECK(!journal_mutation->Empty());
if (journal) {
DCHECK_EQ(journal->key(), journal_mutation->journal_name());
} else {
journal = std::make_unique<JournalStorageProto>();
journal->set_key(journal_mutation->journal_name());
}
JournalMap copy_to_journal;
while (!journal_mutation->Empty()) {
JournalOperation operation = journal_mutation->TakeFristOperation();
switch (operation.type()) {
case JournalOperation::JOURNAL_APPEND:
journal->add_journal_data(operation.value());
break;
case JournalOperation::JOURNAL_COPY:
copy_to_journal[operation.to_journal_name()] =
CopyJouarnal(operation.to_journal_name(), *journal);
break;
case JournalOperation::JOURNAL_DELETE:
journal->clear_journal_data();
break;
}
}
CommitOperations(journal_mutation->GetStartTime(), std::move(journal),
std::move(copy_to_journal), std::move(callback));
}
void FeedJournalDatabase::CommitOperations(
base::TimeTicks start_time,
std::unique_ptr<JournalStorageProto> journal,
JournalMap copy_to_journal,
ConfirmationCallback callback) {
auto journals_to_save = std::make_unique<StorageEntryVector>();
auto journals_to_delete = std::make_unique<std::vector<std::string>>();
if (journal->journal_data_size() == 0) {
// This can only happens when the journal is deleted.
journals_to_delete->push_back(journal->key());
} else {
std::string journal_name = journal->key();
journals_to_save->emplace_back(journal_name, std::move(*journal));
}
for (auto it = copy_to_journal.begin(); it != copy_to_journal.end(); ++it) {
journals_to_save->emplace_back(it->first, std::move(it->second));
}
storage_database_->UpdateEntries(
std::move(journals_to_save), std::move(journals_to_delete),
base::BindOnce(&FeedJournalDatabase::OnOperationCommitted,
weak_ptr_factory_.GetWeakPtr(), std::move(start_time),
std::move(callback)));
}
void FeedJournalDatabase::OnDatabaseInitialized(bool success) {
DCHECK_EQ(database_status_, UNINITIALIZED);
if (success) {
database_status_ = INITIALIZED;
} else {
database_status_ = INIT_FAILURE;
DVLOG(1) << "FeedJournalDatabase init failed.";
}
UMA_HISTOGRAM_BOOLEAN("ContentSuggestions.Feed.JournalStorage.InitialSuccess",
success);
}
void FeedJournalDatabase::OnGetEntryForLoadJournal(
base::TimeTicks start_time,
JournalLoadCallback callback,
bool success,
std::unique_ptr<JournalStorageProto> journal) {
DVLOG_IF(1, !success) << "FeedJournalDatabase load journal failed.";
std::vector<std::string> results;
if (journal) {
for (int i = 0; i < journal->journal_data_size(); ++i) {
results.emplace_back(journal->journal_data(i));
}
}
ReportLoadEntriesHistograms(success, start_time);
std::move(callback).Run(success, std::move(results));
}
void FeedJournalDatabase::OnGetEntryForDoesJournalExist(
base::TimeTicks start_time,
CheckExistingCallback callback,
bool success,
std::unique_ptr<JournalStorageProto> journal) {
DVLOG_IF(1, !success) << "FeedJournalDatabase load journal failed.";
ReportLoadEntriesHistograms(success, start_time);
std::move(callback).Run(success, journal ? true : false);
}
void FeedJournalDatabase::OnLoadKeysForLoadAllJournalKeys(
base::TimeTicks start_time,
JournalLoadCallback callback,
bool success,
std::unique_ptr<std::vector<std::string>> keys) {
DVLOG_IF(1, !success) << "FeedJournalDatabase load journal keys failed.";
UMA_HISTOGRAM_BOOLEAN(
"ContentSuggestions.Feed.JournalStorage.LoadKeysSuccess", success);
std::vector<std::string> results;
if (keys) {
results = std::move(*keys);
}
if (success) {
// Journal count is about how many Feed surfaces opens/shows to a user.
UMA_HISTOGRAM_EXACT_LINEAR("ContentSuggestions.Feed.JournalStorage.Count",
results.size(), 50);
}
base::TimeDelta load_time = base::TimeTicks::Now() - start_time;
UMA_HISTOGRAM_TIMES("ContentSuggestions.Feed.JournalStorage.LoadKeysTime",
load_time);
std::move(callback).Run(success, std::move(results));
}
void FeedJournalDatabase::OnGetEntryForCommitJournalMutation(
std::unique_ptr<JournalMutation> journal_mutation,
ConfirmationCallback callback,
bool success,
std::unique_ptr<JournalStorageProto> journal) {
if (!success) {
DVLOG(1) << "FeedJournalDatabase load journal failed.";
std::move(callback).Run(success);
return;
}
PerformOperations(std::move(journal), std::move(journal_mutation),
std::move(callback));
}
void FeedJournalDatabase::OnOperationCommitted(base::TimeTicks start_time,
ConfirmationCallback callback,
bool success) {
DVLOG_IF(1, !success) << "FeedJournalDatabase commit failed.";
UMA_HISTOGRAM_BOOLEAN(
"ContentSuggestions.Feed.JournalStorage.OperationCommitSuccess", success);
base::TimeDelta commit_time = base::TimeTicks::Now() - start_time;
UMA_HISTOGRAM_TIMES(
"ContentSuggestions.Feed.JournalStorage.OperationCommitTime",
commit_time);
std::move(callback).Run(success);
}
JournalStorageProto FeedJournalDatabase::CopyJouarnal(
const std::string& new_journal_name,
const JournalStorageProto& source_journal) {
JournalStorageProto new_journal;
new_journal.set_key(new_journal_name);
for (int i = 0; i < source_journal.journal_data_size(); ++i) {
new_journal.add_journal_data(source_journal.journal_data(i));
}
return new_journal;
}
} // namespace feed