blob: b5ab97f6581016b7eda33e031e4fd63fd96dd49a [file] [log] [blame]
// Copyright 2014 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/leveldb_proto/leveldb_database.h"
#include <map>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_checker.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/leveldb_chrome.h"
#include "third_party/leveldatabase/src/include/leveldb/cache.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
#include "third_party/leveldatabase/src/include/leveldb/options.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h"
#include "third_party/leveldatabase/src/include/leveldb/status.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace leveldb_proto {
namespace {
// Covers 8MB block cache,
const int kMaxApproxMemoryUseMB = 16;
} // namespace
bool PrefixStopCallback(const std::string& prefix, const std::string& key) {
return base::StartsWith(key, prefix, base::CompareCase::SENSITIVE);
}
LevelDB::LevelDB(const char* client_name)
: open_histogram_(nullptr), destroy_histogram_(nullptr) {
// Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is
// not a constant.
open_histogram_ = base::LinearHistogram::FactoryGet(
std::string("LevelDB.Open.") + client_name, 1,
leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
base::Histogram::kUmaTargetedHistogramFlag);
destroy_histogram_ = base::LinearHistogram::FactoryGet(
std::string("LevelDB.Destroy.") + client_name, 1,
leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1,
base::Histogram::kUmaTargetedHistogramFlag);
approx_memtable_mem_histogram_ = base::LinearHistogram::FactoryGet(
std::string("LevelDB.ApproximateMemTableMemoryUse.") + client_name, 1,
kMaxApproxMemoryUseMB * 1048576, kMaxApproxMemoryUseMB * 4,
base::Histogram::kUmaTargetedHistogramFlag);
}
LevelDB::~LevelDB() {
DFAKE_SCOPED_LOCK(thread_checker_);
}
bool LevelDB::Init(const base::FilePath& database_dir,
const leveldb_env::Options& options) {
auto status = Init(database_dir, options, true /* destroy_on_corruption */);
return status.ok();
}
leveldb::Status LevelDB::Init(const base::FilePath& database_dir,
const leveldb_env::Options& options,
bool destroy_on_corruption) {
DFAKE_SCOPED_LOCK(thread_checker_);
database_dir_ = database_dir;
open_options_ = options;
bool in_mem = database_dir.empty();
if (in_mem) {
env_ = leveldb_chrome::NewMemEnv("LevelDB");
open_options_.env = env_.get();
}
const std::string path = database_dir.AsUTF8Unsafe();
leveldb::Status status = leveldb_env::OpenDB(open_options_, path, &db_);
if (open_histogram_)
open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status));
if (destroy_on_corruption && status.IsCorruption()) {
auto destroy_status = Destroy();
if (!destroy_status.ok())
return status;
status = leveldb_env::OpenDB(open_options_, path, &db_);
// Intentionally do not log the status of the second open. Doing so destroys
// the meaning of corruptions/open which is an important statistic.
}
if (status.ok()) {
if (!in_mem) {
// Record the approximate memory usage of this DB right after init.
// This should just be the size of the MemTable since we haven't done any
// reads/writes and the block cache should be empty.
uint64_t approx_mem = 0;
std::string usage_string;
if (GetApproximateMemoryUse(&approx_mem)) {
approx_memtable_mem_histogram_->Add(
approx_mem -
leveldb_chrome::GetSharedBrowserBlockCache()->TotalCharge());
}
}
} else {
LOG(WARNING) << "Unable to open " << database_dir.value() << ": "
<< status.ToString();
}
return status;
}
bool LevelDB::Save(const base::StringPairs& entries_to_save,
const std::vector<std::string>& keys_to_remove,
leveldb::Status* status) {
DCHECK(status);
DFAKE_SCOPED_LOCK(thread_checker_);
if (!db_)
return false;
leveldb::WriteBatch updates;
for (const auto& pair : entries_to_save)
updates.Put(leveldb::Slice(pair.first), leveldb::Slice(pair.second));
for (const auto& key : keys_to_remove)
updates.Delete(leveldb::Slice(key));
leveldb::WriteOptions options;
options.sync = true;
*status = db_->Write(options, &updates);
if (status->ok())
return true;
DLOG(WARNING) << "Failed writing leveldb_proto entries: "
<< status->ToString();
return false;
}
bool LevelDB::UpdateWithRemoveFilter(const base::StringPairs& entries_to_save,
const KeyFilter& delete_key_filter,
leveldb::Status* status) {
return UpdateWithRemoveFilter(entries_to_save, delete_key_filter,
std::string(), status);
}
bool LevelDB::UpdateWithRemoveFilter(const base::StringPairs& entries_to_save,
const KeyFilter& delete_key_filter,
const std::string& target_prefix,
leveldb::Status* status) {
DCHECK(status);
DFAKE_SCOPED_LOCK(thread_checker_);
if (!db_)
return false;
leveldb::WriteBatch updates;
for (const auto& pair : entries_to_save)
updates.Put(leveldb::Slice(pair.first), leveldb::Slice(pair.second));
leveldb::Slice target(target_prefix);
if (!delete_key_filter.is_null()) {
leveldb::ReadOptions read_options;
std::unique_ptr<leveldb::Iterator> db_iterator(
db_->NewIterator(read_options));
for (db_iterator->Seek(target);
db_iterator->Valid() && db_iterator->key().starts_with(target);
db_iterator->Next()) {
leveldb::Slice key_slice = db_iterator->key();
std::string key(key_slice.data(), key_slice.size());
if (delete_key_filter.Run(key))
updates.Delete(leveldb::Slice(key));
}
}
leveldb::WriteOptions write_options;
write_options.sync = true;
*status = db_->Write(write_options, &updates);
if (status->ok())
return true;
DLOG(WARNING) << "Failed deleting leveldb_proto entries: "
<< status->ToString();
return false;
}
bool LevelDB::Load(std::vector<std::string>* entries) {
return LoadWithFilter(KeyFilter(), entries);
}
bool LevelDB::LoadWithFilter(const KeyFilter& filter,
std::vector<std::string>* entries) {
return LoadWithFilter(filter, entries, leveldb::ReadOptions(), std::string());
}
bool LevelDB::LoadWithFilter(const KeyFilter& filter,
std::vector<std::string>* entries,
const leveldb::ReadOptions& options,
const std::string& target_prefix) {
std::map<std::string, std::string> keys_entries;
bool result = LoadKeysAndEntriesWithFilter(filter, &keys_entries, options,
target_prefix);
if (!result)
return false;
for (const auto& pair : keys_entries)
entries->push_back(pair.second);
return true;
}
bool LevelDB::LoadKeysAndEntries(
std::map<std::string, std::string>* keys_entries) {
return LoadKeysAndEntriesWithFilter(KeyFilter(), keys_entries);
}
bool LevelDB::LoadKeysAndEntriesWithFilter(
const KeyFilter& filter,
std::map<std::string, std::string>* keys_entries) {
return LoadKeysAndEntriesWithFilter(filter, keys_entries,
leveldb::ReadOptions(), std::string());
}
bool LevelDB::LoadKeysAndEntriesWithFilter(
const KeyFilter& filter,
std::map<std::string, std::string>* keys_entries,
const leveldb::ReadOptions& options,
const std::string& target_prefix) {
return LoadKeysAndEntriesWhile(
filter, keys_entries, options, target_prefix,
base::BindRepeating(&PrefixStopCallback, target_prefix));
}
bool LevelDB::LoadKeysAndEntriesWhile(
const KeyFilter& filter,
std::map<std::string, std::string>* keys_entries,
const leveldb::ReadOptions& options,
const std::string& start_key,
const KeyFilter& while_callback) {
DFAKE_SCOPED_LOCK(thread_checker_);
if (!db_)
return false;
std::unique_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options));
leveldb::Slice start(start_key);
for (db_iterator->Seek(start);
db_iterator->Valid() &&
while_callback.Run(db_iterator->key().ToString());
db_iterator->Next()) {
leveldb::Slice key_slice = db_iterator->key();
std::string key_slice_str(key_slice.data(), key_slice.size());
if (!filter.is_null() && !filter.Run(key_slice_str)) {
continue;
}
leveldb::Slice value_slice = db_iterator->value();
keys_entries->insert(std::make_pair(
key_slice_str, std::string(value_slice.data(), value_slice.size())));
}
return true;
}
bool LevelDB::LoadKeys(std::vector<std::string>* keys) {
return LoadKeys(std::string(), keys);
}
bool LevelDB::LoadKeys(const std::string& target_prefix,
std::vector<std::string>* keys) {
leveldb::ReadOptions options;
options.fill_cache = false;
std::map<std::string, std::string> keys_entries;
bool result = LoadKeysAndEntriesWithFilter(KeyFilter(), &keys_entries,
options, target_prefix);
if (!result)
return false;
for (const auto& pair : keys_entries)
keys->push_back(pair.first);
return true;
}
bool LevelDB::Get(const std::string& key,
bool* found,
std::string* entry,
leveldb::Status* status) {
DCHECK(status);
DFAKE_SCOPED_LOCK(thread_checker_);
if (!db_)
return false;
leveldb::ReadOptions options;
*status = db_->Get(options, key, entry);
if (status->ok()) {
*found = true;
return true;
}
if (status->IsNotFound()) {
*found = false;
return true;
}
DLOG(WARNING) << "Failed loading leveldb_proto entry with key \"" << key
<< "\": " << status->ToString();
return false;
}
leveldb::Status LevelDB::Destroy() {
db_.reset();
const std::string path = database_dir_.AsUTF8Unsafe();
leveldb::Status status = leveldb::DestroyDB(path, open_options_);
if (!status.ok())
LOG(WARNING) << "Unable to destroy " << path << ": " << status.ToString();
if (destroy_histogram_)
destroy_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status));
return status;
}
bool LevelDB::GetApproximateMemoryUse(uint64_t* approx_mem) {
std::string usage_string;
return (db_->GetProperty("leveldb.approximate-memory-usage", &usage_string) &&
base::StringToUint64(usage_string, approx_mem));
}
} // namespace leveldb_proto