blob: 891229bc151be51bf400a1192076f991a231eaed [file]
// 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 <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.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 g_forced_experiments[kNumExperiments];
std::atomic<bool> g_loaded(false);
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;
}
}
}
// 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 LoadExperimentsFromConfigVariable() {
g_loaded.store(true, std::memory_order_relaxed);
// Set defaults from metadata.
Experiments experiments;
for (size_t i = 0; i < kNumExperiments; i++) {
if (!g_forced_experiments[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] = g_forced_experiments[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());
}
}
return experiments;
}
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 IsTestExperimentEnabled(size_t experiment_id) {
return (*g_test_experiments)[experiment_id];
}
void PrintExperimentsList() {
size_t max_experiment_length = 0;
for (size_t i = 0; i < kNumExperiments; i++) {
max_experiment_length =
std::max(max_experiment_length, strlen(g_experiment_metadata[i].name));
}
for (size_t i = 0; i < kNumExperiments; i++) {
gpr_log(
GPR_DEBUG, "%s",
absl::StrCat(
"gRPC EXPERIMENT ", g_experiment_metadata[i].name,
std::string(max_experiment_length -
strlen(g_experiment_metadata[i].name) + 1,
' '),
IsExperimentEnabled(i) ? "ON " : "OFF",
" (default:", g_experiment_metadata[i].default_value ? "ON" : "OFF",
(g_check_constraints_cb != nullptr
? absl::StrCat(
" + ", g_experiment_metadata[i].additional_constaints,
" => ",
(*g_check_constraints_cb)(g_experiment_metadata[i])
? "ON "
: "OFF")
: std::string()),
g_forced_experiments[i].forced
? absl::StrCat(" force:",
g_forced_experiments[i].value ? "ON" : "OFF")
: std::string(),
")")
.c_str());
}
}
void ForceEnableExperiment(absl::string_view experiment, bool enable) {
GPR_ASSERT(g_loaded.load(std::memory_order_relaxed) == false);
for (size_t i = 0; i < kNumExperiments; i++) {
if (g_experiment_metadata[i].name != experiment) continue;
if (g_forced_experiments[i].forced) {
GPR_ASSERT(g_forced_experiments[i].value == enable);
} else {
g_forced_experiments[i].forced = true;
g_forced_experiments[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