Persistent Stats: persist stats history to disk (#5046)
Summary: This PR continues the work in https://github.com/facebook/rocksdb/pull/4748 and https://github.com/facebook/rocksdb/pull/4535 by adding a new DBOption `persist_stats_to_disk` which instructs RocksDB to persist stats history to RocksDB itself. When statistics is enabled, and both options `stats_persist_period_sec` and `persist_stats_to_disk` are set, RocksDB will periodically write stats to a built-in column family in the following form: key -> (timestamp in microseconds)#(stats name), value -> stats value. The existing API `GetStatsHistory` will detect the current value of `persist_stats_to_disk` and either read from in-memory data structure or from the hidden column family on disk. Pull Request resolved: https://github.com/facebook/rocksdb/pull/5046 Differential Revision: D15863138 Pulled By: miasantreble fbshipit-source-id: bb82abdb3f2ca581aa42531734ac799f113e931bmain
parent
ee294c24ed
commit
671d15cbdd
@ -0,0 +1,171 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#include "monitoring/persistent_stats_history.h" |
||||||
|
|
||||||
|
#include <cstring> |
||||||
|
#include <string> |
||||||
|
#include <utility> |
||||||
|
#include "db/db_impl/db_impl.h" |
||||||
|
#include "port/likely.h" |
||||||
|
#include "util/string_util.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
// 10 digit seconds timestamp => [Sep 9, 2001 ~ Nov 20, 2286]
|
||||||
|
const int kNowSecondsStringLength = 10; |
||||||
|
const std::string kFormatVersionKeyString = |
||||||
|
"__persistent_stats_format_version__"; |
||||||
|
const std::string kCompatibleVersionKeyString = |
||||||
|
"__persistent_stats_compatible_version__"; |
||||||
|
// Every release maintains two versions numbers for persistents stats: Current
|
||||||
|
// format version and compatible format version. Current format version
|
||||||
|
// designates what type of encoding will be used when writing to stats CF;
|
||||||
|
// compatible format version designates the minimum format version that
|
||||||
|
// can decode the stats CF encoded using the current format version.
|
||||||
|
const uint64_t kStatsCFCurrentFormatVersion = 1; |
||||||
|
const uint64_t kStatsCFCompatibleFormatVersion = 1; |
||||||
|
|
||||||
|
Status DecodePersistentStatsVersionNumber(DBImpl* db, StatsVersionKeyType type, |
||||||
|
uint64_t* version_number) { |
||||||
|
if (type >= StatsVersionKeyType::kKeyTypeMax) { |
||||||
|
return Status::InvalidArgument("Invalid stats version key type provided"); |
||||||
|
} |
||||||
|
std::string key; |
||||||
|
if (type == StatsVersionKeyType::kFormatVersion) { |
||||||
|
key = kFormatVersionKeyString; |
||||||
|
} else if (type == StatsVersionKeyType::kCompatibleVersion) { |
||||||
|
key = kCompatibleVersionKeyString; |
||||||
|
} |
||||||
|
ReadOptions options; |
||||||
|
options.verify_checksums = true; |
||||||
|
std::string result; |
||||||
|
Status s = db->Get(options, db->PersistentStatsColumnFamily(), key, &result); |
||||||
|
if (!s.ok() || result.empty()) { |
||||||
|
return Status::NotFound("Persistent stats version key " + key + |
||||||
|
" not found."); |
||||||
|
} |
||||||
|
|
||||||
|
// read version_number but do nothing in current version
|
||||||
|
*version_number = ParseUint64(result); |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
int EncodePersistentStatsKey(uint64_t now_seconds, const std::string& key, |
||||||
|
int size, char* buf) { |
||||||
|
char timestamp[kNowSecondsStringLength + 1]; |
||||||
|
// make time stamp string equal in length to allow sorting by time
|
||||||
|
snprintf(timestamp, sizeof(timestamp), "%010d", |
||||||
|
static_cast<int>(now_seconds)); |
||||||
|
timestamp[kNowSecondsStringLength] = '\0'; |
||||||
|
return snprintf(buf, size, "%s#%s", timestamp, key.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
void OptimizeForPersistentStats(ColumnFamilyOptions* cfo) { |
||||||
|
cfo->write_buffer_size = 2 << 20; |
||||||
|
cfo->target_file_size_base = 2 * 1048576; |
||||||
|
cfo->max_bytes_for_level_base = 10 * 1048576; |
||||||
|
cfo->snap_refresh_nanos = 0; |
||||||
|
cfo->soft_pending_compaction_bytes_limit = 256 * 1048576; |
||||||
|
cfo->hard_pending_compaction_bytes_limit = 1073741824ul; |
||||||
|
cfo->compression = kNoCompression; |
||||||
|
} |
||||||
|
|
||||||
|
PersistentStatsHistoryIterator::~PersistentStatsHistoryIterator() {} |
||||||
|
|
||||||
|
bool PersistentStatsHistoryIterator::Valid() const { return valid_; } |
||||||
|
|
||||||
|
Status PersistentStatsHistoryIterator::status() const { return status_; } |
||||||
|
|
||||||
|
void PersistentStatsHistoryIterator::Next() { |
||||||
|
// increment start_time by 1 to avoid infinite loop
|
||||||
|
AdvanceIteratorByTime(GetStatsTime() + 1, end_time_); |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t PersistentStatsHistoryIterator::GetStatsTime() const { return time_; } |
||||||
|
|
||||||
|
const std::map<std::string, uint64_t>& |
||||||
|
PersistentStatsHistoryIterator::GetStatsMap() const { |
||||||
|
return stats_map_; |
||||||
|
} |
||||||
|
|
||||||
|
std::pair<uint64_t, std::string> parseKey(const Slice& key, |
||||||
|
uint64_t start_time) { |
||||||
|
std::pair<uint64_t, std::string> result; |
||||||
|
std::string key_str = key.ToString(); |
||||||
|
std::string::size_type pos = key_str.find("#"); |
||||||
|
// TODO(Zhongyi): add counters to track parse failures?
|
||||||
|
if (pos == std::string::npos) { |
||||||
|
result.first = port::kMaxUint64; |
||||||
|
result.second.clear(); |
||||||
|
} else { |
||||||
|
uint64_t parsed_time = ParseUint64(key_str.substr(0, pos)); |
||||||
|
// skip entries with timestamp smaller than start_time
|
||||||
|
if (parsed_time < start_time) { |
||||||
|
result.first = port::kMaxUint64; |
||||||
|
result.second = ""; |
||||||
|
} else { |
||||||
|
result.first = parsed_time; |
||||||
|
std::string key_resize = key_str.substr(pos + 1); |
||||||
|
result.second = key_resize; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// advance the iterator to the next time between [start_time, end_time)
|
||||||
|
// if success, update time_ and stats_map_ with new_time and stats_map
|
||||||
|
void PersistentStatsHistoryIterator::AdvanceIteratorByTime(uint64_t start_time, |
||||||
|
uint64_t end_time) { |
||||||
|
// try to find next entry in stats_history_ map
|
||||||
|
if (db_impl_ != nullptr) { |
||||||
|
ReadOptions ro; |
||||||
|
Iterator* iter = |
||||||
|
db_impl_->NewIterator(ro, db_impl_->PersistentStatsColumnFamily()); |
||||||
|
|
||||||
|
char timestamp[kNowSecondsStringLength + 1]; |
||||||
|
snprintf(timestamp, sizeof(timestamp), "%010d", |
||||||
|
static_cast<int>(std::max(time_, start_time))); |
||||||
|
timestamp[kNowSecondsStringLength] = '\0'; |
||||||
|
|
||||||
|
iter->Seek(timestamp); |
||||||
|
// no more entries with timestamp >= start_time is found or version key
|
||||||
|
// is found to be incompatible
|
||||||
|
if (!iter->Valid()) { |
||||||
|
valid_ = false; |
||||||
|
delete iter; |
||||||
|
return; |
||||||
|
} |
||||||
|
time_ = parseKey(iter->key(), start_time).first; |
||||||
|
valid_ = true; |
||||||
|
// check parsed time and invalid if it exceeds end_time
|
||||||
|
if (time_ > end_time) { |
||||||
|
valid_ = false; |
||||||
|
delete iter; |
||||||
|
return; |
||||||
|
} |
||||||
|
// find all entries with timestamp equal to time_
|
||||||
|
std::map<std::string, uint64_t> new_stats_map; |
||||||
|
std::pair<uint64_t, std::string> kv; |
||||||
|
for (; iter->Valid(); iter->Next()) { |
||||||
|
kv = parseKey(iter->key(), start_time); |
||||||
|
if (kv.first != time_) { |
||||||
|
break; |
||||||
|
} |
||||||
|
if (kv.second.compare(kFormatVersionKeyString) == 0) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
new_stats_map[kv.second] = ParseUint64(iter->value().ToString()); |
||||||
|
} |
||||||
|
stats_map_.swap(new_stats_map); |
||||||
|
delete iter; |
||||||
|
} else { |
||||||
|
valid_ = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,83 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "db/db_impl/db_impl.h" |
||||||
|
#include "rocksdb/stats_history.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
extern const std::string kFormatVersionKeyString; |
||||||
|
extern const std::string kCompatibleVersionKeyString; |
||||||
|
extern const uint64_t kStatsCFCurrentFormatVersion; |
||||||
|
extern const uint64_t kStatsCFCompatibleFormatVersion; |
||||||
|
|
||||||
|
enum StatsVersionKeyType : uint32_t { |
||||||
|
kFormatVersion = 1, |
||||||
|
kCompatibleVersion = 2, |
||||||
|
kKeyTypeMax = 3 |
||||||
|
}; |
||||||
|
|
||||||
|
// Read the version number from persitent stats cf depending on type provided
|
||||||
|
// stores the version number in `*version_number`
|
||||||
|
// returns Status::OK() on success, or other status code on failure
|
||||||
|
Status DecodePersistentStatsVersionNumber(DBImpl* db, StatsVersionKeyType type, |
||||||
|
uint64_t* version_number); |
||||||
|
|
||||||
|
// Encode timestamp and stats key into buf
|
||||||
|
// Format: timestamp(10 digit) + '#' + key
|
||||||
|
// Total length of encoded key will be capped at 100 bytes
|
||||||
|
int EncodePersistentStatsKey(uint64_t timestamp, const std::string& key, |
||||||
|
int size, char* buf); |
||||||
|
|
||||||
|
void OptimizeForPersistentStats(ColumnFamilyOptions* cfo); |
||||||
|
|
||||||
|
class PersistentStatsHistoryIterator final : public StatsHistoryIterator { |
||||||
|
public: |
||||||
|
PersistentStatsHistoryIterator(uint64_t start_time, uint64_t end_time, |
||||||
|
DBImpl* db_impl) |
||||||
|
: time_(0), |
||||||
|
start_time_(start_time), |
||||||
|
end_time_(end_time), |
||||||
|
valid_(true), |
||||||
|
db_impl_(db_impl) { |
||||||
|
AdvanceIteratorByTime(start_time_, end_time_); |
||||||
|
} |
||||||
|
~PersistentStatsHistoryIterator() override; |
||||||
|
bool Valid() const override; |
||||||
|
Status status() const override; |
||||||
|
|
||||||
|
void Next() override; |
||||||
|
uint64_t GetStatsTime() const override; |
||||||
|
|
||||||
|
const std::map<std::string, uint64_t>& GetStatsMap() const override; |
||||||
|
|
||||||
|
private: |
||||||
|
// advance the iterator to the next stats history record with timestamp
|
||||||
|
// between [start_time, end_time)
|
||||||
|
void AdvanceIteratorByTime(uint64_t start_time, uint64_t end_time); |
||||||
|
|
||||||
|
// No copying allowed
|
||||||
|
PersistentStatsHistoryIterator(const PersistentStatsHistoryIterator&) = |
||||||
|
delete; |
||||||
|
void operator=(const PersistentStatsHistoryIterator&) = delete; |
||||||
|
PersistentStatsHistoryIterator(PersistentStatsHistoryIterator&&) = delete; |
||||||
|
PersistentStatsHistoryIterator& operator=(PersistentStatsHistoryIterator&&) = |
||||||
|
delete; |
||||||
|
|
||||||
|
uint64_t time_; |
||||||
|
uint64_t start_time_; |
||||||
|
uint64_t end_time_; |
||||||
|
std::map<std::string, uint64_t> stats_map_; |
||||||
|
Status status_; |
||||||
|
bool valid_; |
||||||
|
DBImpl* db_impl_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,576 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
#include <limits> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
|
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/db_impl/db_impl.h" |
||||||
|
#include "db/db_test_util.h" |
||||||
|
#include "monitoring/persistent_stats_history.h" |
||||||
|
#include "options/options_helper.h" |
||||||
|
#include "port/stack_trace.h" |
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "rocksdb/convenience.h" |
||||||
|
#include "rocksdb/rate_limiter.h" |
||||||
|
#include "rocksdb/stats_history.h" |
||||||
|
#include "test_util/sync_point.h" |
||||||
|
#include "test_util/testutil.h" |
||||||
|
#include "util/random.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class StatsHistoryTest : public DBTestBase { |
||||||
|
public: |
||||||
|
StatsHistoryTest() : DBTestBase("/stats_history_test") {} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(StatsHistoryTest, RunStatsDumpPeriodSec) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_dump_period_sec = 5; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
int counter = 0; |
||||||
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||||
|
#if defined(OS_MACOSX) && !defined(NDEBUG) |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { |
||||||
|
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg); |
||||||
|
if (time_us < mock_env->RealNowMicros()) { |
||||||
|
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000; |
||||||
|
} |
||||||
|
}); |
||||||
|
#endif // OS_MACOSX && !NDEBUG
|
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"DBImpl::DumpStats:1", [&](void* /*arg*/) { counter++; }); |
||||||
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
Reopen(options); |
||||||
|
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_dump_period_sec); |
||||||
|
dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
ASSERT_GE(counter, 1); |
||||||
|
|
||||||
|
// Test cacel job through SetOptions
|
||||||
|
ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}})); |
||||||
|
int old_val = counter; |
||||||
|
for (int i = 6; i < 20; ++i) { |
||||||
|
dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(i); }); |
||||||
|
} |
||||||
|
ASSERT_EQ(counter, old_val); |
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
// Test persistent stats background thread scheduling and cancelling
|
||||||
|
TEST_F(StatsHistoryTest, StatsPersistScheduling) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||||
|
#if defined(OS_MACOSX) && !defined(NDEBUG) |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { |
||||||
|
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg); |
||||||
|
if (time_us < mock_env->RealNowMicros()) { |
||||||
|
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000; |
||||||
|
} |
||||||
|
}); |
||||||
|
#endif // OS_MACOSX && !NDEBUG
|
||||||
|
int counter = 0; |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; }); |
||||||
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
Reopen(options); |
||||||
|
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec); |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
ASSERT_GE(counter, 1); |
||||||
|
|
||||||
|
// Test cacel job through SetOptions
|
||||||
|
ASSERT_TRUE(dbfull()->TEST_IsPersistentStatsEnabled()); |
||||||
|
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); |
||||||
|
ASSERT_FALSE(dbfull()->TEST_IsPersistentStatsEnabled()); |
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
// Test enabling persistent stats for the first time
|
||||||
|
TEST_F(StatsHistoryTest, PersistentStatsFreshInstall) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 0; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||||
|
#if defined(OS_MACOSX) && !defined(NDEBUG) |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { |
||||||
|
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg); |
||||||
|
if (time_us < mock_env->RealNowMicros()) { |
||||||
|
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000; |
||||||
|
} |
||||||
|
}); |
||||||
|
#endif // OS_MACOSX && !NDEBUG
|
||||||
|
int counter = 0; |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; }); |
||||||
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
Reopen(options); |
||||||
|
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "5"}})); |
||||||
|
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec); |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
ASSERT_GE(counter, 1); |
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(Zhongyi): Move persistent stats related tests to a separate file
|
||||||
|
TEST_F(StatsHistoryTest, GetStatsHistoryInMemory) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
options.statistics = rocksdb::CreateDBStatistics(); |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
#if defined(OS_MACOSX) && !defined(NDEBUG) |
||||||
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { |
||||||
|
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg); |
||||||
|
if (time_us < mock_env->RealNowMicros()) { |
||||||
|
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000; |
||||||
|
} |
||||||
|
}); |
||||||
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
#endif // OS_MACOSX && !NDEBUG
|
||||||
|
|
||||||
|
CreateColumnFamilies({"pikachu"}, options); |
||||||
|
ASSERT_OK(Put("foo", "bar")); |
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
|
||||||
|
int mock_time = 1; |
||||||
|
// Wait for stats persist to finish
|
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
std::unique_ptr<StatsHistoryIterator> stats_iter; |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 6 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
// disabled stats snapshots
|
||||||
|
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); |
||||||
|
size_t stats_count = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
ASSERT_EQ(stats_iter->GetStatsTime(), 5); |
||||||
|
stats_count += stats_map.size(); |
||||||
|
} |
||||||
|
ASSERT_GT(stats_count, 0); |
||||||
|
// Wait a bit and verify no more stats are found
|
||||||
|
for (mock_time = 6; mock_time < 20; ++mock_time) { |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(mock_time); }); |
||||||
|
} |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 20 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
size_t stats_count_new = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
stats_count_new += stats_iter->GetStatsMap().size(); |
||||||
|
} |
||||||
|
ASSERT_EQ(stats_count_new, stats_count); |
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(StatsHistoryTest, InMemoryStatsHistoryPurging) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.statistics = rocksdb::CreateDBStatistics(); |
||||||
|
options.stats_persist_period_sec = 1; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
#if defined(OS_MACOSX) && !defined(NDEBUG) |
||||||
|
rocksdb::SyncPoint::GetInstance()->DisableProcessing(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); |
||||||
|
rocksdb::SyncPoint::GetInstance()->SetCallBack( |
||||||
|
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { |
||||||
|
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg); |
||||||
|
if (time_us < mock_env->RealNowMicros()) { |
||||||
|
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000; |
||||||
|
} |
||||||
|
}); |
||||||
|
rocksdb::SyncPoint::GetInstance()->EnableProcessing(); |
||||||
|
#endif // OS_MACOSX && !NDEBUG
|
||||||
|
|
||||||
|
CreateColumnFamilies({"pikachu"}, options); |
||||||
|
ASSERT_OK(Put("foo", "bar")); |
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
// some random operation to populate statistics
|
||||||
|
ASSERT_OK(Delete("foo")); |
||||||
|
ASSERT_OK(Put("sol", "sol")); |
||||||
|
ASSERT_OK(Put("epic", "epic")); |
||||||
|
ASSERT_OK(Put("ltd", "ltd")); |
||||||
|
ASSERT_EQ("sol", Get("sol")); |
||||||
|
ASSERT_EQ("epic", Get("epic")); |
||||||
|
ASSERT_EQ("ltd", Get("ltd")); |
||||||
|
Iterator* iterator = db_->NewIterator(ReadOptions()); |
||||||
|
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { |
||||||
|
ASSERT_TRUE(iterator->key() == iterator->value()); |
||||||
|
} |
||||||
|
delete iterator; |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
ASSERT_OK(Delete("sol")); |
||||||
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); |
||||||
|
int mock_time = 1; |
||||||
|
// Wait for stats persist to finish
|
||||||
|
for (; mock_time < 5; ++mock_time) { |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(mock_time); }); |
||||||
|
} |
||||||
|
|
||||||
|
// second round of ops
|
||||||
|
ASSERT_OK(Put("saigon", "saigon")); |
||||||
|
ASSERT_OK(Put("noodle talk", "noodle talk")); |
||||||
|
ASSERT_OK(Put("ping bistro", "ping bistro")); |
||||||
|
iterator = db_->NewIterator(ReadOptions()); |
||||||
|
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { |
||||||
|
ASSERT_TRUE(iterator->key() == iterator->value()); |
||||||
|
} |
||||||
|
delete iterator; |
||||||
|
ASSERT_OK(Flush()); |
||||||
|
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); |
||||||
|
for (; mock_time < 10; ++mock_time) { |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(mock_time); }); |
||||||
|
} |
||||||
|
std::unique_ptr<StatsHistoryIterator> stats_iter; |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 10 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
size_t stats_count = 0; |
||||||
|
int slice_count = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
slice_count++; |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
stats_count += stats_map.size(); |
||||||
|
} |
||||||
|
size_t stats_history_size = dbfull()->TEST_EstimateInMemoryStatsHistorySize(); |
||||||
|
ASSERT_GE(slice_count, 9); |
||||||
|
ASSERT_GE(stats_history_size, 12000); |
||||||
|
// capping memory cost at 12000 bytes since one slice is around 10000~12000
|
||||||
|
ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "12000"}})); |
||||||
|
ASSERT_EQ(12000, dbfull()->GetDBOptions().stats_history_buffer_size); |
||||||
|
// Wait for stats persist to finish
|
||||||
|
for (; mock_time < 20; ++mock_time) { |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(mock_time); }); |
||||||
|
} |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 20 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
size_t stats_count_reopen = 0; |
||||||
|
slice_count = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
slice_count++; |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
stats_count_reopen += stats_map.size(); |
||||||
|
} |
||||||
|
size_t stats_history_size_reopen = |
||||||
|
dbfull()->TEST_EstimateInMemoryStatsHistorySize(); |
||||||
|
// only one slice can fit under the new stats_history_buffer_size
|
||||||
|
ASSERT_LT(slice_count, 2); |
||||||
|
ASSERT_TRUE(stats_history_size_reopen < 12000 && |
||||||
|
stats_history_size_reopen > 0); |
||||||
|
ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0); |
||||||
|
Close(); |
||||||
|
// TODO: may also want to verify stats timestamp to make sure we are purging
|
||||||
|
// the correct stats snapshot
|
||||||
|
} |
||||||
|
|
||||||
|
int countkeys(Iterator* iter) { |
||||||
|
int count = 0; |
||||||
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
||||||
|
count++; |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(StatsHistoryTest, GetStatsHistoryFromDisk) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
options.statistics = rocksdb::CreateDBStatistics(); |
||||||
|
options.persist_stats_to_disk = true; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
CreateColumnFamilies({"pikachu"}, options); |
||||||
|
ASSERT_OK(Put("foo", "bar")); |
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
ASSERT_EQ(Get("foo"), "bar"); |
||||||
|
|
||||||
|
// Wait for stats persist to finish
|
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
auto iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
int key_count1 = countkeys(iter); |
||||||
|
delete iter; |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(10); }); |
||||||
|
iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
int key_count2 = countkeys(iter); |
||||||
|
delete iter; |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(15); }); |
||||||
|
iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
int key_count3 = countkeys(iter); |
||||||
|
delete iter; |
||||||
|
ASSERT_GE(key_count2, key_count1); |
||||||
|
ASSERT_GE(key_count3, key_count2); |
||||||
|
ASSERT_EQ(key_count3 - key_count2, key_count2 - key_count1); |
||||||
|
std::unique_ptr<StatsHistoryIterator> stats_iter; |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 16 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
size_t stats_count = 0; |
||||||
|
int slice_count = 0; |
||||||
|
int non_zero_count = 0; |
||||||
|
for (int i = 1; stats_iter->Valid(); stats_iter->Next(), i++) { |
||||||
|
slice_count++; |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i); |
||||||
|
for (auto& stat : stats_map) { |
||||||
|
if (stat.second != 0) { |
||||||
|
non_zero_count++; |
||||||
|
} |
||||||
|
} |
||||||
|
stats_count += stats_map.size(); |
||||||
|
} |
||||||
|
ASSERT_EQ(slice_count, 3); |
||||||
|
// 2 extra keys for format version
|
||||||
|
ASSERT_EQ(stats_count, key_count3 - 2); |
||||||
|
// verify reopen will not cause data loss
|
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 16 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
size_t stats_count_reopen = 0; |
||||||
|
int slice_count_reopen = 0; |
||||||
|
int non_zero_count_recover = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
slice_count_reopen++; |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
for (auto& stat : stats_map) { |
||||||
|
if (stat.second != 0) { |
||||||
|
non_zero_count_recover++; |
||||||
|
} |
||||||
|
} |
||||||
|
stats_count_reopen += stats_map.size(); |
||||||
|
} |
||||||
|
ASSERT_EQ(non_zero_count, non_zero_count_recover); |
||||||
|
ASSERT_EQ(slice_count, slice_count_reopen); |
||||||
|
ASSERT_EQ(stats_count, stats_count_reopen); |
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
// Test persisted stats matches the value found in options.statistics and
|
||||||
|
// the stats value retains after DB reopen
|
||||||
|
TEST_F(StatsHistoryTest, PersitentStatsVerifyValue) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
options.statistics = rocksdb::CreateDBStatistics(); |
||||||
|
options.persist_stats_to_disk = true; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
std::map<std::string, uint64_t> stats_map_before; |
||||||
|
ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_before)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
CreateColumnFamilies({"pikachu"}, options); |
||||||
|
ASSERT_OK(Put("foo", "bar")); |
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
ASSERT_EQ(Get("foo"), "bar"); |
||||||
|
|
||||||
|
// Wait for stats persist to finish
|
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
auto iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
countkeys(iter); |
||||||
|
delete iter; |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(10); }); |
||||||
|
iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
countkeys(iter); |
||||||
|
delete iter; |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(15); }); |
||||||
|
iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
countkeys(iter); |
||||||
|
delete iter; |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun( |
||||||
|
[&] { mock_env->set_current_time(20); }); |
||||||
|
|
||||||
|
std::map<std::string, uint64_t> stats_map_after; |
||||||
|
ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_after)); |
||||||
|
std::unique_ptr<StatsHistoryIterator> stats_iter; |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 21 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
std::string sample = "rocksdb.num.iterator.deleted"; |
||||||
|
uint64_t recovered_value = 0; |
||||||
|
for (int i = 1; stats_iter->Valid(); stats_iter->Next(), ++i) { |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i); |
||||||
|
for (const auto& stat : stats_map) { |
||||||
|
if (sample.compare(stat.first) == 0) { |
||||||
|
recovered_value += stat.second; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ASSERT_EQ(recovered_value, stats_map_after[sample]); |
||||||
|
|
||||||
|
// test stats value retains after recovery
|
||||||
|
ReopenWithColumnFamilies({"default", "pikachu"}, options); |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 21 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
uint64_t new_recovered_value = 0; |
||||||
|
for (int i = 1; stats_iter->Valid(); stats_iter->Next(), i++) { |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
ASSERT_EQ(stats_iter->GetStatsTime(), 5 * i); |
||||||
|
for (const auto& stat : stats_map) { |
||||||
|
if (sample.compare(stat.first) == 0) { |
||||||
|
new_recovered_value += stat.second; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ASSERT_EQ(recovered_value, new_recovered_value); |
||||||
|
|
||||||
|
// TODO(Zhongyi): also add test to read raw values from disk and verify
|
||||||
|
// correctness
|
||||||
|
Close(); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO(Zhongyi): add test for different format versions
|
||||||
|
|
||||||
|
TEST_F(StatsHistoryTest, PersistentStatsCreateColumnFamilies) { |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
options.statistics = rocksdb::CreateDBStatistics(); |
||||||
|
options.persist_stats_to_disk = true; |
||||||
|
std::unique_ptr<rocksdb::MockTimeEnv> mock_env; |
||||||
|
mock_env.reset(new rocksdb::MockTimeEnv(env_)); |
||||||
|
mock_env->set_current_time(0); // in seconds
|
||||||
|
options.env = mock_env.get(); |
||||||
|
ASSERT_OK(TryReopen(options)); |
||||||
|
CreateColumnFamilies({"one", "two", "three"}, options); |
||||||
|
ASSERT_OK(Put(1, "foo", "bar")); |
||||||
|
ReopenWithColumnFamilies({"default", "one", "two", "three"}, options); |
||||||
|
ASSERT_EQ(Get(2, "foo"), "bar"); |
||||||
|
CreateColumnFamilies({"four"}, options); |
||||||
|
ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); |
||||||
|
ASSERT_EQ(Get(2, "foo"), "bar"); |
||||||
|
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); |
||||||
|
auto iter = |
||||||
|
db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); |
||||||
|
int key_count = countkeys(iter); |
||||||
|
delete iter; |
||||||
|
ASSERT_GE(key_count, 0); |
||||||
|
uint64_t num_write_wal = 0; |
||||||
|
std::string sample = "rocksdb.write.wal"; |
||||||
|
std::unique_ptr<StatsHistoryIterator> stats_iter; |
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 5 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
for (const auto& stat : stats_map) { |
||||||
|
if (sample.compare(stat.first) == 0) { |
||||||
|
num_write_wal += stat.second; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
stats_iter.reset(); |
||||||
|
ASSERT_EQ(num_write_wal, 2); |
||||||
|
|
||||||
|
options.persist_stats_to_disk = false; |
||||||
|
ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); |
||||||
|
int cf_count = 0; |
||||||
|
for (auto cfd : *dbfull()->versions_->GetColumnFamilySet()) { |
||||||
|
(void)cfd; |
||||||
|
cf_count++; |
||||||
|
} |
||||||
|
// persistent stats cf will be implicitly opened even if
|
||||||
|
// persist_stats_to_disk is false
|
||||||
|
ASSERT_EQ(cf_count, 6); |
||||||
|
ASSERT_EQ(Get(2, "foo"), "bar"); |
||||||
|
|
||||||
|
// attempt to create column family using same name, should fail
|
||||||
|
ColumnFamilyOptions cf_opts(options); |
||||||
|
ColumnFamilyHandle* handle; |
||||||
|
ASSERT_NOK(db_->CreateColumnFamily(cf_opts, kPersistentStatsColumnFamilyName, |
||||||
|
&handle)); |
||||||
|
|
||||||
|
options.persist_stats_to_disk = true; |
||||||
|
ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); |
||||||
|
ASSERT_NOK(db_->CreateColumnFamily(cf_opts, kPersistentStatsColumnFamilyName, |
||||||
|
&handle)); |
||||||
|
// verify stats is not affected by prior failed CF creation
|
||||||
|
db_->GetStatsHistory(0 /*start_time*/, 5 /*end_time*/, &stats_iter); |
||||||
|
ASSERT_TRUE(stats_iter != nullptr); |
||||||
|
num_write_wal = 0; |
||||||
|
for (; stats_iter->Valid(); stats_iter->Next()) { |
||||||
|
auto stats_map = stats_iter->GetStatsMap(); |
||||||
|
for (const auto& stat : stats_map) { |
||||||
|
if (sample.compare(stat.first) == 0) { |
||||||
|
num_write_wal += stat.second; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ASSERT_EQ(num_write_wal, 2); |
||||||
|
|
||||||
|
Close(); |
||||||
|
Destroy(options); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(StatsHistoryTest, PersistentStatsReadOnly) { |
||||||
|
ASSERT_OK(Put("bar", "v2")); |
||||||
|
Close(); |
||||||
|
|
||||||
|
auto options = CurrentOptions(); |
||||||
|
options.stats_persist_period_sec = 5; |
||||||
|
options.persist_stats_to_disk = true; |
||||||
|
assert(options.env == env_); |
||||||
|
ASSERT_OK(ReadOnlyReopen(options)); |
||||||
|
ASSERT_EQ("v2", Get("bar")); |
||||||
|
Close(); |
||||||
|
|
||||||
|
// Reopen and flush memtable.
|
||||||
|
Reopen(options); |
||||||
|
Flush(); |
||||||
|
Close(); |
||||||
|
// Now check keys in read only mode.
|
||||||
|
ASSERT_OK(ReadOnlyReopen(options)); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
rocksdb::port::InstallStackTraceHandler(); |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue