The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) https://grpc.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

284 lines
9.1 KiB

// Copyright 2022 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <grpc/support/port_platform.h>
#include "src/core/lib/experiments/config.h"
#include <string.h>
#include <algorithm>
#include <atomic>
#include <map>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include <grpc/support/log.h>
#include "src/core/lib/config/config_vars.h"
#include "src/core/lib/experiments/experiments.h"
#include "src/core/lib/gprpp/crash.h" // IWYU pragma: keep
#include "src/core/lib/gprpp/no_destruct.h"
#ifndef GRPC_EXPERIMENTS_ARE_FINAL
namespace grpc_core {
namespace {
struct Experiments {
bool enabled[kNumExperiments];
};
struct ForcedExperiment {
bool forced = false;
bool value;
};
ForcedExperiment* ForcedExperiments() {
static NoDestruct<ForcedExperiment> forced_experiments[kNumExperiments];
return &**forced_experiments;
}
std::atomic<bool>* Loaded() {
static NoDestruct<std::atomic<bool>> loaded(false);
return &*loaded;
}
absl::AnyInvocable<bool(struct ExperimentMetadata)>* g_check_constraints_cb =
nullptr;
class TestExperiments {
public:
TestExperiments(const ExperimentMetadata* experiment_metadata,
size_t num_experiments) {
enabled_ = new bool[num_experiments];
for (size_t i = 0; i < num_experiments; i++) {
if (g_check_constraints_cb != nullptr) {
enabled_[i] = (*g_check_constraints_cb)(experiment_metadata[i]);
} else {
enabled_[i] = experiment_metadata[i].default_value;
}
}
// For each comma-separated experiment in the global config:
for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
absl::SkipWhitespace())) {
// Enable unless prefixed with '-' (=> disable).
bool enable = !absl::ConsumePrefix(&experiment, "-");
// See if we can find the experiment in the list in this binary.
for (size_t i = 0; i < num_experiments; i++) {
if (experiment == experiment_metadata[i].name) {
enabled_[i] = enable;
break;
}
}
}
}
// Overloading [] operator to access elements in array style
bool operator[](int index) { return enabled_[index]; }
~TestExperiments() { delete enabled_; }
private:
bool* enabled_;
};
TestExperiments* g_test_experiments = nullptr;
GPR_ATTRIBUTE_NOINLINE Experiments LoadExperimentsFromConfigVariableInner() {
// Set defaults from metadata.
Experiments experiments;
for (size_t i = 0; i < kNumExperiments; i++) {
if (!ForcedExperiments()[i].forced) {
if (g_check_constraints_cb != nullptr) {
experiments.enabled[i] =
(*g_check_constraints_cb)(g_experiment_metadata[i]);
} else {
experiments.enabled[i] = g_experiment_metadata[i].default_value;
}
} else {
experiments.enabled[i] = ForcedExperiments()[i].value;
}
}
// For each comma-separated experiment in the global config:
for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
absl::SkipWhitespace())) {
// Enable unless prefixed with '-' (=> disable).
bool enable = true;
if (experiment[0] == '-') {
enable = false;
experiment.remove_prefix(1);
}
// See if we can find the experiment in the list in this binary.
bool found = false;
for (size_t i = 0; i < kNumExperiments; i++) {
if (experiment == g_experiment_metadata[i].name) {
experiments.enabled[i] = enable;
found = true;
break;
}
}
// If not found log an error, but don't take any other action.
// Allows us an easy path to disabling experiments.
if (!found) {
gpr_log(GPR_ERROR, "Unknown experiment: %s",
std::string(experiment).c_str());
}
}
for (size_t i = 0; i < kNumExperiments; i++) {
// If required experiments are not enabled, disable this one too.
for (size_t j = 0; j < g_experiment_metadata[i].num_required_experiments;
j++) {
// Require that we can check dependent requirements with a linear sweep
// (implies the experiments generator must DAG sort the experiments)
GPR_ASSERT(g_experiment_metadata[i].required_experiments[j] < i);
if (!experiments
.enabled[g_experiment_metadata[i].required_experiments[j]]) {
experiments.enabled[i] = false;
}
}
}
return experiments;
}
Experiments LoadExperimentsFromConfigVariable() {
Loaded()->store(true, std::memory_order_relaxed);
return LoadExperimentsFromConfigVariableInner();
}
Experiments& ExperimentsSingleton() {
// One time initialization:
static NoDestruct<Experiments> experiments{
LoadExperimentsFromConfigVariable()};
return *experiments;
}
} // namespace
void TestOnlyReloadExperimentsFromConfigVariables() {
ExperimentsSingleton() = LoadExperimentsFromConfigVariable();
PrintExperimentsList();
}
void LoadTestOnlyExperimentsFromMetadata(
const ExperimentMetadata* experiment_metadata, size_t num_experiments) {
g_test_experiments =
new TestExperiments(experiment_metadata, num_experiments);
}
bool IsExperimentEnabled(size_t experiment_id) {
return ExperimentsSingleton().enabled[experiment_id];
}
bool IsExperimentEnabledInConfiguration(size_t experiment_id) {
return LoadExperimentsFromConfigVariableInner().enabled[experiment_id];
}
bool IsTestExperimentEnabled(size_t experiment_id) {
return (*g_test_experiments)[experiment_id];
}
void PrintExperimentsList() {
std::map<std::string, std::string> experiment_status;
std::set<std::string> defaulted_on_experiments;
for (size_t i = 0; i < kNumExperiments; i++) {
const char* name = g_experiment_metadata[i].name;
const bool enabled = IsExperimentEnabled(i);
const bool default_enabled = g_experiment_metadata[i].default_value;
const bool forced = ForcedExperiments()[i].forced;
if (!default_enabled && !enabled) continue;
if (default_enabled && enabled) {
defaulted_on_experiments.insert(name);
continue;
}
if (enabled) {
if (g_check_constraints_cb != nullptr &&
(*g_check_constraints_cb)(g_experiment_metadata[i])) {
experiment_status[name] = "on:constraints";
continue;
}
if (forced && ForcedExperiments()[i].value) {
experiment_status[name] = "on:forced";
continue;
}
experiment_status[name] = "on";
} else {
if (forced && !ForcedExperiments()[i].value) {
experiment_status[name] = "off:forced";
continue;
}
experiment_status[name] = "off";
}
}
if (experiment_status.empty()) {
if (!defaulted_on_experiments.empty()) {
gpr_log(GPR_INFO, "gRPC experiments enabled: %s",
absl::StrJoin(defaulted_on_experiments, ", ").c_str());
}
} else {
if (defaulted_on_experiments.empty()) {
gpr_log(GPR_INFO, "gRPC experiments: %s",
absl::StrJoin(experiment_status, ", ", absl::PairFormatter(":"))
.c_str());
} else {
gpr_log(GPR_INFO, "gRPC experiments: %s; default-enabled: %s",
absl::StrJoin(experiment_status, ", ", absl::PairFormatter(":"))
.c_str(),
absl::StrJoin(defaulted_on_experiments, ", ").c_str());
}
}
}
void ForceEnableExperiment(absl::string_view experiment, bool enable) {
GPR_ASSERT(Loaded()->load(std::memory_order_relaxed) == false);
for (size_t i = 0; i < kNumExperiments; i++) {
if (g_experiment_metadata[i].name != experiment) continue;
if (ForcedExperiments()[i].forced) {
GPR_ASSERT(ForcedExperiments()[i].value == enable);
} else {
ForcedExperiments()[i].forced = true;
ForcedExperiments()[i].value = enable;
}
return;
}
gpr_log(GPR_INFO, "gRPC EXPERIMENT %s not found to force %s",
std::string(experiment).c_str(), enable ? "enable" : "disable");
}
void RegisterExperimentConstraintsValidator(
absl::AnyInvocable<bool(struct ExperimentMetadata)> check_constraints_cb) {
g_check_constraints_cb =
new absl::AnyInvocable<bool(struct ExperimentMetadata)>(
std::move(check_constraints_cb));
}
} // namespace grpc_core
#else
namespace grpc_core {
void PrintExperimentsList() {}
void ForceEnableExperiment(absl::string_view experiment_name, bool) {
Crash(absl::StrCat("ForceEnableExperiment(\"", experiment_name,
"\") called in final build"));
}
void RegisterExperimentConstraintsValidator(
absl::AnyInvocable<
bool(struct ExperimentMetadata)> /*check_constraints_cb*/) {}
} // namespace grpc_core
#endif