261 lines
7.9 KiB
C++
261 lines
7.9 KiB
C++
|
/*
|
||
|
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
|
||
|
*
|
||
|
* Use of this source code is governed by a BSD-style license
|
||
|
* that can be found in the LICENSE file in the root of the source
|
||
|
* tree. An additional intellectual property rights grant can be found
|
||
|
* in the file PATENTS. All contributing project authors may
|
||
|
* be found in the AUTHORS file in the root of the source tree.
|
||
|
*/
|
||
|
#include "rtc_base/experiments/field_trial_parser.h"
|
||
|
|
||
|
#include <inttypes.h>
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <map>
|
||
|
#include <type_traits>
|
||
|
#include <utility>
|
||
|
|
||
|
#include "absl/strings/string_view.h"
|
||
|
#include "rtc_base/checks.h"
|
||
|
#include "rtc_base/logging.h"
|
||
|
#include "rtc_base/numerics/safe_conversions.h"
|
||
|
|
||
|
namespace webrtc {
|
||
|
|
||
|
FieldTrialParameterInterface::FieldTrialParameterInterface(
|
||
|
absl::string_view key)
|
||
|
: key_(key) {}
|
||
|
FieldTrialParameterInterface::~FieldTrialParameterInterface() {
|
||
|
RTC_DCHECK(used_) << "Field trial parameter with key: '" << key_
|
||
|
<< "' never used.";
|
||
|
}
|
||
|
|
||
|
void ParseFieldTrial(
|
||
|
std::initializer_list<FieldTrialParameterInterface*> fields,
|
||
|
absl::string_view trial_string) {
|
||
|
std::map<absl::string_view, FieldTrialParameterInterface*> field_map;
|
||
|
FieldTrialParameterInterface* keyless_field = nullptr;
|
||
|
for (FieldTrialParameterInterface* field : fields) {
|
||
|
field->MarkAsUsed();
|
||
|
if (!field->sub_parameters_.empty()) {
|
||
|
for (FieldTrialParameterInterface* sub_field : field->sub_parameters_) {
|
||
|
RTC_DCHECK(!sub_field->key_.empty());
|
||
|
sub_field->MarkAsUsed();
|
||
|
field_map[sub_field->key_] = sub_field;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (field->key_.empty()) {
|
||
|
RTC_DCHECK(!keyless_field);
|
||
|
keyless_field = field;
|
||
|
} else {
|
||
|
field_map[field->key_] = field;
|
||
|
}
|
||
|
}
|
||
|
bool logged_unknown_key = false;
|
||
|
|
||
|
absl::string_view tail = trial_string;
|
||
|
while (!tail.empty()) {
|
||
|
size_t key_end = tail.find_first_of(",:");
|
||
|
absl::string_view key = tail.substr(0, key_end);
|
||
|
absl::optional<std::string> opt_value;
|
||
|
if (key_end == absl::string_view::npos) {
|
||
|
tail = "";
|
||
|
} else if (tail[key_end] == ':') {
|
||
|
tail = tail.substr(key_end + 1);
|
||
|
size_t value_end = tail.find(',');
|
||
|
opt_value.emplace(tail.substr(0, value_end));
|
||
|
if (value_end == absl::string_view::npos) {
|
||
|
tail = "";
|
||
|
} else {
|
||
|
tail = tail.substr(value_end + 1);
|
||
|
}
|
||
|
} else {
|
||
|
RTC_DCHECK_EQ(tail[key_end], ',');
|
||
|
tail = tail.substr(key_end + 1);
|
||
|
}
|
||
|
|
||
|
auto field = field_map.find(key);
|
||
|
if (field != field_map.end()) {
|
||
|
if (!field->second->Parse(std::move(opt_value))) {
|
||
|
RTC_LOG(LS_WARNING) << "Failed to read field with key: '" << key
|
||
|
<< "' in trial: \"" << trial_string << "\"";
|
||
|
}
|
||
|
} else if (!opt_value && keyless_field && !key.empty()) {
|
||
|
if (!keyless_field->Parse(std::string(key))) {
|
||
|
RTC_LOG(LS_WARNING) << "Failed to read empty key field with value '"
|
||
|
<< key << "' in trial: \"" << trial_string << "\"";
|
||
|
}
|
||
|
} else if (key.empty() || key[0] != '_') {
|
||
|
// "_" is be used to prefix keys that are part of the string for
|
||
|
// debugging purposes but not neccessarily used.
|
||
|
// e.g. WebRTC-Experiment/param: value, _DebuggingString
|
||
|
if (!logged_unknown_key) {
|
||
|
RTC_LOG(LS_INFO) << "No field with key: '" << key
|
||
|
<< "' (found in trial: \"" << trial_string << "\")";
|
||
|
std::string valid_keys;
|
||
|
for (const auto& f : field_map) {
|
||
|
valid_keys.append(f.first.data(), f.first.size());
|
||
|
valid_keys += ", ";
|
||
|
}
|
||
|
RTC_LOG(LS_INFO) << "Valid keys are: " << valid_keys;
|
||
|
logged_unknown_key = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (FieldTrialParameterInterface* field : fields) {
|
||
|
field->ParseDone();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<bool> ParseTypedParameter<bool>(absl::string_view str) {
|
||
|
if (str == "true" || str == "1") {
|
||
|
return true;
|
||
|
} else if (str == "false" || str == "0") {
|
||
|
return false;
|
||
|
}
|
||
|
return absl::nullopt;
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<double> ParseTypedParameter<double>(absl::string_view str) {
|
||
|
double value;
|
||
|
char unit[2]{0, 0};
|
||
|
if (sscanf(std::string(str).c_str(), "%lf%1s", &value, unit) >= 1) {
|
||
|
if (unit[0] == '%')
|
||
|
return value / 100;
|
||
|
return value;
|
||
|
} else {
|
||
|
return absl::nullopt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<int> ParseTypedParameter<int>(absl::string_view str) {
|
||
|
int64_t value;
|
||
|
if (sscanf(std::string(str).c_str(), "%" SCNd64, &value) == 1) {
|
||
|
if (rtc::IsValueInRangeForNumericType<int, int64_t>(value)) {
|
||
|
return static_cast<int>(value);
|
||
|
}
|
||
|
}
|
||
|
return absl::nullopt;
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<unsigned> ParseTypedParameter<unsigned>(absl::string_view str) {
|
||
|
int64_t value;
|
||
|
if (sscanf(std::string(str).c_str(), "%" SCNd64, &value) == 1) {
|
||
|
if (rtc::IsValueInRangeForNumericType<unsigned, int64_t>(value)) {
|
||
|
return static_cast<unsigned>(value);
|
||
|
}
|
||
|
}
|
||
|
return absl::nullopt;
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<std::string> ParseTypedParameter<std::string>(
|
||
|
absl::string_view str) {
|
||
|
return std::string(str);
|
||
|
}
|
||
|
|
||
|
template <>
|
||
|
absl::optional<absl::optional<bool>> ParseTypedParameter<absl::optional<bool>>(
|
||
|
absl::string_view str) {
|
||
|
return ParseOptionalParameter<bool>(str);
|
||
|
}
|
||
|
template <>
|
||
|
absl::optional<absl::optional<int>> ParseTypedParameter<absl::optional<int>>(
|
||
|
absl::string_view str) {
|
||
|
return ParseOptionalParameter<int>(str);
|
||
|
}
|
||
|
template <>
|
||
|
absl::optional<absl::optional<unsigned>>
|
||
|
ParseTypedParameter<absl::optional<unsigned>>(absl::string_view str) {
|
||
|
return ParseOptionalParameter<unsigned>(str);
|
||
|
}
|
||
|
template <>
|
||
|
absl::optional<absl::optional<double>>
|
||
|
ParseTypedParameter<absl::optional<double>>(absl::string_view str) {
|
||
|
return ParseOptionalParameter<double>(str);
|
||
|
}
|
||
|
|
||
|
FieldTrialFlag::FieldTrialFlag(absl::string_view key)
|
||
|
: FieldTrialFlag(key, false) {}
|
||
|
|
||
|
FieldTrialFlag::FieldTrialFlag(absl::string_view key, bool default_value)
|
||
|
: FieldTrialParameterInterface(key), value_(default_value) {}
|
||
|
|
||
|
bool FieldTrialFlag::Get() const {
|
||
|
return value_;
|
||
|
}
|
||
|
|
||
|
webrtc::FieldTrialFlag::operator bool() const {
|
||
|
return value_;
|
||
|
}
|
||
|
|
||
|
bool FieldTrialFlag::Parse(absl::optional<std::string> str_value) {
|
||
|
// Only set the flag if there is no argument provided.
|
||
|
if (str_value) {
|
||
|
absl::optional<bool> opt_value = ParseTypedParameter<bool>(*str_value);
|
||
|
if (!opt_value)
|
||
|
return false;
|
||
|
value_ = *opt_value;
|
||
|
} else {
|
||
|
value_ = true;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
AbstractFieldTrialEnum::AbstractFieldTrialEnum(
|
||
|
absl::string_view key,
|
||
|
int default_value,
|
||
|
std::map<std::string, int> mapping)
|
||
|
: FieldTrialParameterInterface(key),
|
||
|
value_(default_value),
|
||
|
enum_mapping_(mapping) {
|
||
|
for (auto& key_val : enum_mapping_)
|
||
|
valid_values_.insert(key_val.second);
|
||
|
}
|
||
|
AbstractFieldTrialEnum::AbstractFieldTrialEnum(const AbstractFieldTrialEnum&) =
|
||
|
default;
|
||
|
AbstractFieldTrialEnum::~AbstractFieldTrialEnum() = default;
|
||
|
|
||
|
bool AbstractFieldTrialEnum::Parse(absl::optional<std::string> str_value) {
|
||
|
if (str_value) {
|
||
|
auto it = enum_mapping_.find(*str_value);
|
||
|
if (it != enum_mapping_.end()) {
|
||
|
value_ = it->second;
|
||
|
return true;
|
||
|
}
|
||
|
absl::optional<int> value = ParseTypedParameter<int>(*str_value);
|
||
|
if (value.has_value() &&
|
||
|
(valid_values_.find(*value) != valid_values_.end())) {
|
||
|
value_ = *value;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
template class FieldTrialParameter<bool>;
|
||
|
template class FieldTrialParameter<double>;
|
||
|
template class FieldTrialParameter<int>;
|
||
|
template class FieldTrialParameter<unsigned>;
|
||
|
template class FieldTrialParameter<std::string>;
|
||
|
|
||
|
template class FieldTrialConstrained<double>;
|
||
|
template class FieldTrialConstrained<int>;
|
||
|
template class FieldTrialConstrained<unsigned>;
|
||
|
|
||
|
template class FieldTrialOptional<double>;
|
||
|
template class FieldTrialOptional<int>;
|
||
|
template class FieldTrialOptional<unsigned>;
|
||
|
template class FieldTrialOptional<bool>;
|
||
|
template class FieldTrialOptional<std::string>;
|
||
|
|
||
|
} // namespace webrtc
|