• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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