1 // Copyright 2022 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #ifndef GRPC_SRC_CORE_LIB_EXPERIMENTS_CONFIG_H
16 #define GRPC_SRC_CORE_LIB_EXPERIMENTS_CONFIG_H
17
18 #include <grpc/support/port_platform.h>
19 #include <stddef.h>
20 #include <stdint.h>
21
22 #include <atomic>
23
24 #include "absl/functional/any_invocable.h"
25 #include "absl/strings/string_view.h"
26
27 // #define GRPC_EXPERIMENTS_ARE_FINAL
28
29 namespace grpc_core {
30
31 struct ExperimentMetadata {
32 const char* name;
33 const char* description;
34 const char* additional_constaints;
35 const uint8_t* required_experiments;
36 uint8_t num_required_experiments;
37 bool default_value;
38 bool allow_in_fuzzing_config;
39 };
40
41 #ifndef GRPC_EXPERIMENTS_ARE_FINAL
42 class ExperimentFlags {
43 public:
IsExperimentEnabled(size_t experiment_id)44 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static bool IsExperimentEnabled(
45 size_t experiment_id) {
46 auto bit = experiment_id % kFlagsPerWord;
47 auto word = experiment_id / kFlagsPerWord;
48 auto cur = experiment_flags_[word].load(std::memory_order_relaxed);
49 if (cur & (1ull << bit)) return true;
50 if (cur & kLoadedFlag) return false;
51 return LoadFlagsAndCheck(experiment_id);
52 }
53
54 template <size_t kExperimentId>
IsExperimentEnabled()55 GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static bool IsExperimentEnabled() {
56 auto bit = kExperimentId % kFlagsPerWord;
57 auto word = kExperimentId / kFlagsPerWord;
58 auto cur = experiment_flags_[word].load(std::memory_order_relaxed);
59 if (cur & (1ull << bit)) return true;
60 if (cur & kLoadedFlag) return false;
61 return LoadFlagsAndCheck(kExperimentId);
62 }
63
64 static void TestOnlyClear();
65
66 private:
67 static bool LoadFlagsAndCheck(size_t experiment_id);
68
69 // We layout experiment flags in groups of 63... each 64 bit word contains
70 // 63 enablement flags (one per experiment), and the high bit which indicates
71 // whether the flags have been loaded from the configuration.
72 // Consequently, with one load, we can tell if the experiment is definitely
73 // enabled (the bit is set), or definitely disabled (the bit is clear, and the
74 // loaded flag is set), or if we need to load the flags and re-check.
75
76 static constexpr size_t kNumExperimentFlagsWords = 8;
77 static constexpr size_t kFlagsPerWord = 63;
78 static constexpr uint64_t kLoadedFlag = 0x8000000000000000ull;
79 static std::atomic<uint64_t> experiment_flags_[kNumExperimentFlagsWords];
80 };
81
82 // Return true if experiment \a experiment_id is enabled.
83 // Experiments are numbered by their order in the g_experiment_metadata array
84 // declared in experiments.h.
IsExperimentEnabled(size_t experiment_id)85 inline bool IsExperimentEnabled(size_t experiment_id) {
86 return ExperimentFlags::IsExperimentEnabled(experiment_id);
87 }
88
89 template <size_t kExperimentId>
IsExperimentEnabled()90 inline bool IsExperimentEnabled() {
91 return ExperimentFlags::IsExperimentEnabled<kExperimentId>();
92 }
93
94 // Given a test experiment id, returns true if the test experiment is enabled.
95 // Test experiments can be loaded using the LoadTestOnlyExperimentsFromMetadata
96 // method.
97 bool IsTestExperimentEnabled(size_t experiment_id);
98
99 template <size_t kExperimentId>
IsTestExperimentEnabled()100 inline bool IsTestExperimentEnabled() {
101 return IsTestExperimentEnabled(kExperimentId);
102 }
103
104 // Slow check for if a named experiment is enabled.
105 // Parses the configuration and looks up the experiment in that, so it does not
106 // affect any global state, but it does require parsing the configuration every
107 // call!
108 bool IsExperimentEnabledInConfiguration(size_t experiment_id);
109
110 // Reload experiment state from config variables.
111 // Does not change ForceEnableExperiment state.
112 // Expects the caller to handle global thread safety - so really only
113 // appropriate for carefully written tests.
114 void TestOnlyReloadExperimentsFromConfigVariables();
115
116 // Reload experiment state from passed metadata.
117 // Does not change ForceEnableExperiment state.
118 // Expects the caller to handle global thread safety - so really only
119 // appropriate for carefully written tests.
120 void LoadTestOnlyExperimentsFromMetadata(
121 const ExperimentMetadata* experiment_metadata, size_t num_experiments);
122 #endif
123
124 // Print out a list of all experiments that are built into this binary.
125 void PrintExperimentsList();
126
127 // Force an experiment to be on or off.
128 // Must be called before experiments are configured (the first
129 // IsExperimentEnabled call).
130 // If the experiment does not exist, emits a warning but continues execution.
131 // If this is called twice for the same experiment, both calls must agree.
132 void ForceEnableExperiment(absl::string_view experiment_name, bool enable);
133
134 // Register a function to be called to validate the value an experiment can
135 // take subject to additional constraints.
136 // The function will take the ExperimentMetadata as its argument. It will return
137 // a bool value indicating the actual value the experiment should take.
138 void RegisterExperimentConstraintsValidator(
139 absl::AnyInvocable<bool(struct ExperimentMetadata)> check_constraints_cb);
140
141 } // namespace grpc_core
142
143 #endif // GRPC_SRC_CORE_LIB_EXPERIMENTS_CONFIG_H
144