Summary: 1. Added a new option that support user-defined table stats collection. 2. Added a deleted key stats collector in `utilities` Test Plan: Added a unit test for newly added code. Also ran make check to make sure other tests are not broken. Reviewers: dhruba, haobo Reviewed By: dhruba CC: leveldb Differential Revision: https://reviews.facebook.net/D13491main
parent
7e91b86f4d
commit
994575c134
@ -0,0 +1,55 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/table_stats_collector.h" |
||||
|
||||
#include "db/dbformat.h" |
||||
#include "util/coding.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
Status InternalKeyStatsCollector::Add(const Slice& key, const Slice& value) { |
||||
ParsedInternalKey ikey; |
||||
if (!ParseInternalKey(key, &ikey)) { |
||||
return Status::InvalidArgument("Invalid internal key"); |
||||
} |
||||
|
||||
if (ikey.type == ValueType::kTypeDeletion) { |
||||
++deleted_keys_; |
||||
} |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status InternalKeyStatsCollector::Finish( |
||||
TableStats::UserCollectedStats* stats) { |
||||
assert(stats); |
||||
assert(stats->find(InternalKeyTableStatsNames::kDeletedKeys) == stats->end()); |
||||
std::string val; |
||||
|
||||
PutVarint64(&val, deleted_keys_); |
||||
stats->insert(std::make_pair(InternalKeyTableStatsNames::kDeletedKeys, val)); |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status UserKeyTableStatsCollector::Add(const Slice& key, const Slice& value) { |
||||
ParsedInternalKey ikey; |
||||
if (!ParseInternalKey(key, &ikey)) { |
||||
return Status::InvalidArgument("Invalid internal key"); |
||||
} |
||||
|
||||
return collector_->Add(ikey.user_key, value); |
||||
} |
||||
|
||||
Status UserKeyTableStatsCollector::Finish( |
||||
TableStats::UserCollectedStats* stats) { |
||||
return collector_->Finish(stats); |
||||
} |
||||
|
||||
const std::string InternalKeyTableStatsNames::kDeletedKeys |
||||
= "rocksdb.deleted.keys"; |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,58 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// This file defines a collection of statistics collectors.
|
||||
#pragma once |
||||
|
||||
#include "rocksdb/table_stats.h" |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
struct InternalKeyTableStatsNames { |
||||
static const std::string kDeletedKeys; |
||||
}; |
||||
|
||||
// Collecting the statistics for internal keys. Visible only by internal
|
||||
// rocksdb modules.
|
||||
class InternalKeyStatsCollector : public TableStatsCollector { |
||||
public: |
||||
virtual Status Add(const Slice& key, const Slice& value); |
||||
virtual Status Finish(TableStats::UserCollectedStats* stats); |
||||
virtual const char* Name() const { return "InternalKeyStatsCollector"; } |
||||
|
||||
private: |
||||
uint64_t deleted_keys_ = 0; |
||||
}; |
||||
|
||||
// When rocksdb creates a new table, it will encode all "user keys" into
|
||||
// "internal keys", which contains meta information of a given entry.
|
||||
//
|
||||
// This class extracts user key from the encoded internal key when Add() is
|
||||
// invoked.
|
||||
class UserKeyTableStatsCollector : public TableStatsCollector { |
||||
public: |
||||
explicit UserKeyTableStatsCollector(TableStatsCollector* collector): |
||||
UserKeyTableStatsCollector( |
||||
std::shared_ptr<TableStatsCollector>(collector) |
||||
) { |
||||
} |
||||
|
||||
explicit UserKeyTableStatsCollector( |
||||
std::shared_ptr<TableStatsCollector> collector) : collector_(collector) { |
||||
} |
||||
virtual ~UserKeyTableStatsCollector() { } |
||||
virtual Status Add(const Slice& key, const Slice& value); |
||||
virtual Status Finish(TableStats::UserCollectedStats* stats); |
||||
virtual const char* Name() const { return collector_->Name(); } |
||||
|
||||
protected: |
||||
std::shared_ptr<TableStatsCollector> collector_; |
||||
}; |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,259 @@ |
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include <map> |
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
#include "db/dbformat.h" |
||||
#include "db/db_impl.h" |
||||
#include "db/table_stats_collector.h" |
||||
#include "rocksdb/table_builder.h" |
||||
#include "rocksdb/table_stats.h" |
||||
#include "table/table.h" |
||||
#include "util/coding.h" |
||||
#include "util/testharness.h" |
||||
#include "util/testutil.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class TableStatsTest { |
||||
private: |
||||
unique_ptr<Table> table_; |
||||
}; |
||||
|
||||
// TODO(kailiu) the following classes should be moved to some more general
|
||||
// places, so that other tests can also make use of them.
|
||||
// `FakeWritableFile` and `FakeRandomeAccessFile` bypass the real file system
|
||||
// and therefore enable us to quickly setup the tests.
|
||||
class FakeWritableFile : public WritableFile { |
||||
public: |
||||
~FakeWritableFile() { } |
||||
|
||||
const std::string& contents() const { return contents_; } |
||||
|
||||
virtual Status Close() { return Status::OK(); } |
||||
virtual Status Flush() { return Status::OK(); } |
||||
virtual Status Sync() { return Status::OK(); } |
||||
|
||||
virtual Status Append(const Slice& data) { |
||||
contents_.append(data.data(), data.size()); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
std::string contents_; |
||||
}; |
||||
|
||||
|
||||
class FakeRandomeAccessFile : public RandomAccessFile { |
||||
public: |
||||
explicit FakeRandomeAccessFile(const Slice& contents) |
||||
: contents_(contents.data(), contents.size()) { |
||||
} |
||||
|
||||
virtual ~FakeRandomeAccessFile() { } |
||||
|
||||
uint64_t Size() const { return contents_.size(); } |
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result, |
||||
char* scratch) const { |
||||
if (offset > contents_.size()) { |
||||
return Status::InvalidArgument("invalid Read offset"); |
||||
} |
||||
if (offset + n > contents_.size()) { |
||||
n = contents_.size() - offset; |
||||
} |
||||
memcpy(scratch, &contents_[offset], n); |
||||
*result = Slice(scratch, n); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
std::string contents_; |
||||
}; |
||||
|
||||
|
||||
class DumbLogger : public Logger { |
||||
public: |
||||
virtual void Logv(const char* format, va_list ap) { } |
||||
virtual size_t GetLogFileSize() const { return 0; } |
||||
}; |
||||
|
||||
// Utilities test functions
|
||||
void MakeBuilder( |
||||
const Options& options, |
||||
std::unique_ptr<FakeWritableFile>* writable, |
||||
std::unique_ptr<TableBuilder>* builder) { |
||||
writable->reset(new FakeWritableFile); |
||||
builder->reset( |
||||
new TableBuilder(options, writable->get()) |
||||
); |
||||
} |
||||
|
||||
void OpenTable( |
||||
const Options& options, |
||||
const std::string& contents, |
||||
std::unique_ptr<Table>* table) { |
||||
std::unique_ptr<RandomAccessFile> file(new FakeRandomeAccessFile(contents)); |
||||
auto s = Table::Open( |
||||
options, |
||||
EnvOptions(), |
||||
std::move(file), |
||||
contents.size(), |
||||
table |
||||
); |
||||
ASSERT_OK(s); |
||||
} |
||||
|
||||
// Collects keys that starts with "A" in a table.
|
||||
class RegularKeysStartWithA: public TableStatsCollector { |
||||
public: |
||||
const char* Name() const { return "RegularKeysStartWithA"; } |
||||
|
||||
Status Finish(TableStats::UserCollectedStats* stats) { |
||||
std::string encoded; |
||||
PutVarint32(&encoded, count_); |
||||
*stats = TableStats::UserCollectedStats { |
||||
{ "TableStatsTest", "Rocksdb" }, |
||||
{ "Count", encoded } |
||||
}; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status Add(const Slice& user_key, const Slice& value) { |
||||
// simply asssume all user keys are not empty.
|
||||
if (user_key.data()[0] == 'A') { |
||||
++count_; |
||||
} |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
uint32_t count_ = 0; |
||||
}; |
||||
|
||||
TEST(TableStatsTest, CustomizedTableStatsCollector) { |
||||
Options options; |
||||
|
||||
// make sure the entries will be inserted with order.
|
||||
std::map<std::string, std::string> kvs = { |
||||
{"About", "val5"}, // starts with 'A'
|
||||
{"Abstract", "val2"}, // starts with 'A'
|
||||
{"Around", "val7"}, // starts with 'A'
|
||||
{"Beyond", "val3"}, |
||||
{"Builder", "val1"}, |
||||
{"Cancel", "val4"}, |
||||
{"Find", "val6"}, |
||||
}; |
||||
|
||||
// Test stats collectors with internal keys or regular keys
|
||||
for (bool encode_as_internal : { true, false }) { |
||||
// -- Step 1: build table
|
||||
auto collector = new RegularKeysStartWithA(); |
||||
if (encode_as_internal) { |
||||
options.table_stats_collectors = { |
||||
std::make_shared<UserKeyTableStatsCollector>(collector) |
||||
}; |
||||
} else { |
||||
options.table_stats_collectors.resize(1); |
||||
options.table_stats_collectors[0].reset(collector); |
||||
} |
||||
std::unique_ptr<TableBuilder> builder; |
||||
std::unique_ptr<FakeWritableFile> writable; |
||||
MakeBuilder(options, &writable, &builder); |
||||
|
||||
for (const auto& kv : kvs) { |
||||
if (encode_as_internal) { |
||||
InternalKey ikey(kv.first, 0, ValueType::kTypeValue); |
||||
builder->Add(ikey.Encode(), kv.second); |
||||
} else { |
||||
builder->Add(kv.first, kv.second); |
||||
} |
||||
} |
||||
ASSERT_OK(builder->Finish()); |
||||
|
||||
// -- Step 2: Open table
|
||||
std::unique_ptr<Table> table; |
||||
OpenTable(options, writable->contents(), &table); |
||||
const auto& stats = table->GetTableStats().user_collected_stats; |
||||
|
||||
ASSERT_EQ("Rocksdb", stats.at("TableStatsTest")); |
||||
|
||||
uint32_t starts_with_A = 0; |
||||
Slice key(stats.at("Count")); |
||||
ASSERT_TRUE(GetVarint32(&key, &starts_with_A)); |
||||
ASSERT_EQ(3u, starts_with_A); |
||||
} |
||||
} |
||||
|
||||
TEST(TableStatsTest, InternalKeyStatsCollector) { |
||||
InternalKey keys[] = { |
||||
InternalKey("A", 0, ValueType::kTypeValue), |
||||
InternalKey("B", 0, ValueType::kTypeValue), |
||||
InternalKey("C", 0, ValueType::kTypeValue), |
||||
InternalKey("W", 0, ValueType::kTypeDeletion), |
||||
InternalKey("X", 0, ValueType::kTypeDeletion), |
||||
InternalKey("Y", 0, ValueType::kTypeDeletion), |
||||
InternalKey("Z", 0, ValueType::kTypeDeletion), |
||||
}; |
||||
|
||||
for (bool sanitized : { false, true }) { |
||||
std::unique_ptr<TableBuilder> builder; |
||||
std::unique_ptr<FakeWritableFile> writable; |
||||
Options options; |
||||
if (sanitized) { |
||||
options.table_stats_collectors = { |
||||
std::make_shared<RegularKeysStartWithA>() |
||||
}; |
||||
// with sanitization, even regular stats collector will be able to
|
||||
// handle internal keys.
|
||||
auto comparator = options.comparator; |
||||
// HACK: Set options.info_log to avoid writing log in
|
||||
// SanitizeOptions().
|
||||
options.info_log = std::make_shared<DumbLogger>(); |
||||
options = SanitizeOptions( |
||||
"db", // just a place holder
|
||||
nullptr, // with skip internal key comparator
|
||||
nullptr, // don't care filter policy
|
||||
options |
||||
); |
||||
options.comparator = comparator; |
||||
} else { |
||||
options.table_stats_collectors = { |
||||
std::make_shared<InternalKeyStatsCollector>() |
||||
}; |
||||
} |
||||
|
||||
MakeBuilder(options, &writable, &builder); |
||||
for (const auto& k : keys) { |
||||
builder->Add(k.Encode(), "val"); |
||||
} |
||||
|
||||
ASSERT_OK(builder->Finish()); |
||||
|
||||
std::unique_ptr<Table> table; |
||||
OpenTable(options, writable->contents(), &table); |
||||
const auto& stats = table->GetTableStats().user_collected_stats; |
||||
|
||||
uint64_t deleted = 0; |
||||
Slice key(stats.at(InternalKeyTableStatsNames::kDeletedKeys)); |
||||
ASSERT_TRUE(GetVarint64(&key, &deleted)); |
||||
ASSERT_EQ(4u, deleted); |
||||
|
||||
if (sanitized) { |
||||
uint32_t starts_with_A = 0; |
||||
Slice key(stats.at("Count")); |
||||
ASSERT_TRUE(GetVarint32(&key, &starts_with_A)); |
||||
ASSERT_EQ(1u, starts_with_A); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
return rocksdb::test::RunAllTests(); |
||||
} |
Loading…
Reference in new issue