blob: 0c5337b5eb5fa4fe0673316a9f0d7d53c314b7e5 [file] [log] [blame]
// Copyright 2015 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 "chrome/browser/android/history_report/delta_file_backend_leveldb.h"
#include <inttypes.h>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/process_memory_dump.h"
#include "chrome/browser/android/history_report/delta_file_commons.h"
#include "chrome/browser/android/history_report/usage_report_util.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/leveldb_chrome.h"
#include "third_party/leveldatabase/src/include/leveldb/comparator.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"
#include "url/gurl.h"
namespace {
const base::FilePath::CharType kDbFileName[] =
FILE_PATH_LITERAL("DeltaFileLevelDb");
int64_t GetLastSeqNo(leveldb::DB* db) {
leveldb::ReadOptions options;
std::unique_ptr<leveldb::Iterator> db_iter(db->NewIterator(options));
db_iter->SeekToLast();
int64_t seq_no = 0;
if (db_iter->Valid()) {
history_report::DeltaFileEntry last_entry;
leveldb::Slice value_slice = db_iter->value();
if (last_entry.ParseFromArray(value_slice.data(), value_slice.size()))
seq_no = last_entry.seq_no();
}
return seq_no;
}
void SaveChange(leveldb::DB* db,
const std::string& url,
const std::string& type) {
int64_t seq_no = GetLastSeqNo(db) + 1;
history_report::DeltaFileEntry entry;
entry.set_seq_no(seq_no);
entry.set_type(type);
entry.set_url(url);
leveldb::WriteOptions writeOptions;
std::string key;
base::SStringPrintf(&key, "%" PRId64, seq_no);
leveldb::Status status = db->Put(
writeOptions,
leveldb::Slice(key),
leveldb::Slice(entry.SerializeAsString()));
if (!status.ok())
LOG(WARNING) << "Save Change failed " << status.ToString();
}
} // namespace
namespace history_report {
// Comparator used in leveldb.
class DeltaFileBackend::DigitsComparator : public leveldb::Comparator {
public:
int Compare(const leveldb::Slice& a,
const leveldb::Slice& b) const override {
int64_t first;
int64_t second;
// Keys which can't be parsed go to the end.
if (!base::StringToInt64(a.ToString(), &first)) return 1;
if (!base::StringToInt64(b.ToString(), &second)) return -1;
if (first < second) return -1;
if (first > second) return 1;
return 0;
}
const char* Name() const override { return "DigitsComparator"; }
void FindShortestSeparator(std::string*,
const leveldb::Slice&) const override { }
void FindShortSuccessor(std::string*) const override { }
};
DeltaFileBackend::DeltaFileBackend(const base::FilePath& dir)
: path_(dir.Append(kDbFileName)),
leveldb_cmp_(new DeltaFileBackend::DigitsComparator()) {
}
DeltaFileBackend::~DeltaFileBackend() {}
bool DeltaFileBackend::Init() {
leveldb_env::Options options;
options.create_if_missing = true;
options.max_open_files = 0; // Use minimum number of files.
options.comparator = leveldb_cmp_.get();
options.write_buffer_size = 500 * 1024;
std::string path = path_.value();
leveldb::Status status = leveldb_env::OpenDB(options, path, &db_);
if (status.IsCorruption()) {
LOG(WARNING) << "Deleting corrupt database";
status = leveldb_chrome::DeleteDB(path_, options);
if (!status.ok()) {
LOG(ERROR) << "Unable to delete corrupt database " << path_
<< ", error: " << status.ToString();
return false;
}
status = leveldb_env::OpenDB(options, path, &db_);
}
if (!status.ok()) {
LOG(WARNING) << "Unable to open " << path_.value() << ": "
<< status.ToString();
return false;
}
CHECK(db_);
UMA_HISTOGRAM_COUNTS_1M("Search.HistoryReport.DeltaFile.LevelDBEntries",
usage_report_util::DatabaseEntries(db_.get()));
UMA_HISTOGRAM_COUNTS_1M("Search.HistoryReport.DeltaFile.LastSeqNo",
GetLastSeqNo(db_.get()));
return true;
}
bool DeltaFileBackend::EnsureInitialized() {
if (db_.get()) return true;
return Init();
}
void DeltaFileBackend::PageAdded(const GURL& url) {
if (!EnsureInitialized()) return;
SaveChange(db_.get(), url.spec(), "add");
}
void DeltaFileBackend::PageDeleted(const GURL& url) {
if (!EnsureInitialized()) return;
SaveChange(db_.get(), url.spec(), "del");
}
int64_t DeltaFileBackend::Trim(int64_t lower_bound) {
if (!EnsureInitialized()) return -1;
leveldb::ReadOptions read_options;
std::unique_ptr<leveldb::Iterator> db_iter(db_->NewIterator(read_options));
db_iter->SeekToFirst();
if (!db_iter->Valid())
return -1;
history_report::DeltaFileEntry first_entry;
leveldb::Slice value_slice = db_iter->value();
if (!first_entry.ParseFromArray(value_slice.data(), value_slice.size()))
return -1;
int64_t min_seq_no = first_entry.seq_no();
db_iter->SeekToLast();
if (!db_iter->Valid())
return -1;
history_report::DeltaFileEntry last_entry;
value_slice = db_iter->value();
if (!last_entry.ParseFromArray(value_slice.data(), value_slice.size()))
return -1;
int64_t max_seq_no = last_entry.seq_no();
// We want to have at least one entry in delta file left to know
// last sequence number in SaveChange.
if (max_seq_no <= lower_bound)
lower_bound = max_seq_no - 1;
leveldb::WriteBatch updates;
for (int64_t seq_no = min_seq_no; seq_no <= lower_bound; ++seq_no) {
std::string key;
base::SStringPrintf(&key, "%" PRId64, seq_no);
updates.Delete(leveldb::Slice(key));
}
leveldb::WriteOptions write_options;
leveldb::Status status = db_->Write(write_options, &updates);
if (status.ok())
return max_seq_no;
LOG(WARNING) << "Trim failed: " << status.ToString();
return -1;
}
bool DeltaFileBackend::Recreate(const std::vector<std::string>& urls) {
if (!EnsureInitialized()) return false;
Clear();
int64_t seq_no = 1;
leveldb::WriteBatch updates;
for (std::vector<std::string>::const_iterator it = urls.begin();
it != urls.end();
++it) {
DeltaFileEntry entry;
entry.set_seq_no(seq_no);
entry.set_url(*it);
entry.set_type("add");
std::string key;
base::SStringPrintf(&key, "%" PRId64, seq_no);
updates.Put(leveldb::Slice(key),
leveldb::Slice(entry.SerializeAsString()));
++seq_no;
}
leveldb::WriteOptions options;
leveldb::Status status = db_->Write(options, &updates);
if (status.ok())
return true;
LOG(WARNING) << "Recreate failed: " << status.ToString();
return false;
}
std::unique_ptr<std::vector<DeltaFileEntryWithData>> DeltaFileBackend::Query(
int64_t last_seq_no,
int32_t limit) {
if (!EnsureInitialized())
return std::make_unique<std::vector<DeltaFileEntryWithData>>();
std::string start;
base::SStringPrintf(&start, "%" PRId64, last_seq_no + 1);
leveldb::ReadOptions options;
std::unique_ptr<leveldb::Iterator> db_it(db_->NewIterator(options));
std::unique_ptr<std::vector<DeltaFileEntryWithData>> result(
new std::vector<DeltaFileEntryWithData>());
int32_t count = 0;
for (db_it->Seek(start); db_it->Valid() && count < limit; db_it->Next()) {
DeltaFileEntry entry;
leveldb::Slice value_slice = db_it->value();
if (!entry.ParseFromArray(value_slice.data(), value_slice.size()))
continue;
result->push_back(DeltaFileEntryWithData(entry));
++count;
}
return result;
}
void DeltaFileBackend::Clear() {
if (!EnsureInitialized()) return;
db_.reset();
leveldb_chrome::DeleteDB(path_, leveldb_env::Options());
Init();
}
std::string DeltaFileBackend::Dump() {
std::string dump("\n Delta File [");
if (!EnsureInitialized()) {
dump.append("not initialized]");
return dump;
}
dump.append("num pending entries=");
leveldb::ReadOptions options;
std::unique_ptr<leveldb::Iterator> db_it(db_->NewIterator(options));
int num_entries = 0;
for (db_it->SeekToFirst(); db_it->Valid(); db_it->Next()) num_entries++;
dump.append(base::IntToString(num_entries));
dump.append("]");
return dump;
}
bool DeltaFileBackend::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
if (!db_)
return true;
// leveldb_env::DBTracker already records memory usage. Add ownership edge
// to the dump.
auto* tracker_dump =
leveldb_env::DBTracker::GetOrCreateAllocatorDump(pmd, db_.get());
if (!tracker_dump)
return true;
auto* dump = pmd->CreateAllocatorDump(
base::StringPrintf("history/delta_file_service/leveldb_0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(db_.get())));
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
tracker_dump->GetSizeInternal());
pmd->AddOwnershipEdge(dump->guid(), tracker_dump->guid());
return true;
}
} // namespace history_report