• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).
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.
default_group_name()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.
group_name_internal()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.)
application_start_time()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