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