rocksdb/options/configurable.cc
Peter Dillinger 9d490593d0 Preliminary support for custom compression algorithms (#13659)
Summary:
This change builds on https://github.com/facebook/rocksdb/issues/13540 and https://github.com/facebook/rocksdb/issues/13626 in allowing a CompressionManager / Compressor / Decompressor to use a custom compression algorithm, with a distinct CompressionType. For background, review the API comments on CompressionManager and its CompatibilityName() function.

Highlights:
* Reserve and name 127 new CompressionTypes that can be used for custom compression algorithms / schemas. In many or most cases I expect the enumerators such as `kCustomCompression8F` to be used in user code rather than casting between integers and CompressionTypes, as I expect the supported custom compression algorithms to be identifiable / enumerable at compile time.
* When using these custom compression types, a CompressionManager must use a CompatibilityName() other than the built-in one AND new format_version=7 (see below).
* When building new SST files, track the full set of CompressionTypes actually used (usually just one aside from kNoCompression), using our efficient bitset SmallEnumSet, which supports fast iteration over the bits set to 1. Ideally, to support mixed or non-mixed compression algorithms in a file as efficiently as possible, we would know the set of CompressionTypes as SST file open time.
* New schema for `TableProperties::compression_name` in format_version=7 to represent the CompressionManager's CompatibilityName(), the set of CompressionTypes used, and potentially more in the future, while keeping the data relatively human-readable.
  * It would be possible to do this without a new format_version, but then the only way to ensure incompatible versions fail is with an unsupported CompressionType tag, not with a compression_name property. Therefore, (a) I prefer not to put something misleading in the `compression_name` property (a built-in compression name) when there is nuance because of a CompressionManager, and (b) I prefer better, more consistent error messages that refer to either format_version or the CompressionManager's CompatibilityName(), rather than an unrecognized custom CompressionType value (which could have come from various CompressionManagers).
* The current configured CompressionManager is passed in to TableReaders so that it (or one it knows about) can be used if it matches the CompatibilityName() used for compression in the SST file. Until the connection with ObjectRegistry is implemented, the only way to read files generated with a particular CompressionManager using custom compression algorithms is to configure it (or a known relative; see FindCompatibleCompressionManager()) in the ColumnFamilyOptions.
* Optimized snappy compression with BuiltinDecompressorV2SnappyOnly, to offset some small added overheads with the new tracking. This is essentially an early part of the planned refactoring that will get rid of the old internal compression APIs.
* Another small optimization in eliminating an unnecessary key copy in flush (builder.cc).
* Fix some handling of named CompressionManagers in CompressionManager::CreateFromString() (problem seen in https://github.com/facebook/rocksdb/issues/13647)

Smaller things:
* Adds Name() and GetId() functions to Compressor for debugging/logging purposes. (Compressor and Decompressor are not expected to be Customizable because they are only instantiated by a CompressionManager.)
* When using an explicit compression_manager, the GetId() of the CompressionManager and the Compressor used to build the file are stored as bonus entries in the compression_options table property. This table property is not parsed anywhere, so it is currently for human reading, but still could be parsed with the new underscore-prefixed bonus entries. IMHO, this is preferable to additional table properties, which would increase memory fragmentation in the TableProperties objects and likely take slightly more CPU on SST open and slightly more storage.
* ReleaseWorkingArea() function from protected to public to make wrappers work, because of a quirk in C++ (vs. Java) in which you cannot access protected members of another instance of the same class (sigh)
* Added `CompressionManager:: SupportsCompressionType()` for early options sanity checking.

Follow-up before release:
* Make format_version=7 official / supported
* Stress test coverage

Sooner than later:
* Update tests for RoundRobinManager and SimpleMixedCompressionManager to take advantage of e.g. set of compression types in compression_name property
* ObjectRegistry stuff
* Refactor away old internal compression APIs

Pull Request resolved: https://github.com/facebook/rocksdb/pull/13659

Test Plan:
Basic unit test added.

## Performance

### SST write performance
```
SUFFIX=`tty | sed 's|/|_|g'`; for ARGS in "-compression_type=none" "-compression_type=snappy" "-compression_type=zstd" "-compression_type=snappy -verify_compression=1" "-compression_type=zstd -verify_compression=1" "-compression_type=zstd -compression_max_dict_bytes=8180"; do echo $ARGS; (for I in `seq 1 20`; do BIN=/dev/shm/dbbench${SUFFIX}.bin; rm -f $BIN; cp db_bench $BIN; $BIN -db=/dev/shm/dbbench$SUFFIX --benchmarks=fillseq -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -format_version=7 $ARGS 2>&1 | grep micros/op; done) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Ops/sec, Before -> After, both fv=6:
-compression_type=none
1894386 -> 1858403 (-2.0%)
-compression_type=snappy
1859131 -> 1807469 (-2.8%)
-compression_type=zstd
1191428 -> 1214374 (+1.9%)
-compression_type=snappy -verify_compression=1
1861819 -> 1858342 (+0.2%)
-compression_type=zstd -verify_compression=1
979435 -> 995870 (+1.6%)
-compression_type=zstd -compression_max_dict_bytes=8180
905349 -> 940563 (+3.9%)

Ops/sec, Before fv=6 -> After fv=7:
-compression_type=none
1879365 -> 1836159 (-2.3%)
-compression_type=snappy
1865460 -> 1830916 (-1.9%)
-compression_type=zstd
1191428 -> 1210260 (+1.6%)
-compression_type=snappy -verify_compression=1
1866756 -> 1818989 (-2.6%)
-compression_type=zstd -verify_compression=1
982640 -> 997129 (+1.5%)
-compression_type=zstd -compression_max_dict_bytes=8180
912608 -> 937248 (+2.7%)

### SST read performance
Create DBs
```
for COMP in none snappy zstd; do echo $ARGS; ./db_bench -db=/dev/shm/dbbench-7-$COMP --benchmarks=fillseq,flush -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=1000 -fifo_compaction_allow_compaction=0 -disable_wal -write_buffer_size=12000000 -compression_type=$COMP -format_version=7; done
```
And test
```
for COMP in none
snappy zstd none; do echo $COMP; (for I in `seq 1 8`; do ./db_bench -readonly -db=/dev/shm/dbbench
-7-$COMP --benchmarks=readrandom -num=10000000 -duration=20 -threads=8 2>&1 | grep micros/op; done
) | awk '{n++; sum += $5;} END { print int(sum / n); }'; done
```

Ops/sec, Before -> After (both fv=6)
none
1491732 -> 1500209 (+0.6%)
snappy
1157216 -> 1169202 (+1.0%)
zstd
695414 -> 703719 (+1.2%)
none (again)
1491787 -> 1528789 (+2.4%)

Ops/sec, Before fv=6 -> After fv=7:
none
1492278 -> 1508668 (+1.1%)
snappy
1140769 -> 1152613 (+1.0%)
zstd
696437 -> 696511 (+0.0%)
none (again)
1500585 -> 1512037 (+0.7%)

Overall, I think we can take the read CPU improvement in exchange for the hit (in some cases) on background write CPU

Reviewed By: hx235

Differential Revision: D76520739

Pulled By: pdillinger

fbshipit-source-id: e73bd72502ff85c8779cba313f26f7d1fd50be3a
2025-06-16 14:19:03 -07:00

734 lines
27 KiB
C++

// 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).
#include "rocksdb/configurable.h"
#include "logging/logging.h"
#include "options/configurable_helper.h"
#include "options/options_helper.h"
#include "rocksdb/customizable.h"
#include "rocksdb/status.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_type.h"
#include "util/coding.h"
#include "util/string_util.h"
namespace ROCKSDB_NAMESPACE {
namespace {
intptr_t GetOffset(const Configurable* holder, void* field) {
return reinterpret_cast<intptr_t>(field) -
reinterpret_cast<intptr_t>(static_cast<const void*>(holder));
}
void* ApplyOffset(const Configurable* holder, intptr_t offset) {
return reinterpret_cast<void*>(
reinterpret_cast<intptr_t>(static_cast<const void*>(holder)) + offset);
}
} // namespace
void Configurable::RegisterOptions(
const std::string& name, void* opt_ptr,
const std::unordered_map<std::string, OptionTypeInfo>* type_map) {
RegisteredOptions opts;
opts.name = name;
opts.type_map = type_map;
opts.opt_offset = GetOffset(this, opt_ptr);
options_.emplace_back(opts);
}
//*************************************************************************
//
// Methods for Initializing and Validating Configurable Objects
//
//*************************************************************************
Status Configurable::PrepareOptions(const ConfigOptions& opts) {
// We ignore the invoke_prepare_options here intentionally,
// as if you are here, you must have called PrepareOptions explicitly.
Status status = Status::OK();
for (const auto& opt_iter : options_) {
if (opt_iter.type_map != nullptr) {
for (const auto& map_iter : *(opt_iter.type_map)) {
auto& opt_info = map_iter.second;
if (opt_info.ShouldPrepare()) {
status = opt_info.Prepare(opts, map_iter.first,
ApplyOffset(this, opt_iter.opt_offset));
if (!status.ok()) {
return status;
}
}
}
}
}
return status;
}
Status Configurable::ValidateOptions(const DBOptions& db_opts,
const ColumnFamilyOptions& cf_opts) const {
Status status;
for (const auto& opt_iter : options_) {
if (opt_iter.type_map != nullptr) {
for (const auto& map_iter : *(opt_iter.type_map)) {
auto& opt_info = map_iter.second;
if (opt_info.ShouldValidate()) {
status = opt_info.Validate(db_opts, cf_opts, map_iter.first,
ApplyOffset(this, opt_iter.opt_offset));
if (!status.ok()) {
return status;
}
}
}
}
}
return status;
}
/*********************************************************************************/
/* */
/* Methods for Retrieving Options from Configurables */
/* */
/*********************************************************************************/
const void* Configurable::GetOptionsPtr(const std::string& name) const {
for (const auto& o : options_) {
if (o.name == name) {
return ApplyOffset(this, o.opt_offset);
}
}
return nullptr;
}
std::string Configurable::GetOptionName(const std::string& opt_name) const {
return opt_name;
}
const OptionTypeInfo* ConfigurableHelper::FindOption(
const Configurable& configurable, const std::string& short_name,
std::string* opt_name, void** opt_ptr) {
for (const auto& iter : configurable.options_) {
if (iter.type_map != nullptr) {
const auto opt_info =
OptionTypeInfo::Find(short_name, *(iter.type_map), opt_name);
if (opt_info != nullptr) {
*opt_ptr = ApplyOffset(&configurable, iter.opt_offset);
return opt_info;
}
}
}
return nullptr;
}
//*************************************************************************
//
// Methods for Configuring Options from Strings/Name-Value Pairs/Maps
//
//*************************************************************************
Status Configurable::ConfigureFromMap(
const ConfigOptions& config_options,
const std::unordered_map<std::string, std::string>& opts_map) {
Status s = ConfigureFromMap(config_options, opts_map, nullptr);
return s;
}
Status Configurable::ConfigureFromMap(
const ConfigOptions& config_options,
const std::unordered_map<std::string, std::string>& opts_map,
std::unordered_map<std::string, std::string>* unused) {
return ConfigureOptions(config_options, opts_map, unused);
}
Status Configurable::ConfigureOptions(
const ConfigOptions& config_options,
const std::unordered_map<std::string, std::string>& opts_map,
std::unordered_map<std::string, std::string>* unused) {
std::string curr_opts;
Status s;
if (!opts_map.empty()) {
// There are options in the map.
// Save the current configuration in curr_opts and then configure the
// options, but do not prepare them now. We will do all the prepare when
// the configuration is complete.
ConfigOptions copy = config_options;
copy.invoke_prepare_options = false;
if (!config_options.ignore_unknown_options) {
// If we are not ignoring unused, get the defaults in case we need to
// reset
copy.depth = ConfigOptions::kDepthDetailed;
copy.delimiter = "; ";
GetOptionString(copy, &curr_opts).PermitUncheckedError();
}
s = ConfigurableHelper::ConfigureOptions(copy, *this, opts_map, unused);
}
if (config_options.invoke_prepare_options && s.ok()) {
s = PrepareOptions(config_options);
}
if (!s.ok() && !curr_opts.empty()) {
ConfigOptions reset = config_options;
reset.ignore_unknown_options = true;
reset.invoke_prepare_options = true;
reset.ignore_unsupported_options = true;
// There are some options to reset from this current error
ConfigureFromString(reset, curr_opts).PermitUncheckedError();
}
return s;
}
Status Configurable::ParseStringOptions(const ConfigOptions& /*config_options*/,
const std::string& /*opts_str*/) {
return Status::OK();
}
Status Configurable::ConfigureFromString(const ConfigOptions& config_options,
const std::string& opts_str) {
Status s;
if (!opts_str.empty()) {
if (opts_str.find(';') != std::string::npos ||
opts_str.find('=') != std::string::npos) {
std::unordered_map<std::string, std::string> opt_map;
s = StringToMap(opts_str, &opt_map);
if (s.ok()) {
s = ConfigureFromMap(config_options, opt_map, nullptr);
}
} else {
s = ParseStringOptions(config_options, opts_str);
if (s.ok() && config_options.invoke_prepare_options) {
s = PrepareOptions(config_options);
}
}
} else if (config_options.invoke_prepare_options) {
s = PrepareOptions(config_options);
} else {
s = Status::OK();
}
return s;
}
/**
* Sets the value of the named property to the input value, returning OK on
* succcess.
*/
Status Configurable::ConfigureOption(const ConfigOptions& config_options,
const std::string& name,
const std::string& value) {
return ConfigurableHelper::ConfigureSingleOption(config_options, *this, name,
value);
}
/**
* Looks for the named option amongst the options for this type and sets
* the value for it to be the input value.
* If the name was found, found_option will be set to true and the resulting
* status should be returned.
*/
Status Configurable::ParseOption(const ConfigOptions& config_options,
const OptionTypeInfo& opt_info,
const std::string& opt_name,
const std::string& opt_value, void* opt_ptr) {
if (opt_info.IsMutable()) {
if (config_options.mutable_options_only) {
// This option is mutable. Treat all of its children as mutable as well
ConfigOptions copy = config_options;
copy.mutable_options_only = false;
return opt_info.Parse(copy, opt_name, opt_value, opt_ptr);
} else {
return opt_info.Parse(config_options, opt_name, opt_value, opt_ptr);
}
} else if (config_options.mutable_options_only) {
return Status::InvalidArgument("Option not changeable: " + opt_name);
} else {
return opt_info.Parse(config_options, opt_name, opt_value, opt_ptr);
}
}
Status ConfigurableHelper::ConfigureOptions(
const ConfigOptions& config_options, Configurable& configurable,
const std::unordered_map<std::string, std::string>& opts_map,
std::unordered_map<std::string, std::string>* unused) {
std::unordered_map<std::string, std::string> remaining = opts_map;
Status s = Status::OK();
if (!opts_map.empty()) {
for (const auto& iter : configurable.options_) {
if (iter.type_map != nullptr) {
s = ConfigureSomeOptions(config_options, configurable, *(iter.type_map),
&remaining,
ApplyOffset(&configurable, iter.opt_offset));
if (remaining.empty()) { // Are there more options left?
break;
} else if (!s.ok()) {
break;
}
}
}
}
if (unused != nullptr && !remaining.empty()) {
unused->insert(remaining.begin(), remaining.end());
}
if (config_options.ignore_unknown_options) {
s = Status::OK();
} else if (s.ok() && unused == nullptr && !remaining.empty()) {
s = Status::NotFound("Extra option not recognized",
remaining.begin()->first);
}
return s;
}
/**
* Updates the object with the named-value property values, returning OK on
* succcess. Any properties that were found are removed from the options list;
* upon return only options that were not found in this opt_map remain.
* Returns:
* - OK if ignore_unknown_options is set
* - InvalidArgument, if any option was invalid
* - NotSupported, if any option is unsupported and ignore_unsupported_options
is OFF
* - OK, if no option was invalid or not supported (or ignored)
*/
Status ConfigurableHelper::ConfigureSomeOptions(
const ConfigOptions& config_options, Configurable& configurable,
const std::unordered_map<std::string, OptionTypeInfo>& type_map,
std::unordered_map<std::string, std::string>* options, void* opt_ptr) {
Status result = Status::OK(); // The last non-OK result (if any)
Status notsup = Status::OK(); // The last NotSupported result (if any)
std::string elem_name;
int found = 1;
std::unordered_set<std::string> unsupported;
// While there are unused properties and we processed at least one,
// go through the remaining unused properties and attempt to configure them.
while (found > 0 && !options->empty()) {
found = 0;
notsup = Status::OK();
for (auto it = options->begin(); it != options->end();) {
const std::string& opt_name = configurable.GetOptionName(it->first);
const std::string& opt_value = it->second;
const auto opt_info =
OptionTypeInfo::Find(opt_name, type_map, &elem_name);
if (opt_info == nullptr) { // Did not find the option. Skip it
++it;
} else {
Status s = ConfigureOption(config_options, configurable, *opt_info,
opt_name, elem_name, opt_value, opt_ptr);
if (s.IsNotFound()) {
++it;
} else if (s.IsNotSupported()) {
notsup = s;
unsupported.insert(it->first);
++it; // Skip it for now
} else {
found++;
it = options->erase(it);
if (!s.ok()) {
result = s;
}
}
}
} // End for all remaining options
} // End while found one or options remain
// Now that we have been through the list, remove any unsupported
for (const auto& u : unsupported) {
auto it = options->find(u);
if (it != options->end()) {
options->erase(it);
}
}
if (config_options.ignore_unknown_options) {
if (!result.ok()) {
result.PermitUncheckedError();
}
if (!notsup.ok()) {
notsup.PermitUncheckedError();
}
return Status::OK();
} else if (!result.ok()) {
if (!notsup.ok()) {
notsup.PermitUncheckedError();
}
return result;
} else if (config_options.ignore_unsupported_options) {
if (!notsup.ok()) {
notsup.PermitUncheckedError();
}
return Status::OK();
} else {
return notsup;
}
}
Status ConfigurableHelper::ConfigureSingleOption(
const ConfigOptions& config_options, Configurable& configurable,
const std::string& name, const std::string& value) {
const std::string& opt_name = configurable.GetOptionName(name);
std::string elem_name;
void* opt_ptr = nullptr;
const auto opt_info =
FindOption(configurable, opt_name, &elem_name, &opt_ptr);
if (opt_info == nullptr) {
return Status::NotFound("Could not find option", name);
} else {
return ConfigureOption(config_options, configurable, *opt_info, opt_name,
elem_name, value, opt_ptr);
}
}
Status ConfigurableHelper::ConfigureCustomizableOption(
const ConfigOptions& config_options, Configurable& configurable,
const OptionTypeInfo& opt_info, const std::string& opt_name,
const std::string& name, const std::string& value, void* opt_ptr) {
Customizable* custom = opt_info.AsRawPointer<Customizable>(opt_ptr);
ConfigOptions copy = config_options;
if (opt_info.IsMutable()) {
// This option is mutable. Pass that property on to any subsequent calls
copy.mutable_options_only = false;
}
if (opt_info.IsMutable() || !config_options.mutable_options_only) {
// Either the option is mutable, or we are processing all of the options
if (opt_name == name || name == OptionTypeInfo::kIdPropName() ||
EndsWith(opt_name, OptionTypeInfo::kIdPropSuffix())) {
return configurable.ParseOption(copy, opt_info, name, value, opt_ptr);
} else if (value.empty()) {
return Status::OK();
} else if (custom == nullptr || !StartsWith(name, custom->GetId() + ".")) {
return configurable.ParseOption(copy, opt_info, name, value, opt_ptr);
} else if (value.find('=') != std::string::npos) {
return custom->ConfigureFromString(copy, value);
} else {
return custom->ConfigureOption(copy, name, value);
}
} else {
// We are processing immutable options, which means that we cannot change
// the Customizable object itself, but could change its mutable properties.
// Check to make sure that nothing is trying to change the Customizable
if (custom == nullptr) {
// We do not have a Customizable to configure. This is OK if the
// value is empty (nothing being configured) but an error otherwise
if (value.empty()) {
return Status::OK();
} else {
return Status::InvalidArgument("Option not changeable: " + opt_name);
}
} else if (EndsWith(opt_name, OptionTypeInfo::kIdPropSuffix()) ||
name == OptionTypeInfo::kIdPropName()) {
// We have a property of the form "id=value" or "table.id=value"
// This is OK if we ID/value matches the current customizable object
if (custom->GetId() == value) {
return Status::OK();
} else {
return Status::InvalidArgument("Option not changeable: " + opt_name);
}
} else if (opt_name == name) {
// The properties are of one of forms:
// name = { id = id; prop1 = value1; ... }
// name = { prop1=value1; prop2=value2; ... }
// name = ID
// Convert the value to a map and extract the ID
// If the ID does not match that of the current customizable, return an
// error. Otherwise, update the current customizable via the properties
// map
std::unordered_map<std::string, std::string> props;
std::string id;
Status s =
Configurable::GetOptionsMap(value, custom->GetId(), &id, &props);
if (!s.ok()) {
return s;
} else if (custom->GetId() != id) {
return Status::InvalidArgument("Option not changeable: " + opt_name);
} else if (props.empty()) {
return Status::OK();
} else {
return custom->ConfigureFromMap(copy, props);
}
} else {
// Attempting to configure one of the properties of the customizable
// Let it through
return custom->ConfigureOption(copy, name, value);
}
}
}
Status ConfigurableHelper::ConfigureOption(
const ConfigOptions& config_options, Configurable& configurable,
const OptionTypeInfo& opt_info, const std::string& opt_name,
const std::string& name, const std::string& value, void* opt_ptr) {
if (opt_info.IsCustomizable()) {
return ConfigureCustomizableOption(config_options, configurable, opt_info,
opt_name, name, value, opt_ptr);
} else if (opt_name == name) {
return configurable.ParseOption(config_options, opt_info, opt_name, value,
opt_ptr);
} else if (opt_info.IsStruct() || opt_info.IsConfigurable()) {
return configurable.ParseOption(config_options, opt_info, name, value,
opt_ptr);
} else {
return Status::NotFound("Unknown how to configure option", name);
}
}
//*******************************************************************************
//
// Methods for Converting Options into strings
//
//*******************************************************************************
Status Configurable::GetOptionString(const ConfigOptions& config_options,
std::string* result) const {
assert(result);
result->clear();
return ConfigurableHelper::SerializeOptions(config_options, *this, "",
result);
}
std::string Configurable::ToString(const ConfigOptions& config_options,
const std::string& prefix) const {
std::string result = SerializeOptions(config_options, prefix);
if (result.empty() || result.find('=') == std::string::npos) {
return result;
} else {
return "{" + result + "}";
}
}
std::string Configurable::SerializeOptions(const ConfigOptions& config_options,
const std::string& header) const {
std::string result;
Status s = ConfigurableHelper::SerializeOptions(config_options, *this, header,
&result);
assert(s.ok());
return result;
}
Status Configurable::GetOption(const ConfigOptions& config_options,
const std::string& name,
std::string* value) const {
return ConfigurableHelper::GetOption(config_options, *this,
GetOptionName(name), value);
}
Status ConfigurableHelper::GetOption(const ConfigOptions& config_options,
const Configurable& configurable,
const std::string& short_name,
std::string* value) {
// Look for option directly
assert(value);
value->clear();
std::string opt_name;
void* opt_ptr = nullptr;
const auto opt_info =
FindOption(configurable, short_name, &opt_name, &opt_ptr);
if (opt_info != nullptr) {
ConfigOptions embedded = config_options;
embedded.delimiter = ";";
if (short_name == opt_name) {
return opt_info->Serialize(embedded, opt_name, opt_ptr, value);
} else if (opt_info->IsStruct()) {
return opt_info->Serialize(embedded, opt_name, opt_ptr, value);
} else if (opt_info->IsConfigurable()) {
auto const* config = opt_info->AsRawPointer<Configurable>(opt_ptr);
if (config != nullptr) {
return config->GetOption(embedded, opt_name, value);
}
}
}
return Status::NotFound("Cannot find option: ", short_name);
}
Status ConfigurableHelper::SerializeOptions(const ConfigOptions& config_options,
const Configurable& configurable,
const std::string& prefix,
std::string* result) {
assert(result);
for (auto const& opt_iter : configurable.options_) {
if (opt_iter.type_map != nullptr) {
for (const auto& map_iter : *(opt_iter.type_map)) {
const auto& opt_name = map_iter.first;
const auto& opt_info = map_iter.second;
if (opt_info.ShouldSerialize()) {
std::string value;
Status s;
void* opt_ptr = ApplyOffset(&configurable, opt_iter.opt_offset);
if (!config_options.mutable_options_only) {
s = opt_info.Serialize(config_options, prefix + opt_name, opt_ptr,
&value);
} else if (opt_info.IsMutable()) {
ConfigOptions copy = config_options;
copy.mutable_options_only = false;
s = opt_info.Serialize(copy, prefix + opt_name, opt_ptr, &value);
} else if (opt_info.IsConfigurable()) {
// If it is a Configurable and we are either printing all of the
// details or not printing only the name, this option should be
// included in the list
if (config_options.IsDetailed() ||
!opt_info.IsEnabled(OptionTypeFlags::kStringNameOnly)) {
s = opt_info.Serialize(config_options, prefix + opt_name, opt_ptr,
&value);
}
}
if (!s.ok()) {
return s;
} else if (!value.empty()) {
// <prefix><opt_name>=<value><delimiter>
result->append(prefix + opt_name + "=" + value +
config_options.delimiter);
}
}
}
}
}
return Status::OK();
}
//********************************************************************************
//
// Methods for listing the options from Configurables
//
//********************************************************************************
Status Configurable::GetOptionNames(
const ConfigOptions& config_options,
std::unordered_set<std::string>* result) const {
assert(result);
return ConfigurableHelper::ListOptions(config_options, *this, "", result);
}
Status ConfigurableHelper::ListOptions(
const ConfigOptions& config_options, const Configurable& configurable,
const std::string& prefix, std::unordered_set<std::string>* result) {
Status status;
for (auto const& opt_iter : configurable.options_) {
if (opt_iter.type_map != nullptr) {
for (const auto& map_iter : *(opt_iter.type_map)) {
const auto& opt_name = map_iter.first;
const auto& opt_info = map_iter.second;
// If the option is no longer used in rocksdb and marked as deprecated,
// we skip it in the serialization.
if (!opt_info.IsDeprecated() && !opt_info.IsAlias()) {
if (!config_options.mutable_options_only) {
result->emplace(prefix + opt_name);
} else if (opt_info.IsMutable()) {
result->emplace(prefix + opt_name);
}
}
}
}
}
return status;
}
//*******************************************************************************
//
// Methods for Comparing Configurables
//
//*******************************************************************************
bool Configurable::AreEquivalent(const ConfigOptions& config_options,
const Configurable* other,
std::string* name) const {
assert(name);
name->clear();
if (this == other || config_options.IsCheckDisabled()) {
return true;
} else if (other != nullptr) {
return ConfigurableHelper::AreEquivalent(config_options, *this, *other,
name);
} else {
return false;
}
}
bool Configurable::OptionsAreEqual(const ConfigOptions& config_options,
const OptionTypeInfo& opt_info,
const std::string& opt_name,
const void* const this_ptr,
const void* const that_ptr,
std::string* mismatch) const {
if (opt_info.AreEqual(config_options, opt_name, this_ptr, that_ptr,
mismatch)) {
return true;
} else if (opt_info.AreEqualByName(config_options, opt_name, this_ptr,
that_ptr)) {
*mismatch = "";
return true;
} else {
return false;
}
}
bool ConfigurableHelper::AreEquivalent(const ConfigOptions& config_options,
const Configurable& this_one,
const Configurable& that_one,
std::string* mismatch) {
assert(mismatch != nullptr);
for (auto const& o : this_one.options_) {
const auto this_offset = this_one.GetOptionsPtr(o.name);
const auto that_offset = that_one.GetOptionsPtr(o.name);
if (this_offset != that_offset) {
if (this_offset == nullptr || that_offset == nullptr) {
return false;
} else if (o.type_map != nullptr) {
for (const auto& map_iter : *(o.type_map)) {
const auto& opt_info = map_iter.second;
if (config_options.IsCheckEnabled(opt_info.GetSanityLevel())) {
if (!config_options.mutable_options_only) {
if (!this_one.OptionsAreEqual(config_options, opt_info,
map_iter.first, this_offset,
that_offset, mismatch)) {
return false;
}
} else if (opt_info.IsMutable()) {
ConfigOptions copy = config_options;
copy.mutable_options_only = false;
if (!this_one.OptionsAreEqual(copy, opt_info, map_iter.first,
this_offset, that_offset,
mismatch)) {
return false;
}
}
}
}
}
}
}
return true;
}
Status Configurable::GetOptionsMap(
const std::string& value, const std::string& default_id, std::string* id,
std::unordered_map<std::string, std::string>* props) {
assert(id);
assert(props);
Status status;
if (value.empty() || value == kNullptrString) {
*id = default_id;
} else if (value.find('=') == std::string::npos) {
*id = value;
} else {
status = StringToMap(value, props);
if (!status.ok()) { // There was an error creating the map.
*id = value; // Treat the value as id
props->clear(); // Clear the properties
status = Status::OK(); // and ignore the error
} else {
auto iter = props->find(OptionTypeInfo::kIdPropName());
if (iter != props->end()) {
*id = iter->second;
props->erase(iter);
if (*id == kNullptrString) {
id->clear();
}
} else if (!default_id.empty()) {
*id = default_id;
} else { // No id property and no default
*id = value; // Treat the value as id
props->clear(); // Clear the properties
}
}
}
return status;
}
} // namespace ROCKSDB_NAMESPACE