1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // FieldTrial is a class for handling details of statistical experiments 6 // performed by actual users in the field (i.e., in a shipped or beta product). 7 // All code is called exclusively on the UI thread currently. 8 // 9 // The simplest example is an experiment to see whether one of two options 10 // produces "better" results across our user population. In that scenario, UMA 11 // data is uploaded to aggregate the test results, and this FieldTrial class 12 // manages the state of each such experiment (state == which option was 13 // pseudo-randomly selected). 14 // 15 // States are typically generated randomly, either based on a one time 16 // randomization (generated randomly once, and then persistently reused in the 17 // client during each future run of the program), or by a startup randomization 18 // (generated each time the application starts up, but held constant during the 19 // duration of the process), or by continuous randomization across a run (where 20 // the state can be recalculated again and again, many times during a process). 21 // Only startup randomization is implemented thus far. 22 23 //------------------------------------------------------------------------------ 24 // Example: Suppose we have an experiment involving memory, such as determining 25 // the impact of some pruning algorithm. 26 // We assume that we already have a histogram of memory usage, such as: 27 28 // HISTOGRAM_COUNTS("Memory.RendererTotal", count); 29 30 // Somewhere in main thread initialization code, we'd probably define an 31 // instance of a FieldTrial, with code such as: 32 33 // // FieldTrials are reference counted, and persist automagically until 34 // // process teardown, courtesy of their automatic registration in 35 // // FieldTrialList. 36 // // Note: This field trial will run in Chrome instances compiled through 37 // // 8 July, 2015, and after that all instances will be in "StandardMem". 38 // scoped_refptr<FieldTrial> trial = new FieldTrial("MemoryExperiment", 1000, 39 // "StandardMem", 2015, 7, 8); 40 // const int kHighMemGroup = 41 // trial->AppendGroup("HighMem", 20); // 2% in HighMem group. 42 // const int kLowMemGroup = 43 // trial->AppendGroup("LowMem", 20); // 2% in LowMem group. 44 // // Take action depending of which group we randomly land in. 45 // if (trial->group() == kHighMemGroup) 46 // SetPruningAlgorithm(kType1); // Sample setting of browser state. 47 // else if (trial->group() == kLowMemGroup) 48 // SetPruningAlgorithm(kType2); // Sample alternate setting. 49 50 // We then, in addition to our original histogram, output histograms which have 51 // slightly different names depending on what group the trial instance happened 52 // to randomly be assigned: 53 54 // HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram. 55 // static bool use_memoryexperiment_histogram( 56 // base::FieldTrialList::Find("MemoryExperiment") && 57 // !base::FieldTrialList::Find("MemoryExperiment")->group_name().empty()); 58 // if (use_memoryexperiment_histogram) { 59 // HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal", 60 // "MemoryExperiment"), count); 61 // } 62 63 // The above code will create four distinct histograms, with each run of the 64 // application being assigned to of of the three groups, and for each group, the 65 // correspondingly named histogram will be populated: 66 67 // Memory.RendererTotal // 100% of users still fill this histogram. 68 // Memory.RendererTotal_HighMem // 2% of users will fill this histogram. 69 // Memory.RendererTotal_LowMem // 2% of users will fill this histogram. 70 // Memory.RendererTotal_StandardMem // 96% of users will fill this histogram. 71 72 //------------------------------------------------------------------------------ 73 74 #ifndef BASE_METRICS_FIELD_TRIAL_H_ 75 #define BASE_METRICS_FIELD_TRIAL_H_ 76 #pragma once 77 78 #include <map> 79 #include <string> 80 81 #include "base/base_api.h" 82 #include "base/gtest_prod_util.h" 83 #include "base/memory/ref_counted.h" 84 #include "base/synchronization/lock.h" 85 #include "base/time.h" 86 87 namespace base { 88 89 class FieldTrialList; 90 91 class BASE_API FieldTrial : public RefCounted<FieldTrial> { 92 public: 93 typedef int Probability; // Probability type for being selected in a trial. 94 95 // A return value to indicate that a given instance has not yet had a group 96 // assignment (and hence is not yet participating in the trial). 97 static const int kNotFinalized; 98 99 // This is the group number of the 'default' group. This provides an easy way 100 // to assign all the remaining probability to a group ('default'). 101 static const int kDefaultGroupNumber; 102 103 // The name is used to register the instance with the FieldTrialList class, 104 // and can be used to find the trial (only one trial can be present for each 105 // name). 106 // Group probabilities that are later supplied must sum to less than or equal 107 // to the total_probability. Arguments year, month and day_of_month specify 108 // the expiration time. If the build time is after the expiration time then 109 // the field trial reverts to the 'default' group. 110 FieldTrial(const std::string& name, Probability total_probability, 111 const std::string& default_group_name, const int year, 112 const int month, const int day_of_month); 113 114 // Establish the name and probability of the next group in this trial. 115 // Sometimes, based on construction randomization, this call may cause the 116 // provided group to be *THE* group selected for use in this instance. 117 // The return value is the group number of the new group. 118 int AppendGroup(const std::string& name, Probability group_probability); 119 120 // Return the name of the FieldTrial (excluding the group name). 121 std::string name() const { return name_; } 122 123 // Return the randomly selected group number that was assigned. 124 // Return kDefaultGroupNumber if the instance is in the 'default' group. 125 // Note that this will force an instance to participate, and make it illegal 126 // to attempt to probabalistically add any other groups to the trial. 127 int group(); 128 129 // If the field trial is not in an experiment, this returns the empty string. 130 // if the group's name is empty, a name of "_" concatenated with the group 131 // number is used as the group name. 132 std::string group_name(); 133 134 // Return the default group name of the FieldTrial. 135 std::string default_group_name() const { return default_group_name_; } 136 137 // Helper function for the most common use: as an argument to specifiy the 138 // name of a HISTOGRAM. Use the original histogram name as the name_prefix. 139 static std::string MakeName(const std::string& name_prefix, 140 const std::string& trial_name); 141 142 // Enable benchmarking sets field trials to a common setting. 143 static void EnableBenchmarking(); 144 145 private: 146 // Allow tests to access our innards for testing purposes. 147 FRIEND_TEST(FieldTrialTest, Registration); 148 FRIEND_TEST(FieldTrialTest, AbsoluteProbabilities); 149 FRIEND_TEST(FieldTrialTest, RemainingProbability); 150 FRIEND_TEST(FieldTrialTest, FiftyFiftyProbability); 151 FRIEND_TEST(FieldTrialTest, MiddleProbabilities); 152 FRIEND_TEST(FieldTrialTest, OneWinner); 153 FRIEND_TEST(FieldTrialTest, DisableProbability); 154 FRIEND_TEST(FieldTrialTest, Save); 155 FRIEND_TEST(FieldTrialTest, DuplicateRestore); 156 FRIEND_TEST(FieldTrialTest, MakeName); 157 158 friend class base::FieldTrialList; 159 160 friend class RefCounted<FieldTrial>; 161 162 virtual ~FieldTrial(); 163 164 // Returns the group_name. A winner need not have been chosen. 165 std::string group_name_internal() const { return group_name_; } 166 167 // Get build time. 168 static Time GetBuildTime(); 169 170 // The name of the field trial, as can be found via the FieldTrialList. 171 // This is empty of the trial is not in the experiment. 172 const std::string name_; 173 174 // The maximum sum of all probabilities supplied, which corresponds to 100%. 175 // This is the scaling factor used to adjust supplied probabilities. 176 const Probability divisor_; 177 178 // The name of the default group. 179 const std::string default_group_name_; 180 181 // The randomly selected probability that is used to select a group (or have 182 // the instance not participate). It is the product of divisor_ and a random 183 // number between [0, 1). 184 const Probability random_; 185 186 // Sum of the probabilities of all appended groups. 187 Probability accumulated_group_probability_; 188 189 int next_group_number_; 190 191 // The pseudo-randomly assigned group number. 192 // This is kNotFinalized if no group has been assigned. 193 int group_; 194 195 // A textual name for the randomly selected group. If this Trial is not a 196 // member of an group, this string is empty. 197 std::string group_name_; 198 199 // When disable_field_trial_ is true, field trial reverts to the 'default' 200 // group. 201 bool disable_field_trial_; 202 203 // When benchmarking is enabled, field trials all revert to the 'default' 204 // group. 205 static bool enable_benchmarking_; 206 207 DISALLOW_COPY_AND_ASSIGN(FieldTrial); 208 }; 209 210 //------------------------------------------------------------------------------ 211 // Class with a list of all active field trials. A trial is active if it has 212 // been registered, which includes evaluating its state based on its probaility. 213 // Only one instance of this class exists. 214 class BASE_API FieldTrialList { 215 public: 216 // Define a separator charactor to use when creating a persistent form of an 217 // instance. This is intended for use as a command line argument, passed to a 218 // second process to mimic our state (i.e., provide the same group name). 219 static const char kPersistentStringSeparator; // Currently a slash. 220 221 // This singleton holds the global list of registered FieldTrials. 222 FieldTrialList(); 223 // Destructor Release()'s references to all registered FieldTrial instances. 224 ~FieldTrialList(); 225 226 // Register() stores a pointer to the given trial in a global map. 227 // This method also AddRef's the indicated trial. 228 static void Register(FieldTrial* trial); 229 230 // The Find() method can be used to test to see if a named Trial was already 231 // registered, or to retrieve a pointer to it from the global map. 232 static FieldTrial* Find(const std::string& name); 233 234 static int FindValue(const std::string& name); 235 236 static std::string FindFullName(const std::string& name); 237 238 // Create a persistent representation of all FieldTrial instances for 239 // resurrection in another process. This allows randomization to be done in 240 // one process, and secondary processes can by synchronized on the result. 241 // The resulting string contains only the names, the trial name, and a "/" 242 // separator. 243 static void StatesToString(std::string* output); 244 245 // Use a previously generated state string (re: StatesToString()) augment the 246 // current list of field tests to include the supplied tests, and using a 100% 247 // probability for each test, force them to have the same group string. This 248 // is commonly used in a sub-process, to carry randomly selected state in a 249 // parent process into this sub-process. 250 // Currently only the group_name_ and name_ are restored. 251 static bool CreateTrialsInChildProcess(const std::string& prior_trials); 252 253 // The time of construction of the global map is recorded in a static variable 254 // and is commonly used by experiments to identify the time since the start 255 // of the application. In some experiments it may be useful to discount 256 // data that is gathered before the application has reached sufficient 257 // stability (example: most DLL have loaded, etc.) 258 static TimeTicks application_start_time() { 259 if (global_) 260 return global_->application_start_time_; 261 // For testing purposes only, or when we don't yet have a start time. 262 return TimeTicks::Now(); 263 } 264 265 // Return the number of active field trials. 266 static size_t GetFieldTrialCount(); 267 268 private: 269 // A map from FieldTrial names to the actual instances. 270 typedef std::map<std::string, FieldTrial*> RegistrationList; 271 272 // Helper function should be called only while holding lock_. 273 FieldTrial* PreLockedFind(const std::string& name); 274 275 static FieldTrialList* global_; // The singleton of this class. 276 277 // This will tell us if there is an attempt to register a field trial without 278 // creating the FieldTrialList. This is not an error, unless a FieldTrialList 279 // is created after that. 280 static bool register_without_global_; 281 282 // A helper value made availabel to users, that shows when the FieldTrialList 283 // was initialized. Note that this is a singleton instance, and hence is a 284 // good approximation to the start of the process. 285 TimeTicks application_start_time_; 286 287 // Lock for access to registered_. 288 base::Lock lock_; 289 RegistrationList registered_; 290 291 DISALLOW_COPY_AND_ASSIGN(FieldTrialList); 292 }; 293 294 } // namespace base 295 296 #endif // BASE_METRICS_FIELD_TRIAL_H_ 297 298