Adds field trial parser.

Bug: webrtc:9346
Change-Id: Ibd07a1753feaa40d4be4d465d61f55bc8a8a9325
Reviewed-on: https://webrtc-review.googlesource.com/80263
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23615}
This commit is contained in:
Sebastian Jansson 2018-06-14 16:47:42 +02:00 committed by Commit Bot
parent 7c32c866c0
commit 9eb38866cd
7 changed files with 615 additions and 0 deletions

View File

@ -20,6 +20,23 @@ rtc_static_library("alr_experiment") {
]
}
rtc_static_library("field_trial_parser") {
sources = [
"field_trial_parser.cc",
"field_trial_parser.h",
"field_trial_units.cc",
"field_trial_units.h",
]
deps = [
"../:rtc_base_approved",
"../../api:optional",
"../../api/units:data_rate",
"../../api/units:data_size",
"../../api/units:time_delta",
"../../system_wrappers:field_trial_api",
]
}
rtc_static_library("congestion_controller_experiment") {
sources = [
"congestion_controller_experiment.cc",
@ -52,13 +69,17 @@ if (rtc_include_tests) {
sources = [
"congestion_controller_experiment_unittest.cc",
"field_trial_parser_unittest.cc",
"field_trial_units_unittest.cc",
"quality_scaling_experiment_unittest.cc",
]
deps = [
":congestion_controller_experiment",
":field_trial_parser",
":quality_scaling_experiment",
"../:rtc_base_tests_main",
"../:rtc_base_tests_utils",
"../../system_wrappers:field_trial_api",
"../../test:field_trial",
]
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2018 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 <algorithm>
#include <map>
#include <type_traits>
#include <utility>
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
int FindOrEnd(std::string str, size_t start, char delimiter) {
size_t pos = str.find(delimiter, start);
pos = (pos == std::string::npos) ? str.length() : pos;
return static_cast<int>(pos);
}
} // namespace
FieldTrialParameterInterface::FieldTrialParameterInterface(std::string key)
: key_(key) {}
FieldTrialParameterInterface::~FieldTrialParameterInterface() = default;
std::string FieldTrialParameterInterface::Key() const {
return key_;
}
void ParseFieldTrial(
std::initializer_list<FieldTrialParameterInterface*> fields,
std::string trial_string) {
std::map<std::string, FieldTrialParameterInterface*> field_map;
for (FieldTrialParameterInterface* field : fields) {
field_map[field->Key()] = field;
}
size_t i = 0;
while (i < trial_string.length()) {
int val_end = FindOrEnd(trial_string, i, ',');
int colon_pos = FindOrEnd(trial_string, i, ':');
int key_end = std::min(val_end, colon_pos);
int val_begin = key_end + 1;
std::string key = trial_string.substr(i, key_end - i);
rtc::Optional<std::string> opt_value;
if (val_end >= val_begin)
opt_value = trial_string.substr(val_begin, val_end - val_begin);
i = val_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 {
RTC_LOG(LS_INFO) << "No field with key: '" << key
<< "' (found in trial: \"" << trial_string << "\")";
}
}
}
template <>
rtc::Optional<bool> ParseTypedParameter<bool>(std::string str) {
if (str == "true" || str == "1") {
return true;
} else if (str == "false" || str == "0") {
return false;
}
return rtc::nullopt;
}
template <>
rtc::Optional<double> ParseTypedParameter<double>(std::string str) {
double value;
if (sscanf(str.c_str(), "%lf", &value) == 1) {
return value;
} else {
return rtc::nullopt;
}
}
template <>
rtc::Optional<int> ParseTypedParameter<int>(std::string str) {
int value;
if (sscanf(str.c_str(), "%i", &value) == 1) {
return value;
} else {
return rtc::nullopt;
}
}
template <>
rtc::Optional<std::string> ParseTypedParameter<std::string>(std::string str) {
return std::move(str);
}
FieldTrialFlag::FieldTrialFlag(std::string key) : FieldTrialFlag(key, false) {}
FieldTrialFlag::FieldTrialFlag(std::string key, bool default_value)
: FieldTrialParameterInterface(key), value_(default_value) {}
bool FieldTrialFlag::Get() const {
return value_;
}
bool FieldTrialFlag::Parse(rtc::Optional<std::string> str_value) {
// Only set the flag if there is no argument provided.
if (str_value) {
rtc::Optional<bool> opt_value = ParseTypedParameter<bool>(*str_value);
if (!opt_value)
return false;
value_ = *opt_value;
} else {
value_ = true;
}
return true;
}
template class FieldTrialParameter<bool>;
template class FieldTrialParameter<double>;
template class FieldTrialParameter<int>;
template class FieldTrialParameter<std::string>;
template class FieldTrialOptional<double>;
template class FieldTrialOptional<int>;
template class FieldTrialOptional<bool>;
template class FieldTrialOptional<std::string>;
} // namespace webrtc

View File

@ -0,0 +1,146 @@
/*
* Copyright 2018 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.
*/
#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_
#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_
#include <stdint.h>
#include <initializer_list>
#include <string>
#include "api/optional.h"
// Field trial parser functionality. Provides funcitonality to parse field trial
// argument strings in key:value format. Each parameter is described using
// key:value, parameters are separated with a ,. Values can't include the comma
// character, since there's no quote facility. For most types, white space is
// ignored. Parameters are declared with a given type for which an
// implementation of ParseTypedParameter should be provided. The
// ParseTypedParameter implementation is given whatever is between the : and the
// ,. FieldTrialOptional will use nullopt if the key is provided without :.
// Example string: "my_optional,my_int:3,my_string:hello"
// For further description of usage and behavior, see the examples in the unit
// tests.
namespace webrtc {
class FieldTrialParameterInterface {
public:
virtual ~FieldTrialParameterInterface();
protected:
explicit FieldTrialParameterInterface(std::string key);
friend void ParseFieldTrial(
std::initializer_list<FieldTrialParameterInterface*> fields,
std::string raw_string);
virtual bool Parse(rtc::Optional<std::string> str_value) = 0;
std::string Key() const;
private:
const std::string key_;
};
// ParseFieldTrial function parses the given string and fills the given fields
// with extrated values if available.
void ParseFieldTrial(
std::initializer_list<FieldTrialParameterInterface*> fields,
std::string raw_string);
// Specialize this in code file for custom types. Should return rtc::nullopt if
// the given string cannot be properly parsed.
template <typename T>
rtc::Optional<T> ParseTypedParameter(std::string);
// This class uses the ParseTypedParameter function to implement a parameter
// implementation with an enforced default value.
template <typename T>
class FieldTrialParameter : public FieldTrialParameterInterface {
public:
FieldTrialParameter(std::string key, T default_value)
: FieldTrialParameterInterface(key), value_(default_value) {}
T Get() const { return value_; }
operator T() const { return Get(); }
protected:
bool Parse(rtc::Optional<std::string> str_value) override {
if (str_value) {
rtc::Optional<T> value = ParseTypedParameter<T>(*str_value);
if (value.has_value()) {
value_ = value.value();
return true;
}
}
return false;
}
private:
T value_;
};
// This class uses the ParseTypedParameter function to implement an optional
// parameter implementation that can default to rtc::nullopt.
template <typename T>
class FieldTrialOptional : public FieldTrialParameterInterface {
public:
explicit FieldTrialOptional(std::string key)
: FieldTrialParameterInterface(key) {}
FieldTrialOptional(std::string key, rtc::Optional<T> default_value)
: FieldTrialParameterInterface(key), value_(default_value) {}
rtc::Optional<T> Get() const { return value_; }
protected:
bool Parse(rtc::Optional<std::string> str_value) override {
if (str_value) {
rtc::Optional<T> value = ParseTypedParameter<T>(*str_value);
if (!value.has_value())
return false;
value_ = value.value();
} else {
value_ = rtc::nullopt;
}
return true;
}
private:
rtc::Optional<T> value_;
};
// Equivalent to a FieldTrialParameter<bool> in the case that both key and value
// are present. If key is missing, evaluates to false. If key is present, but no
// explicit value is provided, the flag evaluates to true.
class FieldTrialFlag : public FieldTrialParameterInterface {
public:
explicit FieldTrialFlag(std::string key);
FieldTrialFlag(std::string key, bool default_value);
bool Get() const;
protected:
bool Parse(rtc::Optional<std::string> str_value) override;
private:
bool value_;
};
// Accepts true, false, else parsed with sscanf %i, true if != 0.
extern template class FieldTrialParameter<bool>;
// Interpreted using sscanf %lf.
extern template class FieldTrialParameter<double>;
// Interpreted using sscanf %i.
extern template class FieldTrialParameter<int>;
// Using the given value as is.
extern template class FieldTrialParameter<std::string>;
extern template class FieldTrialOptional<double>;
extern template class FieldTrialOptional<int>;
extern template class FieldTrialOptional<bool>;
extern template class FieldTrialOptional<std::string>;
} // namespace webrtc
#endif // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_

View File

@ -0,0 +1,131 @@
/*
* Copyright 2018 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 "rtc_base/gunit.h"
#include "system_wrappers/include/field_trial.h"
#include "test/field_trial.h"
namespace webrtc {
namespace {
const char kDummyExperiment[] = "WebRTC-DummyExperiment";
struct DummyExperiment {
FieldTrialFlag enabled = FieldTrialFlag("Enabled");
FieldTrialParameter<double> factor = FieldTrialParameter<double>("f", 0.5);
FieldTrialParameter<int> retries = FieldTrialParameter<int>("r", 5);
FieldTrialParameter<bool> ping = FieldTrialParameter<bool>("p", 0);
FieldTrialParameter<std::string> hash =
FieldTrialParameter<std::string>("h", "a80");
explicit DummyExperiment(std::string field_trial) {
ParseFieldTrial({&enabled, &factor, &retries, &ping, &hash}, field_trial);
}
DummyExperiment() {
std::string trial_string = field_trial::FindFullName(kDummyExperiment);
ParseFieldTrial({&enabled, &factor, &retries, &ping, &hash}, trial_string);
}
};
enum class CustomEnum {
kDefault,
kRed,
kBlue,
};
} // namespace
// Providing a custom parser for an enum can make the trial string easier to
// read, but also adds more code and makes the string more verbose.
template <>
rtc::Optional<CustomEnum> ParseTypedParameter<CustomEnum>(std::string str) {
if (str == "default")
return CustomEnum::kDefault;
else if (str == "red")
return CustomEnum::kRed;
else if (str == "blue")
return CustomEnum::kBlue;
return rtc::nullopt;
}
TEST(FieldTrialParserTest, ParsesValidParameters) {
DummyExperiment exp("Enabled,f:-1.7,r:2,p:1,h:x7c");
EXPECT_TRUE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), -1.7);
EXPECT_EQ(exp.retries.Get(), 2);
EXPECT_EQ(exp.ping.Get(), true);
EXPECT_EQ(exp.hash.Get(), "x7c");
}
TEST(FieldTrialParserTest, InitializesFromFieldTrial) {
test::ScopedFieldTrials field_trials(
"WebRTC-OtherExperiment/Disabled/"
"WebRTC-DummyExperiment/Enabled,f:-1.7,r:2,p:1,h:x7c/"
"WebRTC-AnotherExperiment/Enabled,f:-3.1,otherstuff:beef/");
DummyExperiment exp;
EXPECT_TRUE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), -1.7);
EXPECT_EQ(exp.retries.Get(), 2);
EXPECT_EQ(exp.ping.Get(), true);
EXPECT_EQ(exp.hash.Get(), "x7c");
}
TEST(FieldTrialParserTest, UsesDefaults) {
DummyExperiment exp("");
EXPECT_FALSE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), 0.5);
EXPECT_EQ(exp.retries.Get(), 5);
EXPECT_EQ(exp.ping.Get(), false);
EXPECT_EQ(exp.hash.Get(), "a80");
}
TEST(FieldTrialParserTest, CanHandleMixedInput) {
DummyExperiment exp("p:true,h:,Enabled");
EXPECT_TRUE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), 0.5);
EXPECT_EQ(exp.retries.Get(), 5);
EXPECT_EQ(exp.ping.Get(), true);
EXPECT_EQ(exp.hash.Get(), "");
}
TEST(FieldTrialParserTest, IgnoresNewKey) {
DummyExperiment exp("Disabled,r:-11,foo");
EXPECT_FALSE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), 0.5);
EXPECT_EQ(exp.retries.Get(), -11);
}
TEST(FieldTrialParserTest, IgnoresInvalid) {
DummyExperiment exp("Enabled,f,p:,r:%,,:foo,h");
EXPECT_TRUE(exp.enabled.Get());
EXPECT_EQ(exp.factor.Get(), 0.5);
EXPECT_EQ(exp.retries.Get(), 5);
EXPECT_EQ(exp.ping.Get(), false);
EXPECT_EQ(exp.hash.Get(), "a80");
}
TEST(FieldTrialParserTest, ParsesOptionalParameters) {
FieldTrialOptional<int> max_count("c", rtc::nullopt);
ParseFieldTrial({&max_count}, "");
EXPECT_FALSE(max_count.Get().has_value());
ParseFieldTrial({&max_count}, "c:10");
EXPECT_EQ(max_count.Get().value(), 10);
ParseFieldTrial({&max_count}, "c");
EXPECT_FALSE(max_count.Get().has_value());
ParseFieldTrial({&max_count}, "c:20");
EXPECT_EQ(max_count.Get().value(), 20);
ParseFieldTrial({&max_count}, "c:");
EXPECT_EQ(max_count.Get().value(), 20);
FieldTrialOptional<std::string> optional_string("s", std::string("ab"));
ParseFieldTrial({&optional_string}, "s:");
EXPECT_EQ(optional_string.Get().value(), "");
ParseFieldTrial({&optional_string}, "s");
EXPECT_FALSE(optional_string.Get().has_value());
}
TEST(FieldTrialParserTest, ParsesCustomEnumParameter) {
FieldTrialParameter<CustomEnum> my_enum("e", CustomEnum::kDefault);
ParseFieldTrial({&my_enum}, "");
EXPECT_EQ(my_enum.Get(), CustomEnum::kDefault);
ParseFieldTrial({&my_enum}, "e:red");
EXPECT_EQ(my_enum.Get(), CustomEnum::kRed);
}
} // namespace webrtc

View File

@ -0,0 +1,92 @@
/*
* Copyright 2018 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_units.h"
#include <limits>
#include <string>
#include "api/optional.h"
// Large enough to fit "seconds", the longest supported unit name.
#define RTC_TRIAL_UNIT_LENGTH_STR "7"
#define RTC_TRIAL_UNIT_SIZE 8
namespace webrtc {
namespace {
struct ValueWithUnit {
double value;
std::string unit;
};
rtc::Optional<ValueWithUnit> ParseValueWithUnit(std::string str) {
if (str == "inf") {
return ValueWithUnit{std::numeric_limits<double>::infinity(), ""};
} else if (str == "-inf") {
return ValueWithUnit{-std::numeric_limits<double>::infinity(), ""};
} else {
double double_val;
char unit_char[RTC_TRIAL_UNIT_SIZE];
unit_char[0] = 0;
if (sscanf(str.c_str(), "%lf%" RTC_TRIAL_UNIT_LENGTH_STR "s", &double_val,
unit_char) >= 1) {
return ValueWithUnit{double_val, unit_char};
}
}
return rtc::nullopt;
}
} // namespace
template <>
rtc::Optional<DataRate> ParseTypedParameter<DataRate>(std::string str) {
rtc::Optional<ValueWithUnit> result = ParseValueWithUnit(str);
if (result) {
if (result->unit.empty() || result->unit == "kbps") {
return DataRate::kbps(result->value);
} else if (result->unit == "bps") {
return DataRate::bps(result->value);
}
}
return rtc::nullopt;
}
template <>
rtc::Optional<DataSize> ParseTypedParameter<DataSize>(std::string str) {
rtc::Optional<ValueWithUnit> result = ParseValueWithUnit(str);
if (result) {
if (result->unit.empty() || result->unit == "bytes")
return DataSize::bytes(result->value);
}
return rtc::nullopt;
}
template <>
rtc::Optional<TimeDelta> ParseTypedParameter<TimeDelta>(std::string str) {
rtc::Optional<ValueWithUnit> result = ParseValueWithUnit(str);
if (result) {
if (result->unit == "s" || result->unit == "seconds") {
return TimeDelta::seconds(result->value);
} else if (result->unit == "us") {
return TimeDelta::us(result->value);
} else if (result->unit.empty() || result->unit == "ms") {
return TimeDelta::ms(result->value);
}
}
return rtc::nullopt;
}
template class FieldTrialParameter<DataRate>;
template class FieldTrialParameter<DataSize>;
template class FieldTrialParameter<TimeDelta>;
template class FieldTrialOptional<DataRate>;
template class FieldTrialOptional<DataSize>;
template class FieldTrialOptional<TimeDelta>;
} // namespace webrtc

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018 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.
*/
#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_
#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_
#include "rtc_base/experiments/field_trial_parser.h"
#include "api/units/data_rate.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
namespace webrtc {
extern template class FieldTrialParameter<DataRate>;
extern template class FieldTrialParameter<DataSize>;
extern template class FieldTrialParameter<TimeDelta>;
extern template class FieldTrialOptional<DataRate>;
extern template class FieldTrialOptional<DataSize>;
extern template class FieldTrialOptional<TimeDelta>;
} // namespace webrtc
#endif // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_

View File

@ -0,0 +1,62 @@
/*
* Copyright 2018 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 <string>
#include "rtc_base/experiments/field_trial_parser.h"
#include "rtc_base/experiments/field_trial_units.h"
#include "rtc_base/gunit.h"
namespace webrtc {
namespace {
struct DummyExperiment {
FieldTrialParameter<DataRate> target_rate =
FieldTrialParameter<DataRate>("t", DataRate::kbps(100));
FieldTrialParameter<TimeDelta> period =
FieldTrialParameter<TimeDelta>("p", TimeDelta::ms(100));
FieldTrialOptional<DataSize> max_buffer =
FieldTrialOptional<DataSize>("b", rtc::nullopt);
explicit DummyExperiment(std::string field_trial) {
ParseFieldTrial({&target_rate, &max_buffer, &period}, field_trial);
}
};
} // namespace
TEST(FieldTrialParserUnitsTest, FallsBackToDefaults) {
DummyExperiment exp("");
EXPECT_EQ(exp.target_rate.Get(), DataRate::kbps(100));
EXPECT_FALSE(exp.max_buffer.Get().has_value());
EXPECT_EQ(exp.period.Get(), TimeDelta::ms(100));
}
TEST(FieldTrialParserUnitsTest, ParsesUnitParameters) {
DummyExperiment exp("t:300kbps,b:5bytes,p:300ms");
EXPECT_EQ(exp.target_rate.Get(), DataRate::kbps(300));
EXPECT_EQ(*exp.max_buffer.Get(), DataSize::bytes(5));
EXPECT_EQ(exp.period.Get(), TimeDelta::ms(300));
}
TEST(FieldTrialParserUnitsTest, ParsesDefaultUnitParameters) {
DummyExperiment exp("t:300,b:5,p:300");
EXPECT_EQ(exp.target_rate.Get(), DataRate::kbps(300));
EXPECT_EQ(*exp.max_buffer.Get(), DataSize::bytes(5));
EXPECT_EQ(exp.period.Get(), TimeDelta::ms(300));
}
TEST(FieldTrialParserUnitsTest, ParsesInfinityParameter) {
DummyExperiment exp("t:inf,p:inf");
EXPECT_EQ(exp.target_rate.Get(), DataRate::Infinity());
EXPECT_EQ(exp.period.Get(), TimeDelta::PlusInfinity());
}
TEST(FieldTrialParserUnitsTest, ParsesOtherUnitParameters) {
DummyExperiment exp("t:300bps,p:0.3 seconds,b:8 bytes");
EXPECT_EQ(exp.target_rate.Get(), DataRate::bps(300));
EXPECT_EQ(*exp.max_buffer.Get(), DataSize::bytes(8));
EXPECT_EQ(exp.period.Get(), TimeDelta::ms(300));
}
} // namespace webrtc