[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 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 | // |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 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 | // |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 15 | // States are typically generated randomly, either based on a one time |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 16 | // randomization (which will yield the same results, in terms of selecting |
| 17 | // the client for a field trial or not, for every run of the program on a |
| 18 | // given machine), or by a startup randomization (generated each time the |
| 19 | // application starts up, but held constant during the duration of the |
| 20 | // process), or by continuous randomization across a run (where the state |
| 21 | // can be recalculated again and again, many times during a process). |
| 22 | // Continuous randomization is not yet implemented. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 23 | |
| 24 | //------------------------------------------------------------------------------ |
| 25 | // Example: Suppose we have an experiment involving memory, such as determining |
[email protected] | 57a336a | 2009-09-30 20:42:27 | [diff] [blame] | 26 | // the impact of some pruning algorithm. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 27 | // We assume that we already have a histogram of memory usage, such as: |
| 28 | |
| 29 | // HISTOGRAM_COUNTS("Memory.RendererTotal", count); |
| 30 | |
| 31 | // Somewhere in main thread initialization code, we'd probably define an |
| 32 | // instance of a FieldTrial, with code such as: |
| 33 | |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 34 | // // FieldTrials are reference counted, and persist automagically until |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 35 | // // process teardown, courtesy of their automatic registration in |
| 36 | // // FieldTrialList. |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 37 | // // Note: This field trial will run in Chrome instances compiled through |
| 38 | // // 8 July, 2015, and after that all instances will be in "StandardMem". |
[email protected] | 9f8c0a2 | 2012-06-13 02:01:24 | [diff] [blame] | 39 | // scoped_refptr<base::FieldTrial> trial( |
| 40 | // base::FieldTrialList::FactoryGetFieldTrial("MemoryExperiment", 1000, |
[email protected] | 70d7ca9 | 2012-08-17 22:43:10 | [diff] [blame] | 41 | // "StandardMem", 2015, 7, 8, |
| 42 | // NULL)); |
| 43 | // const int high_mem_group = |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 44 | // trial->AppendGroup("HighMem", 20); // 2% in HighMem group. |
[email protected] | 70d7ca9 | 2012-08-17 22:43:10 | [diff] [blame] | 45 | // const int low_mem_group = |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 46 | // trial->AppendGroup("LowMem", 20); // 2% in LowMem group. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 47 | // // Take action depending of which group we randomly land in. |
[email protected] | 70d7ca9 | 2012-08-17 22:43:10 | [diff] [blame] | 48 | // if (trial->group() == high_mem_group) |
[email protected] | 57a336a | 2009-09-30 20:42:27 | [diff] [blame] | 49 | // SetPruningAlgorithm(kType1); // Sample setting of browser state. |
[email protected] | 70d7ca9 | 2012-08-17 22:43:10 | [diff] [blame] | 50 | // else if (trial->group() == low_mem_group) |
[email protected] | 57a336a | 2009-09-30 20:42:27 | [diff] [blame] | 51 | // SetPruningAlgorithm(kType2); // Sample alternate setting. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 52 | |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 53 | // We then, in addition to our original histogram, output histograms which have |
| 54 | // slightly different names depending on what group the trial instance happened |
| 55 | // to randomly be assigned: |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 56 | |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 57 | // HISTOGRAM_COUNTS("Memory.RendererTotal", count); // The original histogram. |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 58 | // static const bool memory_renderer_total_trial_exists = |
[email protected] | a140efe | 2011-09-15 23:15:02 | [diff] [blame] | 59 | // FieldTrialList::TrialExists("MemoryExperiment"); |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 60 | // if (memory_renderer_total_trial_exists) { |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 61 | // HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal", |
| 62 | // "MemoryExperiment"), count); |
| 63 | // } |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 64 | |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 65 | // The above code will create four distinct histograms, with each run of the |
[email protected] | b37fdaa | 2009-07-01 01:14:56 | [diff] [blame] | 66 | // application being assigned to of of the three groups, and for each group, the |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 67 | // correspondingly named histogram will be populated: |
| 68 | |
[email protected] | f1d16d4a | 2011-03-21 14:04:01 | [diff] [blame] | 69 | // Memory.RendererTotal // 100% of users still fill this histogram. |
| 70 | // Memory.RendererTotal_HighMem // 2% of users will fill this histogram. |
| 71 | // Memory.RendererTotal_LowMem // 2% of users will fill this histogram. |
| 72 | // Memory.RendererTotal_StandardMem // 96% of users will fill this histogram. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 73 | |
| 74 | //------------------------------------------------------------------------------ |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 75 | |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 76 | #ifndef BASE_METRICS_FIELD_TRIAL_H_ |
| 77 | #define BASE_METRICS_FIELD_TRIAL_H_ |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 78 | |
| 79 | #include <map> |
| 80 | #include <string> |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 81 | #include <vector> |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 82 | |
[email protected] | 0bea725 | 2011-08-05 15:34:00 | [diff] [blame] | 83 | #include "base/base_export.h" |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 84 | #include "base/gtest_prod_util.h" |
[email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 85 | #include "base/memory/ref_counted.h" |
[email protected] | 8cffde0e | 2012-05-04 01:14:14 | [diff] [blame] | 86 | #include "base/observer_list_threadsafe.h" |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 87 | #include "base/synchronization/lock.h" |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 88 | #include "base/time.h" |
| 89 | |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 90 | namespace base { |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 91 | |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 92 | class FieldTrialList; |
| 93 | |
[email protected] | 0bea725 | 2011-08-05 15:34:00 | [diff] [blame] | 94 | class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 95 | public: |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 96 | typedef int Probability; // Probability type for being selected in a trial. |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 97 | |
[email protected] | 20f999b5 | 2012-08-24 22:32:59 | [diff] [blame] | 98 | // EntropyProvider is an interface for providing entropy for one-time |
| 99 | // randomized (persistent) field trials. |
| 100 | class BASE_EXPORT EntropyProvider { |
| 101 | public: |
| 102 | virtual ~EntropyProvider(); |
| 103 | |
| 104 | // Returns a double in the range of [0, 1) based on |trial_name| that will |
| 105 | // be used for the dice roll for the specified field trial. A given instance |
| 106 | // should always return the same value given the same input |trial_name|. |
| 107 | virtual double GetEntropyForTrial(const std::string& trial_name) const = 0; |
| 108 | }; |
| 109 | |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 110 | // A pair representing a Field Trial and its selected group. |
| 111 | struct SelectedGroup { |
| 112 | std::string trial; |
| 113 | std::string group; |
[email protected] | 25655dd | 2012-01-27 13:50:26 | [diff] [blame] | 114 | }; |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 115 | |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 116 | typedef std::vector<SelectedGroup> SelectedGroups; |
| 117 | |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 118 | // A return value to indicate that a given instance has not yet had a group |
| 119 | // assignment (and hence is not yet participating in the trial). |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 120 | static const int kNotFinalized; |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 121 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 122 | // Changes the field trial to use one-time randomization, i.e. produce the |
| 123 | // same result for the current trial on every run of this client. Must be |
| 124 | // called right after construction. |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 125 | void UseOneTimeRandomization(); |
| 126 | |
| 127 | // Disables this trial, meaning it always determines the default group |
| 128 | // has been selected. May be called immediately after construction, or |
| 129 | // at any time after initialization (should not be interleaved with |
| 130 | // AppendGroup calls). Once disabled, there is no way to re-enable a |
| 131 | // trial. |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 132 | // TODO(mad): http://code.google.com/p/chromium/issues/detail?id=121446 |
| 133 | // This doesn't properly reset to Default when a group was forced. |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 134 | void Disable(); |
| 135 | |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 136 | // Establish the name and probability of the next group in this trial. |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 137 | // Sometimes, based on construction randomization, this call may cause the |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 138 | // provided group to be *THE* group selected for use in this instance. |
[email protected] | 9238ab9 | 2011-02-25 17:22:46 | [diff] [blame] | 139 | // The return value is the group number of the new group. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 140 | int AppendGroup(const std::string& name, Probability group_probability); |
| 141 | |
| 142 | // Return the name of the FieldTrial (excluding the group name). |
| 143 | std::string name() const { return name_; } |
| 144 | |
[email protected] | 7aea117 | 2012-11-01 18:59:47 | [diff] [blame^] | 145 | // Return the randomly selected group number that was assigned, and notify |
| 146 | // any/all observers that this finalized group number has presumably been used |
| 147 | // (queried), and will never change. Note that this will force an instance to |
| 148 | // participate, and make it illegal to attempt to probabilistically add any |
| 149 | // other groups to the trial. |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 150 | int group(); |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 151 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 152 | // If the group's name is empty, a string version containing the group number |
| 153 | // is used as the group name. This causes a winner to be chosen if none was. |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 154 | std::string group_name(); |
| 155 | |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 156 | // Gets the SelectedGroup of the Field Trial, but only if a group was |
[email protected] | 25655dd | 2012-01-27 13:50:26 | [diff] [blame] | 157 | // officially chosen, otherwise name_group_id is left untouched and false |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 158 | // is returned. When true is returned, the trial and group names were |
| 159 | // successfully set in selected_group. |
| 160 | bool GetSelectedGroup(SelectedGroup* selected_group); |
[email protected] | 25655dd | 2012-01-27 13:50:26 | [diff] [blame] | 161 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 162 | // Helper function for the most common use: as an argument to specify the |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 163 | // name of a HISTOGRAM. Use the original histogram name as the name_prefix. |
| 164 | static std::string MakeName(const std::string& name_prefix, |
| 165 | const std::string& trial_name); |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 166 | |
[email protected] | e8d82c61 | 2010-12-07 22:54:27 | [diff] [blame] | 167 | // Enable benchmarking sets field trials to a common setting. |
| 168 | static void EnableBenchmarking(); |
| 169 | |
[email protected] | 0284bf7c | 2012-05-07 22:48:19 | [diff] [blame] | 170 | // Set the field trial as forced, meaning that it was setup earlier than |
| 171 | // the hard coded registration of the field trial to override it. |
| 172 | // This allows the code that was hard coded to register the field trial to |
| 173 | // still succeed even though the field trial has already been registered. |
| 174 | // This must be called after appending all the groups, since we will make |
| 175 | // the group choice here. Note that this is a NOOP for already forced trials. |
| 176 | // And, as the rest of the FieldTrial code, this is not thread safe and must |
| 177 | // be done from the UI thread. |
| 178 | void SetForced(); |
| 179 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 180 | private: |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 181 | // Allow tests to access our innards for testing purposes. |
[email protected] | 225020ce | 2011-11-29 14:45:53 | [diff] [blame] | 182 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); |
| 183 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, AbsoluteProbabilities); |
| 184 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, RemainingProbability); |
| 185 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, FiftyFiftyProbability); |
| 186 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MiddleProbabilities); |
| 187 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, OneWinner); |
| 188 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DisableProbability); |
| 189 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save); |
| 190 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore); |
| 191 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, MakeName); |
| 192 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientId); |
| 193 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientIdIsUniform); |
[email protected] | 25655dd | 2012-01-27 13:50:26 | [diff] [blame] | 194 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, NameGroupIds); |
[email protected] | 225020ce | 2011-11-29 14:45:53 | [diff] [blame] | 195 | FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, UseOneTimeRandomization); |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 196 | |
| 197 | friend class base::FieldTrialList; |
| 198 | |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 199 | friend class RefCounted<FieldTrial>; |
[email protected] | 877d55d | 2009-11-05 21:53:08 | [diff] [blame] | 200 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 201 | // This is the group number of the 'default' group when a choice wasn't forced |
| 202 | // by a call to FieldTrialList::CreateFieldTrial. It is kept private so that |
| 203 | // consumers don't use it by mistake in cases where the group was forced. |
| 204 | static const int kDefaultGroupNumber; |
| 205 | |
[email protected] | 8826a6e8 | 2012-05-11 02:16:32 | [diff] [blame] | 206 | FieldTrial(const std::string& name, |
| 207 | Probability total_probability, |
| 208 | const std::string& default_group_name); |
[email protected] | 9b2331d9 | 2010-10-04 23:11:19 | [diff] [blame] | 209 | virtual ~FieldTrial(); |
[email protected] | 877d55d | 2009-11-05 21:53:08 | [diff] [blame] | 210 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 211 | // Return the default group name of the FieldTrial. |
| 212 | std::string default_group_name() const { return default_group_name_; } |
| 213 | |
| 214 | // Sets the group_name as well as group_name_hash to make sure they are sync. |
| 215 | void SetGroupChoice(const std::string& name, int number); |
| 216 | |
[email protected] | 7aea117 | 2012-11-01 18:59:47 | [diff] [blame^] | 217 | // Ensures that a group is chosen, if it hasn't yet been. The field trial |
| 218 | // might yet be disabled, so this call will *not* notify observers of the |
| 219 | // status. |
| 220 | void FinalizeGroupChoice(); |
| 221 | |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 222 | // Returns the group_name. A winner need not have been chosen. |
| 223 | std::string group_name_internal() const { return group_name_; } |
| 224 | |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 225 | // The name of the field trial, as can be found via the FieldTrialList. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 226 | const std::string name_; |
| 227 | |
[email protected] | 58d2d2d | 2010-08-05 22:46:33 | [diff] [blame] | 228 | // The maximum sum of all probabilities supplied, which corresponds to 100%. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 229 | // This is the scaling factor used to adjust supplied probabilities. |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 230 | const Probability divisor_; |
| 231 | |
| 232 | // The name of the default group. |
| 233 | const std::string default_group_name_; |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 234 | |
| 235 | // The randomly selected probability that is used to select a group (or have |
| 236 | // the instance not participate). It is the product of divisor_ and a random |
| 237 | // number between [0, 1). |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 238 | Probability random_; |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 239 | |
| 240 | // Sum of the probabilities of all appended groups. |
| 241 | Probability accumulated_group_probability_; |
| 242 | |
| 243 | int next_group_number_; |
| 244 | |
| 245 | // The pseudo-randomly assigned group number. |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 246 | // This is kNotFinalized if no group has been assigned. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 247 | int group_; |
| 248 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 249 | // A textual name for the randomly selected group. Valid after |group()| |
| 250 | // has been called. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 251 | std::string group_name_; |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 252 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 253 | // When enable_field_trial_ is false, field trial reverts to the 'default' |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 254 | // group. |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 255 | bool enable_field_trial_; |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 256 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 257 | // When forced_ is true, we return the chosen group from AppendGroup when |
| 258 | // appropriate. |
| 259 | bool forced_; |
| 260 | |
[email protected] | 7aea117 | 2012-11-01 18:59:47 | [diff] [blame^] | 261 | // Specifies whether the group choice has been reported to observers. |
| 262 | bool group_reported_; |
| 263 | |
[email protected] | e8d82c61 | 2010-12-07 22:54:27 | [diff] [blame] | 264 | // When benchmarking is enabled, field trials all revert to the 'default' |
[email protected] | 933729bc | 2011-01-19 18:52:32 | [diff] [blame] | 265 | // group. |
[email protected] | e8d82c61 | 2010-12-07 22:54:27 | [diff] [blame] | 266 | static bool enable_benchmarking_; |
| 267 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 268 | DISALLOW_COPY_AND_ASSIGN(FieldTrial); |
| 269 | }; |
| 270 | |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 271 | //------------------------------------------------------------------------------ |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 272 | // Class with a list of all active field trials. A trial is active if it has |
| 273 | // been registered, which includes evaluating its state based on its probaility. |
| 274 | // Only one instance of this class exists. |
[email protected] | 0bea725 | 2011-08-05 15:34:00 | [diff] [blame] | 275 | class BASE_EXPORT FieldTrialList { |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 276 | public: |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 277 | // Define a separator character to use when creating a persistent form of an |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 278 | // instance. This is intended for use as a command line argument, passed to a |
| 279 | // second process to mimic our state (i.e., provide the same group name). |
| 280 | static const char kPersistentStringSeparator; // Currently a slash. |
| 281 | |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 282 | // Define expiration year in future. It is initialized to two years from Now. |
| 283 | static int kExpirationYearInFuture; |
| 284 | |
| 285 | // Observer is notified when a FieldTrial's group is selected. |
[email protected] | 8cffde0e | 2012-05-04 01:14:14 | [diff] [blame] | 286 | class BASE_EXPORT Observer { |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 287 | public: |
| 288 | // Notify observers when FieldTrials's group is selected. |
| 289 | virtual void OnFieldTrialGroupFinalized(const std::string& trial_name, |
| 290 | const std::string& group_name) = 0; |
| 291 | |
| 292 | protected: |
[email protected] | 20f999b5 | 2012-08-24 22:32:59 | [diff] [blame] | 293 | virtual ~Observer(); |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 294 | }; |
| 295 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 296 | // This singleton holds the global list of registered FieldTrials. |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 297 | // |
[email protected] | 20f999b5 | 2012-08-24 22:32:59 | [diff] [blame] | 298 | // To support one-time randomized field trials, specify a non-NULL |
| 299 | // |entropy_provider| which should be a source of uniformly distributed |
| 300 | // entropy values. Takes ownership of |entropy_provider|. If one time |
| 301 | // randomization is not desired, pass in NULL for |entropy_provider|. |
| 302 | explicit FieldTrialList(const FieldTrial::EntropyProvider* entropy_provider); |
| 303 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 304 | // Destructor Release()'s references to all registered FieldTrial instances. |
| 305 | ~FieldTrialList(); |
| 306 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 307 | // Get a FieldTrial instance from the factory. |
| 308 | // |
| 309 | // |name| is used to register the instance with the FieldTrialList class, |
| 310 | // and can be used to find the trial (only one trial can be present for each |
| 311 | // name). |default_group_name| is the name of the default group which will |
| 312 | // be chosen if none of the subsequent appended groups get to be chosen. |
| 313 | // |default_group_number| can receive the group number of the default group as |
| 314 | // AppendGroup returns the number of the subsequence groups. |name| and |
| 315 | // |default_group_name| may not be empty but |default_group_number| can be |
| 316 | // NULL if the value is not needed. |
| 317 | // |
| 318 | // Group probabilities that are later supplied must sum to less than or equal |
| 319 | // to the |total_probability|. Arguments |year|, |month| and |day_of_month| |
| 320 | // specify the expiration time. If the build time is after the expiration time |
| 321 | // then the field trial reverts to the 'default' group. |
| 322 | // |
| 323 | // Use this static method to get a startup-randomized FieldTrial or a |
| 324 | // previously created forced FieldTrial. If you want a one-time randomized |
| 325 | // trial, call UseOneTimeRandomization() right after creation. |
| 326 | static FieldTrial* FactoryGetFieldTrial( |
| 327 | const std::string& name, |
| 328 | FieldTrial::Probability total_probability, |
| 329 | const std::string& default_group_name, |
| 330 | const int year, |
| 331 | const int month, |
| 332 | const int day_of_month, |
| 333 | int* default_group_number); |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 334 | |
| 335 | // The Find() method can be used to test to see if a named Trial was already |
| 336 | // registered, or to retrieve a pointer to it from the global map. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 337 | static FieldTrial* Find(const std::string& name); |
| 338 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 339 | // Returns the group number chosen for the named trial, or |
| 340 | // FieldTrial::kNotFinalized if the trial does not exist. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 341 | static int FindValue(const std::string& name); |
| 342 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 343 | // Returns the group name chosen for the named trial, or the |
| 344 | // empty string if the trial does not exist. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 345 | static std::string FindFullName(const std::string& name); |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 346 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 347 | // Returns true if the named trial has been registered. |
| 348 | static bool TrialExists(const std::string& name); |
| 349 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 350 | // Creates a persistent representation of all FieldTrial instances for |
| 351 | // resurrection in another process. This allows randomization to be done in |
| 352 | // one process, and secondary processes can be synchronized on the result. |
| 353 | // The resulting string contains the name and group name pairs for all trials, |
| 354 | // with "/" used to separate all names and to terminate the string. This |
| 355 | // string is parsed by CreateTrialsFromString(). |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 356 | static void StatesToString(std::string* output); |
| 357 | |
[email protected] | ad2461c | 2012-04-27 21:11:03 | [diff] [blame] | 358 | // Fills in the supplied vector |selected_groups| (which must be empty when |
| 359 | // called) with a snapshot of all existing FieldTrials for which a group has |
| 360 | // been chosen (if the group is not yet known, then it excluded from the |
| 361 | // vector). |
| 362 | static void GetFieldTrialSelectedGroups( |
| 363 | FieldTrial::SelectedGroups* selected_groups); |
[email protected] | 25655dd | 2012-01-27 13:50:26 | [diff] [blame] | 364 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 365 | // Use a state string (re: StatesToString()) to augment the current list of |
| 366 | // field tests to include the supplied tests, and using a 100% probability for |
| 367 | // each test, force them to have the same group string. This is commonly used |
| 368 | // in a non-browser process, to carry randomly selected state in a browser |
| 369 | // process into this non-browser process, but could also be invoked through a |
| 370 | // command line argument to the browser process. |
| 371 | static bool CreateTrialsFromString(const std::string& prior_trials); |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 372 | |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 373 | // Create a FieldTrial with the given |name| and using 100% probability for |
| 374 | // the FieldTrial, force FieldTrial to have the same group string as |
| 375 | // |group_name|. This is commonly used in a non-browser process, to carry |
| 376 | // randomly selected state in a browser process into this non-browser process. |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 377 | // It returns NULL if there is a FieldTrial that is already registered with |
| 378 | // the same |name| but has different finalized group string (|group_name|). |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 379 | static FieldTrial* CreateFieldTrial(const std::string& name, |
| 380 | const std::string& group_name); |
| 381 | |
| 382 | // Add an observer to be notified when a field trial is irrevocably committed |
| 383 | // to being part of some specific field_group (and hence the group_name is |
| 384 | // also finalized for that field_trial). |
| 385 | static void AddObserver(Observer* observer); |
| 386 | |
| 387 | // Remove an observer. |
| 388 | static void RemoveObserver(Observer* observer); |
| 389 | |
| 390 | // Notify all observers that a group is finalized for the named Trial. |
| 391 | static void NotifyFieldTrialGroupSelection(const std::string& name, |
| 392 | const std::string& group_name); |
| 393 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 394 | // The time of construction of the global map is recorded in a static variable |
| 395 | // and is commonly used by experiments to identify the time since the start |
| 396 | // of the application. In some experiments it may be useful to discount |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 397 | // data that is gathered before the application has reached sufficient |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 398 | // stability (example: most DLL have loaded, etc.) |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 399 | static TimeTicks application_start_time() { |
[email protected] | 423041b | 2008-10-27 17:39:28 | [diff] [blame] | 400 | if (global_) |
| 401 | return global_->application_start_time_; |
| 402 | // For testing purposes only, or when we don't yet have a start time. |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 403 | return TimeTicks::Now(); |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 404 | } |
| 405 | |
[email protected] | e8d82c61 | 2010-12-07 22:54:27 | [diff] [blame] | 406 | // Return the number of active field trials. |
| 407 | static size_t GetFieldTrialCount(); |
| 408 | |
[email protected] | 20f999b5 | 2012-08-24 22:32:59 | [diff] [blame] | 409 | // If one-time randomization is enabled, returns a weak pointer to the |
| 410 | // corresponding EntropyProvider. Otherwise, returns NULL. |
| 411 | static const FieldTrial::EntropyProvider* |
| 412 | GetEntropyProviderForOneTimeRandomization(); |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 413 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 414 | private: |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 415 | // A map from FieldTrial names to the actual instances. |
[email protected] | 9660b97 | 2009-03-02 19:02:56 | [diff] [blame] | 416 | typedef std::map<std::string, FieldTrial*> RegistrationList; |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 417 | |
[email protected] | a502bbe7 | 2011-01-07 18:06:45 | [diff] [blame] | 418 | // Helper function should be called only while holding lock_. |
| 419 | FieldTrial* PreLockedFind(const std::string& name); |
| 420 | |
[email protected] | 2d472958 | 2012-04-12 07:08:07 | [diff] [blame] | 421 | // Register() stores a pointer to the given trial in a global map. |
| 422 | // This method also AddRef's the indicated trial. |
| 423 | // This should always be called after creating a new FieldTrial instance. |
| 424 | static void Register(FieldTrial* trial); |
| 425 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 426 | static FieldTrialList* global_; // The singleton of this class. |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 427 | |
[email protected] | 0f1373a | 2011-06-22 23:51:24 | [diff] [blame] | 428 | // This will tell us if there is an attempt to register a field |
| 429 | // trial or check if one-time randomization is enabled without |
| 430 | // creating the FieldTrialList. This is not an error, unless a |
| 431 | // FieldTrialList is created after that. |
| 432 | static bool used_without_global_; |
[email protected] | 7e05f6c4 | 2009-07-11 01:50:48 | [diff] [blame] | 433 | |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 434 | // A helper value made available to users, that shows when the FieldTrialList |
[email protected] | e695fbd6 | 2009-06-30 16:31:54 | [diff] [blame] | 435 | // was initialized. Note that this is a singleton instance, and hence is a |
| 436 | // good approximation to the start of the process. |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 437 | TimeTicks application_start_time_; |
[email protected] | 0b48db4 | 2009-03-23 02:45:11 | [diff] [blame] | 438 | |
| 439 | // Lock for access to registered_. |
[email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 440 | base::Lock lock_; |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 441 | RegistrationList registered_; |
| 442 | |
[email protected] | 20f999b5 | 2012-08-24 22:32:59 | [diff] [blame] | 443 | // Entropy provider to be used for one-time randomized field trials. If NULL, |
| 444 | // one-time randomization is not supported. |
| 445 | scoped_ptr<const FieldTrial::EntropyProvider> entropy_provider_; |
[email protected] | edafd4c | 2011-05-10 17:18:53 | [diff] [blame] | 446 | |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 447 | // List of observers to be notified when a group is selected for a FieldTrial. |
[email protected] | 8cffde0e | 2012-05-04 01:14:14 | [diff] [blame] | 448 | scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_; |
[email protected] | 63a8ba1 | 2011-04-29 05:42:22 | [diff] [blame] | 449 | |
[email protected] | ac262c9f | 2008-10-19 17:45:21 | [diff] [blame] | 450 | DISALLOW_COPY_AND_ASSIGN(FieldTrialList); |
| 451 | }; |
| 452 | |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 453 | } // namespace base |
| 454 | |
| 455 | #endif // BASE_METRICS_FIELD_TRIAL_H_ |